@shapeshiftoss/hdwallet-keepkey 1.62.39 → 1.62.42
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +6 -6
- package/dist/cjs/.tsbuildinfo +1 -0
- package/dist/{adapter.d.ts → cjs/adapter.d.ts} +6 -7
- package/dist/cjs/adapter.js +146 -0
- package/dist/{binance.d.ts → cjs/binance.d.ts} +3 -4
- package/dist/cjs/binance.js +144 -0
- package/dist/{bitcoin.d.ts → cjs/bitcoin.d.ts} +3 -4
- package/dist/cjs/bitcoin.js +549 -0
- package/dist/{bnbencoding.d.ts → cjs/bnbencoding.d.ts} +3 -2
- package/dist/{bnbencoding.js → cjs/bnbencoding.js} +22 -22
- package/dist/{cosmos.d.ts → cjs/cosmos.d.ts} +4 -5
- package/dist/cjs/cosmos.js +219 -0
- package/dist/{eos.d.ts → cjs/eos.d.ts} +3 -4
- package/dist/cjs/eos.js +244 -0
- package/dist/{ethereum.d.ts → cjs/ethereum.d.ts} +4 -5
- package/dist/cjs/ethereum.js +264 -0
- package/dist/cjs/index.d.ts +5 -0
- package/dist/{index.js → cjs/index.js} +0 -1
- package/dist/{keepkey.d.ts → cjs/keepkey.d.ts} +45 -20
- package/dist/cjs/keepkey.js +959 -0
- package/dist/{mayachain.d.ts → cjs/mayachain.d.ts} +4 -5
- package/dist/cjs/mayachain.js +169 -0
- package/dist/{osmosis.d.ts → cjs/osmosis.d.ts} +4 -5
- package/dist/cjs/osmosis.js +270 -0
- package/dist/cjs/package.json +1 -0
- package/dist/{ripple.d.ts → cjs/ripple.d.ts} +3 -4
- package/dist/cjs/ripple.js +104 -0
- package/dist/{thorchain.d.ts → cjs/thorchain.d.ts} +4 -5
- package/dist/cjs/thorchain.js +169 -0
- package/dist/{transport.d.ts → cjs/transport.d.ts} +2 -3
- package/dist/cjs/transport.js +333 -0
- package/dist/{typeRegistry.d.ts → cjs/typeRegistry.d.ts} +2 -3
- package/dist/{typeRegistry.js → cjs/typeRegistry.js} +26 -13
- package/dist/{utils.d.ts → cjs/utils.d.ts} +1 -2
- package/dist/{utils.js → cjs/utils.js} +7 -8
- package/dist/esm/.tsbuildinfo +1 -0
- package/dist/esm/adapter.d.ts +35 -0
- package/dist/esm/adapter.js +119 -0
- package/dist/esm/binance.d.ts +5 -0
- package/dist/esm/binance.js +112 -0
- package/dist/esm/bitcoin.d.ts +11 -0
- package/dist/esm/bitcoin.js +511 -0
- package/dist/esm/bnbencoding.d.ts +11 -0
- package/dist/esm/bnbencoding.js +103 -0
- package/dist/esm/cosmos.d.ts +6 -0
- package/dist/esm/cosmos.js +187 -0
- package/dist/esm/eos.d.ts +5 -0
- package/dist/esm/eos.js +212 -0
- package/dist/esm/ethereum.d.ts +17 -0
- package/dist/esm/ethereum.js +229 -0
- package/dist/esm/index.d.ts +5 -0
- package/dist/esm/index.js +5 -0
- package/dist/esm/keepkey.d.ts +177 -0
- package/dist/esm/keepkey.js +925 -0
- package/dist/esm/mayachain.d.ts +6 -0
- package/dist/esm/mayachain.js +137 -0
- package/dist/esm/osmosis.d.ts +6 -0
- package/dist/esm/osmosis.js +238 -0
- package/dist/esm/ripple.d.ts +5 -0
- package/dist/esm/ripple.js +72 -0
- package/dist/esm/thorchain.d.ts +6 -0
- package/dist/esm/thorchain.js +137 -0
- package/dist/esm/transport.d.ts +55 -0
- package/dist/esm/transport.js +306 -0
- package/dist/esm/typeRegistry.d.ts +4 -0
- package/dist/esm/typeRegistry.js +47 -0
- package/dist/esm/utils.d.ts +6 -0
- package/dist/esm/utils.js +56 -0
- package/package.json +33 -19
- package/dist/adapter.d.ts.map +0 -1
- package/dist/adapter.js +0 -175
- package/dist/adapter.js.map +0 -1
- package/dist/binance.d.ts.map +0 -1
- package/dist/binance.js +0 -146
- package/dist/binance.js.map +0 -1
- package/dist/bitcoin.d.ts.map +0 -1
- package/dist/bitcoin.js +0 -568
- package/dist/bitcoin.js.map +0 -1
- package/dist/bnbencoding.d.ts.map +0 -1
- package/dist/bnbencoding.js.map +0 -1
- package/dist/cosmos.d.ts.map +0 -1
- package/dist/cosmos.js +0 -230
- package/dist/cosmos.js.map +0 -1
- package/dist/eos.d.ts.map +0 -1
- package/dist/eos.js +0 -247
- package/dist/eos.js.map +0 -1
- package/dist/ethereum.d.ts.map +0 -1
- package/dist/ethereum.js +0 -281
- package/dist/ethereum.js.map +0 -1
- package/dist/index.d.ts +0 -6
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/keepkey.d.ts.map +0 -1
- package/dist/keepkey.js +0 -1027
- package/dist/keepkey.js.map +0 -1
- package/dist/mayachain.d.ts.map +0 -1
- package/dist/mayachain.js +0 -180
- package/dist/mayachain.js.map +0 -1
- package/dist/osmosis.d.ts.map +0 -1
- package/dist/osmosis.js +0 -278
- package/dist/osmosis.js.map +0 -1
- package/dist/ripple.d.ts.map +0 -1
- package/dist/ripple.js +0 -111
- package/dist/ripple.js.map +0 -1
- package/dist/thorchain.d.ts.map +0 -1
- package/dist/thorchain.js +0 -180
- package/dist/thorchain.js.map +0 -1
- package/dist/transport.d.ts.map +0 -1
- package/dist/transport.js +0 -365
- package/dist/transport.js.map +0 -1
- package/dist/typeRegistry.d.ts.map +0 -1
- package/dist/typeRegistry.js.map +0 -1
- package/dist/utils.d.ts.map +0 -1
- package/dist/utils.js.map +0 -1
|
@@ -0,0 +1,511 @@
|
|
|
1
|
+
import * as Messages from '@keepkey/device-protocol/lib/messages_pb';
|
|
2
|
+
import * as Types from '@keepkey/device-protocol/lib/types_pb';
|
|
3
|
+
import * as bitcoinjs from '@shapeshiftoss/bitcoinjs-lib';
|
|
4
|
+
import * as core from '@shapeshiftoss/hdwallet-core';
|
|
5
|
+
import assert from 'assert';
|
|
6
|
+
import { thaw } from 'icepick';
|
|
7
|
+
import { toUTF8Array, translateInputScriptType, translateOutputScriptType } from './utils.js';
|
|
8
|
+
// FIXME: load this from the device's coin table, or from some static features
|
|
9
|
+
// table... instead of, you know, adding another God-forsaken coin table.
|
|
10
|
+
// :facepalm:
|
|
11
|
+
const supportedCoins = [
|
|
12
|
+
'Bitcoin',
|
|
13
|
+
'Testnet',
|
|
14
|
+
'BitcoinCash',
|
|
15
|
+
'BitcoinGold',
|
|
16
|
+
'Litecoin',
|
|
17
|
+
'Dash',
|
|
18
|
+
'DigiByte',
|
|
19
|
+
'Dogecoin',
|
|
20
|
+
];
|
|
21
|
+
const segwitCoins = ['Bitcoin', 'Testnet', 'BitcoinGold', 'Litecoin'];
|
|
22
|
+
function legacyAccount(coin, slip44, accountIdx) {
|
|
23
|
+
return {
|
|
24
|
+
coin,
|
|
25
|
+
scriptType: core.BTCInputScriptType.SpendAddress,
|
|
26
|
+
addressNList: [0x80000000 + 44, 0x80000000 + slip44, 0x80000000 + accountIdx],
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function segwitAccount(coin, slip44, accountIdx) {
|
|
30
|
+
return {
|
|
31
|
+
coin,
|
|
32
|
+
scriptType: core.BTCInputScriptType.SpendP2SHWitness,
|
|
33
|
+
addressNList: [0x80000000 + 49, 0x80000000 + slip44, 0x80000000 + accountIdx],
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
function segwitNativeAccount(coin, slip44, accountIdx) {
|
|
37
|
+
return {
|
|
38
|
+
coin,
|
|
39
|
+
scriptType: core.BTCInputScriptType.SpendWitness,
|
|
40
|
+
addressNList: [0x80000000 + 84, 0x80000000 + slip44, 0x80000000 + accountIdx],
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
function packVarint(n) {
|
|
44
|
+
if (n < 253)
|
|
45
|
+
return n.toString(16).padStart(2, '0');
|
|
46
|
+
else if (n < 0xffff)
|
|
47
|
+
return 'FD' + n.toString(16).padStart(4, '0');
|
|
48
|
+
else if (n < 0xffffffff)
|
|
49
|
+
return 'FE' + n.toString(16).padStart(8, '0');
|
|
50
|
+
else
|
|
51
|
+
return 'FF' + n.toString(16).padStart(16, '0');
|
|
52
|
+
}
|
|
53
|
+
function prepareSignTx(coin, inputs, outputs) {
|
|
54
|
+
const unsignedTx = new Types.TransactionType();
|
|
55
|
+
unsignedTx.setInputsCnt(inputs.length);
|
|
56
|
+
unsignedTx.setOutputsCnt(outputs.length);
|
|
57
|
+
inputs.forEach((input, i) => {
|
|
58
|
+
const utxo = new Types.TxInputType();
|
|
59
|
+
utxo.setPrevHash(core.fromHexString(input.txid));
|
|
60
|
+
utxo.setPrevIndex(input.vout);
|
|
61
|
+
if (input.sequence !== undefined)
|
|
62
|
+
utxo.setSequence(input.sequence);
|
|
63
|
+
utxo.setScriptType(translateInputScriptType(input.scriptType));
|
|
64
|
+
utxo.setAddressNList(input.addressNList);
|
|
65
|
+
utxo.setAmount(Number(input.amount));
|
|
66
|
+
unsignedTx.addInputs(utxo, i);
|
|
67
|
+
});
|
|
68
|
+
outputs.forEach((o, k) => {
|
|
69
|
+
const output = o;
|
|
70
|
+
const newOutput = new Types.TxOutputType();
|
|
71
|
+
newOutput.setAmount(Number(output.amount));
|
|
72
|
+
// map to BCH address format
|
|
73
|
+
if (coin === 'BitcoinCash') {
|
|
74
|
+
if (output.address && !output.address.startsWith('bitcoincash:')) {
|
|
75
|
+
output.address = 'bitcoincash:' + output.address;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (output.isChange || output.addressType === core.BTCOutputAddressType.Transfer) {
|
|
79
|
+
// BTCSignTxOutputTranfer || BTCSignTxOutputChange
|
|
80
|
+
newOutput.setScriptType(translateOutputScriptType(output.scriptType));
|
|
81
|
+
newOutput.setAddressNList(output.addressNList);
|
|
82
|
+
newOutput.setAddressType(output.isChange ? Types.OutputAddressType.CHANGE : Types.OutputAddressType.TRANSFER);
|
|
83
|
+
}
|
|
84
|
+
else if (output.opReturnData !== undefined && output.opReturnData !== null) {
|
|
85
|
+
// BTCSignTxOutputMemo
|
|
86
|
+
newOutput.setScriptType(Types.OutputScriptType.PAYTOOPRETURN);
|
|
87
|
+
newOutput.setAddressType(Types.OutputAddressType.SPEND);
|
|
88
|
+
newOutput.setOpReturnData(output.opReturnData);
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
// BTCSignTxOutputSpend
|
|
92
|
+
newOutput.setScriptType(Types.OutputScriptType.PAYTOADDRESS);
|
|
93
|
+
assert(output.address !== undefined, 'Output must have a valid BTC address.');
|
|
94
|
+
newOutput.setAddress(output.address);
|
|
95
|
+
newOutput.setAddressType(Types.OutputAddressType.SPEND);
|
|
96
|
+
}
|
|
97
|
+
unsignedTx.addOutputs(newOutput, k);
|
|
98
|
+
});
|
|
99
|
+
const txmap = {}; // Create a map of transactions by txid needed for the KeepKey signing flow.
|
|
100
|
+
txmap['unsigned'] = unsignedTx;
|
|
101
|
+
const forceBip143Coins = ['BitcoinGold', 'BitcoinCash', 'BitcoinSV'];
|
|
102
|
+
if (forceBip143Coins.includes(coin))
|
|
103
|
+
return txmap;
|
|
104
|
+
inputs.forEach(inputTx => {
|
|
105
|
+
if (inputTx.txid in txmap)
|
|
106
|
+
return;
|
|
107
|
+
if (inputTx.scriptType === core.BTCInputScriptType.SpendP2SHWitness ||
|
|
108
|
+
inputTx.scriptType === core.BTCInputScriptType.SpendWitness ||
|
|
109
|
+
inputTx.scriptType === core.BTCInputScriptType.External)
|
|
110
|
+
return;
|
|
111
|
+
const prevTx = (() => {
|
|
112
|
+
if (inputTx.tx)
|
|
113
|
+
return inputTx.tx;
|
|
114
|
+
if (!inputTx.hex)
|
|
115
|
+
throw new Error('non-segwit inputs must have the associated prev tx');
|
|
116
|
+
const tx = bitcoinjs.Transaction.fromHex(inputTx.hex);
|
|
117
|
+
return {
|
|
118
|
+
version: tx.version,
|
|
119
|
+
locktime: tx.locktime,
|
|
120
|
+
vin: tx.ins.map(input => ({
|
|
121
|
+
txid: Buffer.from(input.hash).reverse().toString('hex'),
|
|
122
|
+
vout: input.index,
|
|
123
|
+
scriptSig: {
|
|
124
|
+
hex: Buffer.from(input.script).toString('hex'),
|
|
125
|
+
},
|
|
126
|
+
sequence: input.sequence,
|
|
127
|
+
})),
|
|
128
|
+
vout: tx.outs.map(output => ({
|
|
129
|
+
value: String(output.value),
|
|
130
|
+
scriptPubKey: {
|
|
131
|
+
hex: Buffer.from(output.script).toString('hex'),
|
|
132
|
+
},
|
|
133
|
+
})),
|
|
134
|
+
};
|
|
135
|
+
})();
|
|
136
|
+
const tx = new Types.TransactionType();
|
|
137
|
+
tx.setVersion(prevTx.version);
|
|
138
|
+
tx.setLockTime(prevTx.locktime);
|
|
139
|
+
tx.setInputsCnt(prevTx.vin.length);
|
|
140
|
+
tx.setOutputsCnt(prevTx.vout.length);
|
|
141
|
+
prevTx.vin.forEach((vin, i) => {
|
|
142
|
+
const txInput = new Types.TxInputType();
|
|
143
|
+
if ('coinbase' in vin) {
|
|
144
|
+
txInput.setPrevHash(core.fromHexString('\0'.repeat(64)));
|
|
145
|
+
txInput.setPrevIndex(0xffffffff);
|
|
146
|
+
txInput.setScriptSig(core.fromHexString(core.mustBeDefined(vin.coinbase)));
|
|
147
|
+
txInput.setSequence(vin.sequence);
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
txInput.setPrevHash(core.fromHexString(vin.txid));
|
|
151
|
+
txInput.setPrevIndex(vin.vout);
|
|
152
|
+
txInput.setScriptSig(core.fromHexString(vin.scriptSig.hex));
|
|
153
|
+
txInput.setSequence(vin.sequence);
|
|
154
|
+
}
|
|
155
|
+
tx.addInputs(txInput, i);
|
|
156
|
+
});
|
|
157
|
+
prevTx.vout.forEach((vout, i) => {
|
|
158
|
+
const txOutput = new Types.TxOutputBinType();
|
|
159
|
+
txOutput.setAmount(Number(vout.value));
|
|
160
|
+
txOutput.setScriptPubkey(core.fromHexString(vout.scriptPubKey.hex));
|
|
161
|
+
tx.addBinOutputs(txOutput, i);
|
|
162
|
+
});
|
|
163
|
+
if (coin === 'Dash') {
|
|
164
|
+
const dip2_type = prevTx.type || 0;
|
|
165
|
+
// DIP2 Special Tx with payload
|
|
166
|
+
if (prevTx.version === 3 && dip2_type !== 0) {
|
|
167
|
+
if (!prevTx.extraPayload)
|
|
168
|
+
throw new Error('Payload missing in DIP2 transaction');
|
|
169
|
+
tx.setExtraData(core.fromHexString(packVarint(prevTx.extraPayload.length * 2) + prevTx.extraPayload));
|
|
170
|
+
}
|
|
171
|
+
// Trezor (and therefore KeepKey) firmware doesn't understand the
|
|
172
|
+
// split of version and type, so let's mimic the old serialization
|
|
173
|
+
// format
|
|
174
|
+
tx.setVersion(prevTx.version | (dip2_type << 16));
|
|
175
|
+
}
|
|
176
|
+
txmap[inputTx.txid] = tx;
|
|
177
|
+
});
|
|
178
|
+
return txmap;
|
|
179
|
+
}
|
|
180
|
+
async function ensureCoinSupport(wallet, coin) {
|
|
181
|
+
if (!supportedCoins.includes(coin))
|
|
182
|
+
throw new Error(`'${coin}' not yet supported in HDWalletKeepKey`);
|
|
183
|
+
if (!wallet.btcSupportsCoin(coin))
|
|
184
|
+
throw new Error(`'${coin} is not supported in this firmware version`);
|
|
185
|
+
}
|
|
186
|
+
function validateVoutOrdering(msg) {
|
|
187
|
+
// From THORChain specification:
|
|
188
|
+
/* ignoreTx checks if we can already ignore a tx according to preset rules
|
|
189
|
+
|
|
190
|
+
we expect array of "vout" for a BTC to have this format
|
|
191
|
+
OP_RETURN is mandatory only on inbound tx
|
|
192
|
+
vout:0 is our vault
|
|
193
|
+
vout:1 is any any change back to themselves
|
|
194
|
+
vout:2 is OP_RETURN (first 80 bytes)
|
|
195
|
+
vout:3 is OP_RETURN (next 80 bytes)
|
|
196
|
+
|
|
197
|
+
Rules to ignore a tx are:
|
|
198
|
+
- vout:0 doesn't have coins (value)
|
|
199
|
+
- vout:0 doesn't have address
|
|
200
|
+
- count vouts > 4
|
|
201
|
+
- count vouts with coins (value) > 2
|
|
202
|
+
*/
|
|
203
|
+
// Check that vout:0 contains the vault address
|
|
204
|
+
if (msg.outputs[0].address != msg.vaultAddress) {
|
|
205
|
+
return false;
|
|
206
|
+
}
|
|
207
|
+
// Check that vout:1 is change address
|
|
208
|
+
if (msg.outputs[1].addressType != core.BTCOutputAddressType.Change) {
|
|
209
|
+
return false;
|
|
210
|
+
}
|
|
211
|
+
// Check and make sure vout:2 has OP_RETURN data
|
|
212
|
+
if (!(msg.outputs[2] && msg.outputs[2]?.opReturnData)) {
|
|
213
|
+
return false;
|
|
214
|
+
}
|
|
215
|
+
return true;
|
|
216
|
+
}
|
|
217
|
+
export async function btcSupportsCoin(coin) {
|
|
218
|
+
// FIXME: inspect the CoinTable to determine which coins are actually supported by the device.
|
|
219
|
+
return supportedCoins.includes(coin);
|
|
220
|
+
}
|
|
221
|
+
export async function btcSupportsScriptType(coin, scriptType) {
|
|
222
|
+
if (!supportedCoins.includes(coin))
|
|
223
|
+
return false;
|
|
224
|
+
if (!segwitCoins.includes(coin) && scriptType === core.BTCInputScriptType.SpendP2SHWitness)
|
|
225
|
+
return false;
|
|
226
|
+
if (!segwitCoins.includes(coin) && scriptType === core.BTCInputScriptType.SpendWitness)
|
|
227
|
+
return false;
|
|
228
|
+
return true;
|
|
229
|
+
}
|
|
230
|
+
export async function btcGetAddress(wallet, transport, msg) {
|
|
231
|
+
await ensureCoinSupport(wallet, msg.coin);
|
|
232
|
+
const addr = new Messages.GetAddress();
|
|
233
|
+
addr.setAddressNList(msg.addressNList);
|
|
234
|
+
addr.setCoinName(msg.coin);
|
|
235
|
+
addr.setShowDisplay(msg.showDisplay || false);
|
|
236
|
+
addr.setScriptType(translateInputScriptType(msg.scriptType || core.BTCInputScriptType.SpendAddress));
|
|
237
|
+
const response = await transport.call(Messages.MessageType.MESSAGETYPE_GETADDRESS, addr, {
|
|
238
|
+
msgTimeout: core.LONG_TIMEOUT,
|
|
239
|
+
});
|
|
240
|
+
if (response.message_type === core.Events.CANCEL)
|
|
241
|
+
throw response;
|
|
242
|
+
const btcAddress = response.proto;
|
|
243
|
+
return core.mustBeDefined(btcAddress.getAddress());
|
|
244
|
+
}
|
|
245
|
+
export async function btcSignTx(wallet, transport, msgIn) {
|
|
246
|
+
return transport.lockDuring(async () => {
|
|
247
|
+
// Make a copy of the input parameter so as to not mutate the caller's data.
|
|
248
|
+
// Unfreezing a recursively-frozen object is nontrivial, so we leverage an existing package
|
|
249
|
+
const msg = thaw(msgIn);
|
|
250
|
+
msg.outputs = thaw(msgIn.outputs);
|
|
251
|
+
await ensureCoinSupport(wallet, msg.coin);
|
|
252
|
+
if (msg.opReturnData) {
|
|
253
|
+
if (msg.opReturnData.length > 80) {
|
|
254
|
+
throw new Error('OP_RETURN output character count is too damn high.');
|
|
255
|
+
}
|
|
256
|
+
msg.outputs.push({
|
|
257
|
+
addressType: core.BTCOutputAddressType.Spend,
|
|
258
|
+
opReturnData: Buffer.from(msg.opReturnData).toString('base64'),
|
|
259
|
+
amount: '0',
|
|
260
|
+
isChange: false,
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
// If this is a THORChain transaction, validate the vout ordering
|
|
264
|
+
if (msg.vaultAddress && !validateVoutOrdering(msg)) {
|
|
265
|
+
throw new Error('Improper vout ordering for BTC Thorchain transaction');
|
|
266
|
+
}
|
|
267
|
+
const txmap = prepareSignTx(msg.coin, msg.inputs, msg.outputs);
|
|
268
|
+
// Prepare and send initial message
|
|
269
|
+
const tx = new Messages.SignTx();
|
|
270
|
+
tx.setInputsCount(msg.inputs.length);
|
|
271
|
+
tx.setOutputsCount(msg.outputs.length);
|
|
272
|
+
tx.setCoinName(msg.coin);
|
|
273
|
+
if (msg.version !== undefined)
|
|
274
|
+
tx.setVersion(msg.version);
|
|
275
|
+
tx.setLockTime(msg.locktime || 0);
|
|
276
|
+
let responseType;
|
|
277
|
+
let response;
|
|
278
|
+
const { message_enum, proto } = await transport.call(Messages.MessageType.MESSAGETYPE_SIGNTX, tx, {
|
|
279
|
+
msgTimeout: core.LONG_TIMEOUT,
|
|
280
|
+
omitLock: true,
|
|
281
|
+
}); // 5 Minute timeout
|
|
282
|
+
responseType = message_enum;
|
|
283
|
+
response = proto;
|
|
284
|
+
// Prepare structure for signatures
|
|
285
|
+
const signatures = new Array(msg.inputs.length).fill(null);
|
|
286
|
+
let serializedTx = '';
|
|
287
|
+
try {
|
|
288
|
+
// Begin callback loop
|
|
289
|
+
// eslint-disable-next-line no-constant-condition
|
|
290
|
+
while (true) {
|
|
291
|
+
if (responseType !== Messages.MessageType.MESSAGETYPE_TXREQUEST) {
|
|
292
|
+
throw new Error(`Unexpected message type: ${responseType}`);
|
|
293
|
+
}
|
|
294
|
+
const txRequest = response;
|
|
295
|
+
// If there's some part of signed transaction, add it
|
|
296
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
297
|
+
if (txRequest.hasSerialized() && txRequest.getSerialized().hasSerializedTx()) {
|
|
298
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
299
|
+
serializedTx += core.toHexString(txRequest.getSerialized().getSerializedTx_asU8());
|
|
300
|
+
}
|
|
301
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
302
|
+
if (txRequest.hasSerialized() && txRequest.getSerialized().hasSignatureIndex()) {
|
|
303
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
304
|
+
const sigIdx = txRequest.getSerialized().getSignatureIndex();
|
|
305
|
+
if (signatures[sigIdx] !== null) {
|
|
306
|
+
throw new Error(`Signature for index ${sigIdx} already filled`);
|
|
307
|
+
}
|
|
308
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
309
|
+
signatures[sigIdx] = core.toHexString(txRequest.getSerialized().getSignature_asU8());
|
|
310
|
+
}
|
|
311
|
+
if (txRequest.getRequestType() === Types.RequestType.TXFINISHED) {
|
|
312
|
+
// Device didn't ask for more information, finish workflow
|
|
313
|
+
break;
|
|
314
|
+
}
|
|
315
|
+
let currentTx;
|
|
316
|
+
let currentMsg;
|
|
317
|
+
let txAck;
|
|
318
|
+
// Device asked for one more information, let's process it.
|
|
319
|
+
if (!txRequest.hasDetails())
|
|
320
|
+
throw new Error('expected details');
|
|
321
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
322
|
+
const reqDetails = txRequest.getDetails();
|
|
323
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
324
|
+
if (!reqDetails.hasTxHash()) {
|
|
325
|
+
currentTx = txmap['unsigned'];
|
|
326
|
+
}
|
|
327
|
+
else {
|
|
328
|
+
currentTx = txmap[core.toHexString(reqDetails.getTxHash_asU8())];
|
|
329
|
+
}
|
|
330
|
+
if (txRequest.getRequestType() === Types.RequestType.TXMETA) {
|
|
331
|
+
currentMsg = new Types.TransactionType();
|
|
332
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
333
|
+
if (currentTx.hasVersion())
|
|
334
|
+
currentMsg.setVersion(currentTx.getVersion());
|
|
335
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
336
|
+
if (currentTx.hasLockTime())
|
|
337
|
+
currentMsg.setLockTime(currentTx.getLockTime());
|
|
338
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
339
|
+
if (currentTx.hasInputsCnt())
|
|
340
|
+
currentMsg.setInputsCnt(currentTx.getInputsCnt());
|
|
341
|
+
if (reqDetails.hasTxHash()) {
|
|
342
|
+
currentMsg.setOutputsCnt(currentTx.getBinOutputsList().length);
|
|
343
|
+
}
|
|
344
|
+
else {
|
|
345
|
+
currentMsg.setOutputsCnt(currentTx.getOutputsList().length);
|
|
346
|
+
}
|
|
347
|
+
if (currentTx.hasExtraData()) {
|
|
348
|
+
currentMsg.setExtraDataLen(currentTx.getExtraData_asU8().length);
|
|
349
|
+
}
|
|
350
|
+
else {
|
|
351
|
+
currentMsg.setExtraDataLen(0);
|
|
352
|
+
}
|
|
353
|
+
txAck = new Messages.TxAck();
|
|
354
|
+
txAck.setTx(currentMsg);
|
|
355
|
+
const message = await transport.call(Messages.MessageType.MESSAGETYPE_TXACK, txAck, {
|
|
356
|
+
msgTimeout: core.LONG_TIMEOUT,
|
|
357
|
+
omitLock: true,
|
|
358
|
+
}); // 5 Minute timeout
|
|
359
|
+
responseType = message.message_enum;
|
|
360
|
+
response = message.proto;
|
|
361
|
+
continue;
|
|
362
|
+
}
|
|
363
|
+
if (txRequest.getRequestType() === Types.RequestType.TXINPUT) {
|
|
364
|
+
if (!reqDetails.hasRequestIndex())
|
|
365
|
+
throw new Error('expected request index');
|
|
366
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
367
|
+
const reqIndex = reqDetails.getRequestIndex();
|
|
368
|
+
currentMsg = new Types.TransactionType();
|
|
369
|
+
currentMsg.setInputsList([currentTx.getInputsList()[reqIndex]]);
|
|
370
|
+
txAck = new Messages.TxAck();
|
|
371
|
+
txAck.setTx(currentMsg);
|
|
372
|
+
const message = await transport.call(Messages.MessageType.MESSAGETYPE_TXACK, txAck, {
|
|
373
|
+
msgTimeout: core.LONG_TIMEOUT,
|
|
374
|
+
omitLock: true,
|
|
375
|
+
}); // 5 Minute timeout
|
|
376
|
+
responseType = message.message_enum;
|
|
377
|
+
response = message.proto;
|
|
378
|
+
continue;
|
|
379
|
+
}
|
|
380
|
+
if (txRequest.getRequestType() === Types.RequestType.TXOUTPUT) {
|
|
381
|
+
if (!reqDetails.hasRequestIndex())
|
|
382
|
+
throw new Error('expected request index');
|
|
383
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
384
|
+
const reqIndex = reqDetails.getRequestIndex();
|
|
385
|
+
currentMsg = new Types.TransactionType();
|
|
386
|
+
if (reqDetails.hasTxHash()) {
|
|
387
|
+
currentMsg.setBinOutputsList([currentTx.getBinOutputsList()[reqIndex]]);
|
|
388
|
+
}
|
|
389
|
+
else {
|
|
390
|
+
currentMsg.setOutputsList([currentTx.getOutputsList()[reqIndex]]);
|
|
391
|
+
currentMsg.setOutputsCnt(1);
|
|
392
|
+
}
|
|
393
|
+
txAck = new Messages.TxAck();
|
|
394
|
+
txAck.setTx(currentMsg);
|
|
395
|
+
const message = await transport.call(Messages.MessageType.MESSAGETYPE_TXACK, txAck, {
|
|
396
|
+
msgTimeout: core.LONG_TIMEOUT,
|
|
397
|
+
omitLock: true,
|
|
398
|
+
}); // 5 Minute timeout
|
|
399
|
+
responseType = message.message_enum;
|
|
400
|
+
response = message.proto;
|
|
401
|
+
continue;
|
|
402
|
+
}
|
|
403
|
+
if (txRequest.getRequestType() === Types.RequestType.TXEXTRADATA) {
|
|
404
|
+
if (!reqDetails.hasExtraDataOffset() || !reqDetails.hasExtraDataLen())
|
|
405
|
+
throw new Error('missing extra data offset and length');
|
|
406
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
407
|
+
const offset = reqDetails.getExtraDataOffset();
|
|
408
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
409
|
+
const length = reqDetails.getExtraDataLen();
|
|
410
|
+
currentMsg = new Types.TransactionType();
|
|
411
|
+
currentMsg.setExtraData(currentTx.getExtraData_asU8().slice(offset, offset + length));
|
|
412
|
+
txAck = new Messages.TxAck();
|
|
413
|
+
txAck.setTx(currentMsg);
|
|
414
|
+
const message = await transport.call(Messages.MessageType.MESSAGETYPE_TXACK, txAck, {
|
|
415
|
+
msgTimeout: core.LONG_TIMEOUT,
|
|
416
|
+
omitLock: true,
|
|
417
|
+
}); // 5 Minute timeout
|
|
418
|
+
responseType = message.message_enum;
|
|
419
|
+
response = message.proto;
|
|
420
|
+
continue;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
catch (error) {
|
|
425
|
+
console.error({ error });
|
|
426
|
+
throw new Error('Failed to sign BTC transaction');
|
|
427
|
+
}
|
|
428
|
+
if (signatures.includes(null)) {
|
|
429
|
+
throw new Error('Some signatures are missing!');
|
|
430
|
+
}
|
|
431
|
+
return {
|
|
432
|
+
signatures: signatures,
|
|
433
|
+
serializedTx,
|
|
434
|
+
};
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
export async function btcSupportsSecureTransfer() {
|
|
438
|
+
return true;
|
|
439
|
+
}
|
|
440
|
+
export function btcSupportsNativeShapeShift() {
|
|
441
|
+
return true;
|
|
442
|
+
}
|
|
443
|
+
export async function btcSignMessage(wallet, transport, msg) {
|
|
444
|
+
await ensureCoinSupport(wallet, msg.coin);
|
|
445
|
+
const sign = new Messages.SignMessage();
|
|
446
|
+
sign.setAddressNList(msg.addressNList);
|
|
447
|
+
sign.setMessage(toUTF8Array(msg.message));
|
|
448
|
+
sign.setCoinName(msg.coin || 'Bitcoin');
|
|
449
|
+
sign.setScriptType(translateInputScriptType(msg.scriptType ?? core.BTCInputScriptType.SpendAddress));
|
|
450
|
+
const event = await transport.call(Messages.MessageType.MESSAGETYPE_SIGNMESSAGE, sign, {
|
|
451
|
+
msgTimeout: core.LONG_TIMEOUT,
|
|
452
|
+
});
|
|
453
|
+
const messageSignature = event.proto;
|
|
454
|
+
const address = messageSignature.getAddress();
|
|
455
|
+
if (!address)
|
|
456
|
+
throw new Error('btcSignMessage failed');
|
|
457
|
+
return {
|
|
458
|
+
address,
|
|
459
|
+
signature: core.toHexString(messageSignature.getSignature_asU8()),
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
export async function btcVerifyMessage(wallet, transport, msg) {
|
|
463
|
+
await ensureCoinSupport(wallet, msg.coin);
|
|
464
|
+
const verify = new Messages.VerifyMessage();
|
|
465
|
+
verify.setAddress(msg.address);
|
|
466
|
+
verify.setSignature(core.arrayify('0x' + msg.signature));
|
|
467
|
+
verify.setMessage(toUTF8Array(msg.message));
|
|
468
|
+
verify.setCoinName(msg.coin);
|
|
469
|
+
let event;
|
|
470
|
+
try {
|
|
471
|
+
event = await transport.call(Messages.MessageType.MESSAGETYPE_VERIFYMESSAGE, verify);
|
|
472
|
+
}
|
|
473
|
+
catch (e) {
|
|
474
|
+
if (core.isIndexable(e) && e.message_enum === Messages.MessageType.MESSAGETYPE_FAILURE) {
|
|
475
|
+
return false;
|
|
476
|
+
}
|
|
477
|
+
throw e;
|
|
478
|
+
}
|
|
479
|
+
const success = event.proto;
|
|
480
|
+
return success.getMessage() === 'Message verified';
|
|
481
|
+
}
|
|
482
|
+
export function btcGetAccountPaths(msg) {
|
|
483
|
+
const slip44 = core.slip44ByCoin(msg.coin);
|
|
484
|
+
if (slip44 === undefined)
|
|
485
|
+
return [];
|
|
486
|
+
const bip44 = legacyAccount(msg.coin, slip44, msg.accountIdx);
|
|
487
|
+
const bip49 = segwitAccount(msg.coin, slip44, msg.accountIdx);
|
|
488
|
+
const bip84 = segwitNativeAccount(msg.coin, slip44, msg.accountIdx);
|
|
489
|
+
// For BTC Forks
|
|
490
|
+
const btcLegacy = legacyAccount(msg.coin, core.slip44ByCoin('Bitcoin'), msg.accountIdx);
|
|
491
|
+
const btcSegwit = segwitAccount(msg.coin, core.slip44ByCoin('Bitcoin'), msg.accountIdx);
|
|
492
|
+
const btcSegwitNative = segwitNativeAccount(msg.coin, core.slip44ByCoin('Bitcoin'), msg.accountIdx);
|
|
493
|
+
// For BCH Forks
|
|
494
|
+
const bchLegacy = legacyAccount(msg.coin, core.slip44ByCoin('BitcoinCash'), msg.accountIdx);
|
|
495
|
+
let paths = {
|
|
496
|
+
Bitcoin: [bip44, bip49, bip84],
|
|
497
|
+
Litecoin: [bip44, bip49, bip84],
|
|
498
|
+
Dash: [bip44],
|
|
499
|
+
DigiByte: [bip44, bip49, bip84],
|
|
500
|
+
Dogecoin: [bip44],
|
|
501
|
+
Testnet: [bip44, bip49, bip84],
|
|
502
|
+
BitcoinCash: [bip44, btcLegacy],
|
|
503
|
+
BitcoinSV: [bip44, bchLegacy, btcLegacy],
|
|
504
|
+
BitcoinGold: [bip44, bip49, bip84, btcLegacy, btcSegwit, btcSegwitNative],
|
|
505
|
+
}[msg.coin] ?? [];
|
|
506
|
+
if (msg.scriptType !== undefined)
|
|
507
|
+
paths = paths.filter(path => {
|
|
508
|
+
return path.scriptType === msg.scriptType;
|
|
509
|
+
});
|
|
510
|
+
return paths;
|
|
511
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
/// <reference types="node" />
|
|
3
|
+
import type * as core from '@shapeshiftoss/hdwallet-core';
|
|
4
|
+
export declare function decodeBnbTx(txBytes: Buffer, chainId: string): {
|
|
5
|
+
signBytes: string;
|
|
6
|
+
signBytesHash: string;
|
|
7
|
+
pubKey: Buffer;
|
|
8
|
+
signature: any;
|
|
9
|
+
};
|
|
10
|
+
export declare function validateBnbTx(txBytes: Buffer, chainId: string): boolean;
|
|
11
|
+
export declare function encodeBnbTx(unsignedTx: core.BinanceTx, publicKey: Buffer, signature: Buffer): Buffer;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import ecc from '@bitcoinerlab/secp256k1';
|
|
2
|
+
import * as bnbSdk from 'bnb-javascript-sdk-nobroadcast';
|
|
3
|
+
import CryptoJS from 'crypto-js';
|
|
4
|
+
export function decodeBnbTx(txBytes, chainId) {
|
|
5
|
+
const txDecoded = bnbSdk.amino.decoder.unMarshalBinaryLengthPrefixed(txBytes, {
|
|
6
|
+
aminoPrefix: 'f0625dee',
|
|
7
|
+
msgs: [
|
|
8
|
+
{
|
|
9
|
+
aminoPrefix: '2a2c87fa',
|
|
10
|
+
inputs: [{ address: Buffer.alloc(0), coins: [{ denom: '', amount: 0 }] }],
|
|
11
|
+
outputs: [{ address: Buffer.alloc(0), coins: [{ denom: '', amount: 0 }] }],
|
|
12
|
+
},
|
|
13
|
+
],
|
|
14
|
+
signatures: [
|
|
15
|
+
{
|
|
16
|
+
pubKey: Buffer.alloc(0),
|
|
17
|
+
signature: Buffer.alloc(0),
|
|
18
|
+
accountNumber: 0,
|
|
19
|
+
sequence: 0,
|
|
20
|
+
},
|
|
21
|
+
],
|
|
22
|
+
memo: '',
|
|
23
|
+
source: 0,
|
|
24
|
+
data: Buffer.alloc(0),
|
|
25
|
+
}).val;
|
|
26
|
+
if (txDecoded.data !== null)
|
|
27
|
+
throw new Error('bad data length');
|
|
28
|
+
if (txDecoded.msgs.length !== 1)
|
|
29
|
+
throw new Error('bad msgs length');
|
|
30
|
+
if (txDecoded.signatures.length !== 1)
|
|
31
|
+
throw new Error('bad signatures length');
|
|
32
|
+
const signBytes = JSON.stringify({
|
|
33
|
+
account_number: String(txDecoded.signatures[0].accountNumber),
|
|
34
|
+
chain_id: chainId,
|
|
35
|
+
data: null,
|
|
36
|
+
memo: txDecoded.memo,
|
|
37
|
+
msgs: [
|
|
38
|
+
{
|
|
39
|
+
inputs: txDecoded.msgs[0].inputs.map((x) => ({
|
|
40
|
+
address: bnbSdk.crypto.encodeAddress(x.address, 'bnb'),
|
|
41
|
+
coins: x.coins.map((y) => ({
|
|
42
|
+
amount: Number(y.amount),
|
|
43
|
+
denom: y.denom,
|
|
44
|
+
})),
|
|
45
|
+
})),
|
|
46
|
+
outputs: txDecoded.msgs[0].outputs.map((x) => ({
|
|
47
|
+
address: bnbSdk.crypto.encodeAddress(x.address, 'bnb'),
|
|
48
|
+
coins: x.coins.map((y) => ({
|
|
49
|
+
amount: Number(y.amount),
|
|
50
|
+
denom: y.denom,
|
|
51
|
+
})),
|
|
52
|
+
})),
|
|
53
|
+
},
|
|
54
|
+
],
|
|
55
|
+
sequence: String(txDecoded.signatures[0].sequence),
|
|
56
|
+
source: String(txDecoded.source),
|
|
57
|
+
});
|
|
58
|
+
const signBytesHash = CryptoJS.SHA256(CryptoJS.enc.Utf8.parse(signBytes)).toString();
|
|
59
|
+
const pubKeyAmino = Buffer.from(txDecoded.signatures[0].pubKey);
|
|
60
|
+
if (pubKeyAmino.readUInt32BE(0) !== 0xeb5ae987)
|
|
61
|
+
throw new Error('bad pubkey aminoPrefix');
|
|
62
|
+
if (pubKeyAmino.readUInt8(4) !== 33)
|
|
63
|
+
throw new Error('bad pubKey length');
|
|
64
|
+
const pubKey = pubKeyAmino.slice(5);
|
|
65
|
+
const signature = txDecoded.signatures[0].signature;
|
|
66
|
+
return { signBytes, signBytesHash, pubKey, signature };
|
|
67
|
+
}
|
|
68
|
+
export function validateBnbTx(txBytes, chainId) {
|
|
69
|
+
const { signBytesHash, pubKey, signature } = decodeBnbTx(txBytes, chainId);
|
|
70
|
+
return ecc.verify(Buffer.from(signBytesHash, 'hex'), pubKey, signature);
|
|
71
|
+
}
|
|
72
|
+
export function encodeBnbTx(unsignedTx, publicKey, signature) {
|
|
73
|
+
const { account_number, chain_id, sequence, source } = unsignedTx;
|
|
74
|
+
const msg = unsignedTx.msgs[0];
|
|
75
|
+
const amountToInt = (x) => Number(x);
|
|
76
|
+
const msgNormalizer = (x) => ({
|
|
77
|
+
address: bnbSdk.crypto.decodeAddress(x.address),
|
|
78
|
+
coins: x.coins.map((y) => ({
|
|
79
|
+
// In particular, these keys are backwards because we can't have nice things.
|
|
80
|
+
denom: y.denom,
|
|
81
|
+
amount: amountToInt(y.amount),
|
|
82
|
+
})),
|
|
83
|
+
});
|
|
84
|
+
const baseMsg = {
|
|
85
|
+
inputs: msg.inputs.map(msgNormalizer),
|
|
86
|
+
outputs: msg.outputs.map(msgNormalizer),
|
|
87
|
+
aminoPrefix: '2A2C87FA',
|
|
88
|
+
};
|
|
89
|
+
const tx = new bnbSdk.Transaction(Object.assign({}, unsignedTx, {
|
|
90
|
+
chainId: chain_id,
|
|
91
|
+
accountNumber: Number(account_number),
|
|
92
|
+
source: Number(source ?? 0),
|
|
93
|
+
sequence: Number(sequence),
|
|
94
|
+
// A bug in the binance SDK makes this field required, even though it shouldn't be.
|
|
95
|
+
baseMsg: { getMsg: () => baseMsg, getBaseMsg: () => baseMsg, getSignMsg: () => baseMsg },
|
|
96
|
+
}));
|
|
97
|
+
const ecPubKey = bnbSdk.crypto.getPublicKey(Buffer.from(publicKey).toString('hex'));
|
|
98
|
+
tx.addSignature(ecPubKey, signature);
|
|
99
|
+
const serializedTx = Buffer.from(tx.serialize(), 'hex');
|
|
100
|
+
if (!validateBnbTx(serializedTx, chain_id))
|
|
101
|
+
throw new Error('serialized tx did not validate');
|
|
102
|
+
return serializedTx;
|
|
103
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import * as CosmosMessages from '@keepkey/device-protocol/lib/messages-cosmos_pb';
|
|
2
|
+
import * as core from '@shapeshiftoss/hdwallet-core';
|
|
3
|
+
import type { Transport } from './transport.js';
|
|
4
|
+
export declare function cosmosGetAccountPaths(msg: core.CosmosGetAccountPaths): core.CosmosAccountPath[];
|
|
5
|
+
export declare function cosmosGetAddress(transport: Transport, msg: CosmosMessages.CosmosGetAddress.AsObject): Promise<string>;
|
|
6
|
+
export declare function cosmosSignTx(transport: Transport, msg: core.CosmosSignTx): Promise<any>;
|