@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.
Files changed (114) hide show
  1. package/LICENSE.md +6 -6
  2. package/dist/cjs/.tsbuildinfo +1 -0
  3. package/dist/{adapter.d.ts → cjs/adapter.d.ts} +6 -7
  4. package/dist/cjs/adapter.js +146 -0
  5. package/dist/{binance.d.ts → cjs/binance.d.ts} +3 -4
  6. package/dist/cjs/binance.js +144 -0
  7. package/dist/{bitcoin.d.ts → cjs/bitcoin.d.ts} +3 -4
  8. package/dist/cjs/bitcoin.js +549 -0
  9. package/dist/{bnbencoding.d.ts → cjs/bnbencoding.d.ts} +3 -2
  10. package/dist/{bnbencoding.js → cjs/bnbencoding.js} +22 -22
  11. package/dist/{cosmos.d.ts → cjs/cosmos.d.ts} +4 -5
  12. package/dist/cjs/cosmos.js +219 -0
  13. package/dist/{eos.d.ts → cjs/eos.d.ts} +3 -4
  14. package/dist/cjs/eos.js +244 -0
  15. package/dist/{ethereum.d.ts → cjs/ethereum.d.ts} +4 -5
  16. package/dist/cjs/ethereum.js +264 -0
  17. package/dist/cjs/index.d.ts +5 -0
  18. package/dist/{index.js → cjs/index.js} +0 -1
  19. package/dist/{keepkey.d.ts → cjs/keepkey.d.ts} +45 -20
  20. package/dist/cjs/keepkey.js +959 -0
  21. package/dist/{mayachain.d.ts → cjs/mayachain.d.ts} +4 -5
  22. package/dist/cjs/mayachain.js +169 -0
  23. package/dist/{osmosis.d.ts → cjs/osmosis.d.ts} +4 -5
  24. package/dist/cjs/osmosis.js +270 -0
  25. package/dist/cjs/package.json +1 -0
  26. package/dist/{ripple.d.ts → cjs/ripple.d.ts} +3 -4
  27. package/dist/cjs/ripple.js +104 -0
  28. package/dist/{thorchain.d.ts → cjs/thorchain.d.ts} +4 -5
  29. package/dist/cjs/thorchain.js +169 -0
  30. package/dist/{transport.d.ts → cjs/transport.d.ts} +2 -3
  31. package/dist/cjs/transport.js +333 -0
  32. package/dist/{typeRegistry.d.ts → cjs/typeRegistry.d.ts} +2 -3
  33. package/dist/{typeRegistry.js → cjs/typeRegistry.js} +26 -13
  34. package/dist/{utils.d.ts → cjs/utils.d.ts} +1 -2
  35. package/dist/{utils.js → cjs/utils.js} +7 -8
  36. package/dist/esm/.tsbuildinfo +1 -0
  37. package/dist/esm/adapter.d.ts +35 -0
  38. package/dist/esm/adapter.js +119 -0
  39. package/dist/esm/binance.d.ts +5 -0
  40. package/dist/esm/binance.js +112 -0
  41. package/dist/esm/bitcoin.d.ts +11 -0
  42. package/dist/esm/bitcoin.js +511 -0
  43. package/dist/esm/bnbencoding.d.ts +11 -0
  44. package/dist/esm/bnbencoding.js +103 -0
  45. package/dist/esm/cosmos.d.ts +6 -0
  46. package/dist/esm/cosmos.js +187 -0
  47. package/dist/esm/eos.d.ts +5 -0
  48. package/dist/esm/eos.js +212 -0
  49. package/dist/esm/ethereum.d.ts +17 -0
  50. package/dist/esm/ethereum.js +229 -0
  51. package/dist/esm/index.d.ts +5 -0
  52. package/dist/esm/index.js +5 -0
  53. package/dist/esm/keepkey.d.ts +177 -0
  54. package/dist/esm/keepkey.js +925 -0
  55. package/dist/esm/mayachain.d.ts +6 -0
  56. package/dist/esm/mayachain.js +137 -0
  57. package/dist/esm/osmosis.d.ts +6 -0
  58. package/dist/esm/osmosis.js +238 -0
  59. package/dist/esm/ripple.d.ts +5 -0
  60. package/dist/esm/ripple.js +72 -0
  61. package/dist/esm/thorchain.d.ts +6 -0
  62. package/dist/esm/thorchain.js +137 -0
  63. package/dist/esm/transport.d.ts +55 -0
  64. package/dist/esm/transport.js +306 -0
  65. package/dist/esm/typeRegistry.d.ts +4 -0
  66. package/dist/esm/typeRegistry.js +47 -0
  67. package/dist/esm/utils.d.ts +6 -0
  68. package/dist/esm/utils.js +56 -0
  69. package/package.json +33 -19
  70. package/dist/adapter.d.ts.map +0 -1
  71. package/dist/adapter.js +0 -175
  72. package/dist/adapter.js.map +0 -1
  73. package/dist/binance.d.ts.map +0 -1
  74. package/dist/binance.js +0 -146
  75. package/dist/binance.js.map +0 -1
  76. package/dist/bitcoin.d.ts.map +0 -1
  77. package/dist/bitcoin.js +0 -568
  78. package/dist/bitcoin.js.map +0 -1
  79. package/dist/bnbencoding.d.ts.map +0 -1
  80. package/dist/bnbencoding.js.map +0 -1
  81. package/dist/cosmos.d.ts.map +0 -1
  82. package/dist/cosmos.js +0 -230
  83. package/dist/cosmos.js.map +0 -1
  84. package/dist/eos.d.ts.map +0 -1
  85. package/dist/eos.js +0 -247
  86. package/dist/eos.js.map +0 -1
  87. package/dist/ethereum.d.ts.map +0 -1
  88. package/dist/ethereum.js +0 -281
  89. package/dist/ethereum.js.map +0 -1
  90. package/dist/index.d.ts +0 -6
  91. package/dist/index.d.ts.map +0 -1
  92. package/dist/index.js.map +0 -1
  93. package/dist/keepkey.d.ts.map +0 -1
  94. package/dist/keepkey.js +0 -1027
  95. package/dist/keepkey.js.map +0 -1
  96. package/dist/mayachain.d.ts.map +0 -1
  97. package/dist/mayachain.js +0 -180
  98. package/dist/mayachain.js.map +0 -1
  99. package/dist/osmosis.d.ts.map +0 -1
  100. package/dist/osmosis.js +0 -278
  101. package/dist/osmosis.js.map +0 -1
  102. package/dist/ripple.d.ts.map +0 -1
  103. package/dist/ripple.js +0 -111
  104. package/dist/ripple.js.map +0 -1
  105. package/dist/thorchain.d.ts.map +0 -1
  106. package/dist/thorchain.js +0 -180
  107. package/dist/thorchain.js.map +0 -1
  108. package/dist/transport.d.ts.map +0 -1
  109. package/dist/transport.js +0 -365
  110. package/dist/transport.js.map +0 -1
  111. package/dist/typeRegistry.d.ts.map +0 -1
  112. package/dist/typeRegistry.js.map +0 -1
  113. package/dist/utils.d.ts.map +0 -1
  114. 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>;