@ledgerhq/hw-app-btc 6.11.1 → 6.15.0
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/README.md +106 -48
- package/lib/Btc.d.ts.map +1 -1
- package/lib/Btc.js +5 -3
- package/lib/Btc.js.map +1 -1
- package/lib/BtcNew.d.ts.map +1 -1
- package/lib/BtcNew.js +38 -167
- package/lib/BtcNew.js.map +1 -1
- package/lib/newops/accounttype.d.ts +110 -0
- package/lib/newops/accounttype.d.ts.map +1 -0
- package/lib/newops/accounttype.js +236 -0
- package/lib/newops/accounttype.js.map +1 -0
- package/lib/newops/appClient.js +4 -4
- package/lib/newops/appClient.js.map +1 -1
- package/lib/newops/clientCommands.d.ts.map +1 -1
- package/lib/newops/clientCommands.js +14 -9
- package/lib/newops/clientCommands.js.map +1 -1
- package/lib/newops/merkle.js +2 -2
- package/lib/newops/merkle.js.map +1 -1
- package/lib/newops/psbtExtractor.js +2 -2
- package/lib/newops/psbtExtractor.js.map +1 -1
- package/lib/newops/psbtv2.js +4 -4
- package/lib/newops/psbtv2.js.map +1 -1
- package/lib-es/Btc.d.ts.map +1 -1
- package/lib-es/Btc.js +5 -3
- package/lib-es/Btc.js.map +1 -1
- package/lib-es/BtcNew.d.ts.map +1 -1
- package/lib-es/BtcNew.js +41 -170
- package/lib-es/BtcNew.js.map +1 -1
- package/lib-es/newops/accounttype.d.ts +110 -0
- package/lib-es/newops/accounttype.d.ts.map +1 -0
- package/lib-es/newops/accounttype.js +233 -0
- package/lib-es/newops/accounttype.js.map +1 -0
- package/lib-es/newops/appClient.js +4 -4
- package/lib-es/newops/appClient.js.map +1 -1
- package/lib-es/newops/clientCommands.d.ts.map +1 -1
- package/lib-es/newops/clientCommands.js +14 -9
- package/lib-es/newops/clientCommands.js.map +1 -1
- package/lib-es/newops/merkle.js +2 -2
- package/lib-es/newops/merkle.js.map +1 -1
- package/lib-es/newops/psbtExtractor.js +2 -2
- package/lib-es/newops/psbtExtractor.js.map +1 -1
- package/lib-es/newops/psbtv2.js +4 -4
- package/lib-es/newops/psbtv2.js.map +1 -1
- package/package.json +3 -3
- package/src/Btc.ts +34 -3
- package/src/BtcNew.ts +64 -166
- package/src/newops/accounttype.ts +373 -0
- package/src/newops/appClient.ts +4 -4
- package/src/newops/clientCommands.ts +15 -9
- package/src/newops/merkle.ts +2 -2
- package/src/newops/psbtExtractor.ts +2 -2
- package/src/newops/psbtv2.ts +4 -4
- package/tests/Btc.test.ts +68 -39
- package/tests/newops/BtcNew.test.ts +23 -10
- package/tests/newops/integrationtools.ts +71 -41
- package/tests/newops/merkle.test.ts +1 -1
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
import { crypto } from "bitcoinjs-lib";
|
|
2
|
+
import { pointAddScalar } from "tiny-secp256k1";
|
|
3
|
+
import { BufferWriter } from "../buffertools";
|
|
4
|
+
import {
|
|
5
|
+
HASH_SIZE,
|
|
6
|
+
OP_CHECKSIG,
|
|
7
|
+
OP_DUP,
|
|
8
|
+
OP_EQUAL,
|
|
9
|
+
OP_EQUALVERIFY,
|
|
10
|
+
OP_HASH160,
|
|
11
|
+
} from "../constants";
|
|
12
|
+
import { hashPublicKey } from "../hashPublicKey";
|
|
13
|
+
import { DefaultDescriptorTemplate } from "./policy";
|
|
14
|
+
import { PsbtV2 } from "./psbtv2";
|
|
15
|
+
|
|
16
|
+
export type SpendingCondition = {
|
|
17
|
+
scriptPubKey: Buffer;
|
|
18
|
+
redeemScript?: Buffer;
|
|
19
|
+
// Possible future extension:
|
|
20
|
+
// witnessScript?: Buffer; // For p2wsh witnessScript
|
|
21
|
+
// tapScript?: {tapPath: Buffer[], script: Buffer} // For taproot
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export type SpentOutput = { cond: SpendingCondition; amount: Buffer };
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Encapsulates differences between account types, for example p2wpkh,
|
|
28
|
+
* p2wpkhWrapped, p2tr.
|
|
29
|
+
*/
|
|
30
|
+
export interface AccountType {
|
|
31
|
+
/**
|
|
32
|
+
* Generates a scriptPubKey (output script) from a list of public keys. If a
|
|
33
|
+
* p2sh redeemScript or a p2wsh witnessScript is needed it will also be set on
|
|
34
|
+
* the returned SpendingCondition.
|
|
35
|
+
*
|
|
36
|
+
* The pubkeys are expected to be 33 byte ecdsa compressed pubkeys.
|
|
37
|
+
*/
|
|
38
|
+
spendingCondition(pubkeys: Buffer[]): SpendingCondition;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Populates the psbt with account type-specific data for an input.
|
|
42
|
+
* @param i The index of the input map to populate
|
|
43
|
+
* @param inputTx The full transaction containing the spent output. This may
|
|
44
|
+
* be omitted for taproot.
|
|
45
|
+
* @param spentOutput The amount and spending condition of the spent output
|
|
46
|
+
* @param pubkeys The 33 byte ecdsa compressed public keys involved in the input
|
|
47
|
+
* @param pathElems The paths corresponding to the pubkeys, in same order.
|
|
48
|
+
*/
|
|
49
|
+
setInput(
|
|
50
|
+
i: number,
|
|
51
|
+
inputTx: Buffer | undefined,
|
|
52
|
+
spentOutput: SpentOutput,
|
|
53
|
+
pubkeys: Buffer[],
|
|
54
|
+
pathElems: number[][]
|
|
55
|
+
): void;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Populates the psbt with account type-specific data for an output. This is typically
|
|
59
|
+
* done for change outputs and other outputs that goes to the same account as
|
|
60
|
+
* being spent from.
|
|
61
|
+
* @param i The index of the output map to populate
|
|
62
|
+
* @param cond The spending condition for this output
|
|
63
|
+
* @param pubkeys The 33 byte ecdsa compressed public keys involved in this output
|
|
64
|
+
* @param paths The paths corresponding to the pubkeys, in same order.
|
|
65
|
+
*/
|
|
66
|
+
setOwnOutput(
|
|
67
|
+
i: number,
|
|
68
|
+
cond: SpendingCondition,
|
|
69
|
+
pubkeys: Buffer[],
|
|
70
|
+
paths: number[][]
|
|
71
|
+
): void;
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Returns the descriptor template for this account type. Currently only
|
|
75
|
+
* DefaultDescriptorTemplates are allowed, but that might be changed in the
|
|
76
|
+
* future. See class WalletPolicy for more information on descriptor
|
|
77
|
+
* templates.
|
|
78
|
+
*/
|
|
79
|
+
getDescriptorTemplate(): DefaultDescriptorTemplate;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
|
83
|
+
interface BaseAccount extends AccountType {}
|
|
84
|
+
|
|
85
|
+
abstract class BaseAccount implements AccountType {
|
|
86
|
+
constructor(protected psbt: PsbtV2, protected masterFp: Buffer) {}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Superclass for single signature accounts. This will make sure that the pubkey
|
|
91
|
+
* arrays and path arrays in the method arguments contains exactly one element
|
|
92
|
+
* and calls an abstract method to do the actual work.
|
|
93
|
+
*/
|
|
94
|
+
abstract class SingleKeyAccount extends BaseAccount {
|
|
95
|
+
spendingCondition(pubkeys: Buffer[]): SpendingCondition {
|
|
96
|
+
if (pubkeys.length != 1) {
|
|
97
|
+
throw new Error("Expected single key, got " + pubkeys.length);
|
|
98
|
+
}
|
|
99
|
+
return this.singleKeyCondition(pubkeys[0]);
|
|
100
|
+
}
|
|
101
|
+
protected abstract singleKeyCondition(pubkey: Buffer): SpendingCondition;
|
|
102
|
+
|
|
103
|
+
setInput(
|
|
104
|
+
i: number,
|
|
105
|
+
inputTx: Buffer | undefined,
|
|
106
|
+
spentOutput: SpentOutput,
|
|
107
|
+
pubkeys: Buffer[],
|
|
108
|
+
pathElems: number[][]
|
|
109
|
+
) {
|
|
110
|
+
if (pubkeys.length != 1) {
|
|
111
|
+
throw new Error("Expected single key, got " + pubkeys.length);
|
|
112
|
+
}
|
|
113
|
+
if (pathElems.length != 1) {
|
|
114
|
+
throw new Error("Expected single path, got " + pathElems.length);
|
|
115
|
+
}
|
|
116
|
+
this.setSingleKeyInput(i, inputTx, spentOutput, pubkeys[0], pathElems[0]);
|
|
117
|
+
}
|
|
118
|
+
protected abstract setSingleKeyInput(
|
|
119
|
+
i: number,
|
|
120
|
+
inputTx: Buffer | undefined,
|
|
121
|
+
spentOutput: SpentOutput,
|
|
122
|
+
pubkey: Buffer,
|
|
123
|
+
path: number[]
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
setOwnOutput(
|
|
127
|
+
i: number,
|
|
128
|
+
cond: SpendingCondition,
|
|
129
|
+
pubkeys: Buffer[],
|
|
130
|
+
paths: number[][]
|
|
131
|
+
) {
|
|
132
|
+
if (pubkeys.length != 1) {
|
|
133
|
+
throw new Error("Expected single key, got " + pubkeys.length);
|
|
134
|
+
}
|
|
135
|
+
if (paths.length != 1) {
|
|
136
|
+
throw new Error("Expected single path, got " + paths.length);
|
|
137
|
+
}
|
|
138
|
+
this.setSingleKeyOutput(i, cond, pubkeys[0], paths[0]);
|
|
139
|
+
}
|
|
140
|
+
protected abstract setSingleKeyOutput(
|
|
141
|
+
i: number,
|
|
142
|
+
cond: SpendingCondition,
|
|
143
|
+
pubkey: Buffer,
|
|
144
|
+
path: number[]
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export class p2pkh extends SingleKeyAccount {
|
|
149
|
+
singleKeyCondition(pubkey: Buffer): SpendingCondition {
|
|
150
|
+
const buf = new BufferWriter();
|
|
151
|
+
const pubkeyHash = hashPublicKey(pubkey);
|
|
152
|
+
buf.writeSlice(Buffer.from([OP_DUP, OP_HASH160, HASH_SIZE]));
|
|
153
|
+
buf.writeSlice(pubkeyHash);
|
|
154
|
+
buf.writeSlice(Buffer.from([OP_EQUALVERIFY, OP_CHECKSIG]));
|
|
155
|
+
return { scriptPubKey: buf.buffer() };
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
setSingleKeyInput(
|
|
159
|
+
i: number,
|
|
160
|
+
inputTx: Buffer | undefined,
|
|
161
|
+
_spentOutput: SpentOutput,
|
|
162
|
+
pubkey: Buffer,
|
|
163
|
+
path: number[]
|
|
164
|
+
) {
|
|
165
|
+
if (!inputTx) {
|
|
166
|
+
throw new Error("Full input base transaction required");
|
|
167
|
+
}
|
|
168
|
+
this.psbt.setInputNonWitnessUtxo(i, inputTx);
|
|
169
|
+
this.psbt.setInputBip32Derivation(i, pubkey, this.masterFp, path);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
setSingleKeyOutput(
|
|
173
|
+
i: number,
|
|
174
|
+
cond: SpendingCondition,
|
|
175
|
+
pubkey: Buffer,
|
|
176
|
+
path: number[]
|
|
177
|
+
) {
|
|
178
|
+
this.psbt.setOutputBip32Derivation(i, pubkey, this.masterFp, path);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
getDescriptorTemplate(): DefaultDescriptorTemplate {
|
|
182
|
+
return "pkh(@0)";
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export class p2tr extends SingleKeyAccount {
|
|
187
|
+
singleKeyCondition(pubkey: Buffer): SpendingCondition {
|
|
188
|
+
const xonlyPubkey = pubkey.slice(1); // x-only pubkey
|
|
189
|
+
const buf = new BufferWriter();
|
|
190
|
+
const outputKey = this.getTaprootOutputKey(xonlyPubkey);
|
|
191
|
+
buf.writeSlice(Buffer.from([0x51, 32])); // push1, pubkeylen
|
|
192
|
+
buf.writeSlice(outputKey);
|
|
193
|
+
return { scriptPubKey: buf.buffer() };
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
setSingleKeyInput(
|
|
197
|
+
i: number,
|
|
198
|
+
_inputTx: Buffer | undefined,
|
|
199
|
+
spentOutput: SpentOutput,
|
|
200
|
+
pubkey: Buffer,
|
|
201
|
+
path: number[]
|
|
202
|
+
) {
|
|
203
|
+
const xonly = pubkey.slice(1);
|
|
204
|
+
this.psbt.setInputTapBip32Derivation(i, xonly, [], this.masterFp, path);
|
|
205
|
+
this.psbt.setInputWitnessUtxo(
|
|
206
|
+
i,
|
|
207
|
+
spentOutput.amount,
|
|
208
|
+
spentOutput.cond.scriptPubKey
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
setSingleKeyOutput(
|
|
213
|
+
i: number,
|
|
214
|
+
cond: SpendingCondition,
|
|
215
|
+
pubkey: Buffer,
|
|
216
|
+
path: number[]
|
|
217
|
+
) {
|
|
218
|
+
const xonly = pubkey.slice(1);
|
|
219
|
+
this.psbt.setOutputTapBip32Derivation(i, xonly, [], this.masterFp, path);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
getDescriptorTemplate(): DefaultDescriptorTemplate {
|
|
223
|
+
return "tr(@0)";
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/*
|
|
227
|
+
The following two functions are copied from wallet-btc and adapted.
|
|
228
|
+
They should be moved to a library to avoid code reuse.
|
|
229
|
+
*/
|
|
230
|
+
private hashTapTweak(x: Buffer): Buffer {
|
|
231
|
+
// hash_tag(x) = SHA256(SHA256(tag) || SHA256(tag) || x), see BIP340
|
|
232
|
+
// See https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki#specification
|
|
233
|
+
const h = crypto.sha256(Buffer.from("TapTweak", "utf-8"));
|
|
234
|
+
return crypto.sha256(Buffer.concat([h, h, x]));
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Calculates a taproot output key from an internal key. This output key will be
|
|
239
|
+
* used as witness program in a taproot output. The internal key is tweaked
|
|
240
|
+
* according to recommendation in BIP341:
|
|
241
|
+
* https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki#cite_ref-22-0
|
|
242
|
+
*
|
|
243
|
+
* @param internalPubkey A 32 byte x-only taproot internal key
|
|
244
|
+
* @returns The output key
|
|
245
|
+
*/
|
|
246
|
+
getTaprootOutputKey(internalPubkey: Buffer): Buffer {
|
|
247
|
+
if (internalPubkey.length != 32) {
|
|
248
|
+
throw new Error("Expected 32 byte pubkey. Got " + internalPubkey.length);
|
|
249
|
+
}
|
|
250
|
+
// A BIP32 derived key can be converted to a schnorr pubkey by dropping
|
|
251
|
+
// the first byte, which represent the oddness/evenness. In schnorr all
|
|
252
|
+
// pubkeys are even.
|
|
253
|
+
// https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki#public-key-conversion
|
|
254
|
+
const evenEcdsaPubkey = Buffer.concat([
|
|
255
|
+
Buffer.from([0x02]),
|
|
256
|
+
internalPubkey,
|
|
257
|
+
]);
|
|
258
|
+
const tweak = this.hashTapTweak(internalPubkey);
|
|
259
|
+
|
|
260
|
+
// Q = P + int(hash_TapTweak(bytes(P)))G
|
|
261
|
+
const outputEcdsaKey = Buffer.from(pointAddScalar(evenEcdsaPubkey, tweak));
|
|
262
|
+
// Convert to schnorr.
|
|
263
|
+
const outputSchnorrKey = outputEcdsaKey.slice(1);
|
|
264
|
+
// Create address
|
|
265
|
+
return outputSchnorrKey;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
export class p2wpkhWrapped extends SingleKeyAccount {
|
|
270
|
+
singleKeyCondition(pubkey: Buffer): SpendingCondition {
|
|
271
|
+
const buf = new BufferWriter();
|
|
272
|
+
const redeemScript = this.createRedeemScript(pubkey);
|
|
273
|
+
const scriptHash = hashPublicKey(redeemScript);
|
|
274
|
+
buf.writeSlice(Buffer.from([OP_HASH160, HASH_SIZE]));
|
|
275
|
+
buf.writeSlice(scriptHash);
|
|
276
|
+
buf.writeUInt8(OP_EQUAL);
|
|
277
|
+
return { scriptPubKey: buf.buffer(), redeemScript: redeemScript };
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
setSingleKeyInput(
|
|
281
|
+
i: number,
|
|
282
|
+
inputTx: Buffer | undefined,
|
|
283
|
+
spentOutput: SpentOutput,
|
|
284
|
+
pubkey: Buffer,
|
|
285
|
+
path: number[]
|
|
286
|
+
) {
|
|
287
|
+
if (!inputTx) {
|
|
288
|
+
throw new Error("Full input base transaction required");
|
|
289
|
+
}
|
|
290
|
+
this.psbt.setInputNonWitnessUtxo(i, inputTx);
|
|
291
|
+
this.psbt.setInputBip32Derivation(i, pubkey, this.masterFp, path);
|
|
292
|
+
|
|
293
|
+
const userSuppliedRedeemScript = spentOutput.cond.redeemScript;
|
|
294
|
+
const expectedRedeemScript = this.createRedeemScript(pubkey);
|
|
295
|
+
if (
|
|
296
|
+
userSuppliedRedeemScript &&
|
|
297
|
+
!expectedRedeemScript.equals(userSuppliedRedeemScript)
|
|
298
|
+
) {
|
|
299
|
+
// At what point might a user set the redeemScript on its own?
|
|
300
|
+
throw new Error(`User-supplied redeemScript ${userSuppliedRedeemScript.toString(
|
|
301
|
+
"hex"
|
|
302
|
+
)} doesn't
|
|
303
|
+
match expected ${expectedRedeemScript.toString("hex")} for input ${i}`);
|
|
304
|
+
}
|
|
305
|
+
this.psbt.setInputRedeemScript(i, expectedRedeemScript);
|
|
306
|
+
this.psbt.setInputWitnessUtxo(
|
|
307
|
+
i,
|
|
308
|
+
spentOutput.amount,
|
|
309
|
+
spentOutput.cond.scriptPubKey
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
setSingleKeyOutput(
|
|
314
|
+
i: number,
|
|
315
|
+
cond: SpendingCondition,
|
|
316
|
+
pubkey: Buffer,
|
|
317
|
+
path: number[]
|
|
318
|
+
) {
|
|
319
|
+
this.psbt.setOutputRedeemScript(i, cond.redeemScript!);
|
|
320
|
+
this.psbt.setOutputBip32Derivation(i, pubkey, this.masterFp, path);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
getDescriptorTemplate(): DefaultDescriptorTemplate {
|
|
324
|
+
return "sh(wpkh(@0))";
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
private createRedeemScript(pubkey: Buffer): Buffer {
|
|
328
|
+
const pubkeyHash = hashPublicKey(pubkey);
|
|
329
|
+
return Buffer.concat([Buffer.from("0014", "hex"), pubkeyHash]);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
export class p2wpkh extends SingleKeyAccount {
|
|
334
|
+
singleKeyCondition(pubkey: Buffer): SpendingCondition {
|
|
335
|
+
const buf = new BufferWriter();
|
|
336
|
+
const pubkeyHash = hashPublicKey(pubkey);
|
|
337
|
+
buf.writeSlice(Buffer.from([0, HASH_SIZE]));
|
|
338
|
+
buf.writeSlice(pubkeyHash);
|
|
339
|
+
return { scriptPubKey: buf.buffer() };
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
setSingleKeyInput(
|
|
343
|
+
i: number,
|
|
344
|
+
inputTx: Buffer | undefined,
|
|
345
|
+
spentOutput: SpentOutput,
|
|
346
|
+
pubkey: Buffer,
|
|
347
|
+
path: number[]
|
|
348
|
+
) {
|
|
349
|
+
if (!inputTx) {
|
|
350
|
+
throw new Error("Full input base transaction required");
|
|
351
|
+
}
|
|
352
|
+
this.psbt.setInputNonWitnessUtxo(i, inputTx);
|
|
353
|
+
this.psbt.setInputBip32Derivation(i, pubkey, this.masterFp, path);
|
|
354
|
+
this.psbt.setInputWitnessUtxo(
|
|
355
|
+
i,
|
|
356
|
+
spentOutput.amount,
|
|
357
|
+
spentOutput.cond.scriptPubKey
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
setSingleKeyOutput(
|
|
362
|
+
i: number,
|
|
363
|
+
cond: SpendingCondition,
|
|
364
|
+
pubkey: Buffer,
|
|
365
|
+
path: number[]
|
|
366
|
+
) {
|
|
367
|
+
this.psbt.setOutputBip32Derivation(i, pubkey, this.masterFp, path);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
getDescriptorTemplate(): DefaultDescriptorTemplate {
|
|
371
|
+
return "wpkh(@0)";
|
|
372
|
+
}
|
|
373
|
+
}
|
package/src/newops/appClient.ts
CHANGED
|
@@ -73,7 +73,7 @@ export class AppClient {
|
|
|
73
73
|
const response = await this.makeRequest(
|
|
74
74
|
BitcoinIns.GET_PUBKEY,
|
|
75
75
|
Buffer.concat([
|
|
76
|
-
Buffer.
|
|
76
|
+
Buffer.from(display ? [1] : [0]),
|
|
77
77
|
pathElementsToBuffer(pathElements),
|
|
78
78
|
])
|
|
79
79
|
);
|
|
@@ -108,10 +108,10 @@ export class AppClient {
|
|
|
108
108
|
const response = await this.makeRequest(
|
|
109
109
|
BitcoinIns.GET_WALLET_ADDRESS,
|
|
110
110
|
Buffer.concat([
|
|
111
|
-
Buffer.
|
|
111
|
+
Buffer.from(display ? [1] : [0]),
|
|
112
112
|
walletPolicy.getWalletId(),
|
|
113
113
|
walletHMAC || Buffer.alloc(32, 0),
|
|
114
|
-
Buffer.
|
|
114
|
+
Buffer.from([change]),
|
|
115
115
|
addressIndexBuffer,
|
|
116
116
|
]),
|
|
117
117
|
clientInterpreter
|
|
@@ -181,6 +181,6 @@ export class AppClient {
|
|
|
181
181
|
}
|
|
182
182
|
|
|
183
183
|
async getMasterFingerprint(): Promise<Buffer> {
|
|
184
|
-
return this.makeRequest(BitcoinIns.GET_MASTER_FINGERPRINT, Buffer.
|
|
184
|
+
return this.makeRequest(BitcoinIns.GET_MASTER_FINGERPRINT, Buffer.from([]));
|
|
185
185
|
}
|
|
186
186
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { crypto } from "bitcoinjs-lib";
|
|
2
|
+
import { BufferReader } from "../buffertools";
|
|
2
3
|
import { createVarint } from "../varint";
|
|
3
4
|
import { hashLeaf, Merkle } from "./merkle";
|
|
4
5
|
import { MerkleMap } from "./merkleMap";
|
|
@@ -106,19 +107,24 @@ export class GetMerkleLeafProofCommand extends ClientCommand {
|
|
|
106
107
|
execute(request: Buffer): Buffer {
|
|
107
108
|
const req = request.subarray(1);
|
|
108
109
|
|
|
109
|
-
if (req.length
|
|
110
|
-
throw new Error("Invalid request,
|
|
110
|
+
if (req.length < 32 + 1 + 1) {
|
|
111
|
+
throw new Error("Invalid request, expected at least 34 bytes");
|
|
111
112
|
}
|
|
112
113
|
|
|
113
|
-
|
|
114
|
-
const hash =
|
|
115
|
-
for (let i = 0; i < 32; i++) {
|
|
116
|
-
hash[i] = req.readUInt8(i);
|
|
117
|
-
}
|
|
114
|
+
const reqBuf = new BufferReader(req);
|
|
115
|
+
const hash = reqBuf.readSlice(32);
|
|
118
116
|
const hash_hex = hash.toString("hex");
|
|
119
117
|
|
|
120
|
-
|
|
121
|
-
|
|
118
|
+
let tree_size;
|
|
119
|
+
let leaf_index;
|
|
120
|
+
try {
|
|
121
|
+
tree_size = reqBuf.readVarInt();
|
|
122
|
+
leaf_index = reqBuf.readVarInt();
|
|
123
|
+
} catch (e: any) {
|
|
124
|
+
throw new Error(
|
|
125
|
+
"Invalid request, couldn't parse tree_size or leaf_index"
|
|
126
|
+
);
|
|
127
|
+
}
|
|
122
128
|
|
|
123
129
|
const mt = this.known_trees.get(hash_hex);
|
|
124
130
|
if (!mt) {
|
package/src/newops/merkle.ts
CHANGED
|
@@ -62,7 +62,7 @@ export class Merkle {
|
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
hashNode(left: Buffer, right: Buffer): Buffer {
|
|
65
|
-
return this.h(Buffer.concat([Buffer.
|
|
65
|
+
return this.h(Buffer.concat([Buffer.from([1]), left, right]));
|
|
66
66
|
}
|
|
67
67
|
}
|
|
68
68
|
|
|
@@ -70,7 +70,7 @@ export function hashLeaf(
|
|
|
70
70
|
buf: Buffer,
|
|
71
71
|
hashFunction: (buf: Buffer) => Buffer = crypto.sha256
|
|
72
72
|
): Buffer {
|
|
73
|
-
return hashConcat(Buffer.
|
|
73
|
+
return hashConcat(Buffer.from([0]), buf, hashFunction);
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
function hashConcat(
|
|
@@ -13,7 +13,7 @@ export function extract(psbt: PsbtV2): Buffer {
|
|
|
13
13
|
|
|
14
14
|
const isSegwit = !!psbt.getInputWitnessUtxo(0);
|
|
15
15
|
if (isSegwit) {
|
|
16
|
-
tx.writeSlice(Buffer.
|
|
16
|
+
tx.writeSlice(Buffer.from([0, 1]));
|
|
17
17
|
}
|
|
18
18
|
const inputCount = psbt.getGlobalInputCount();
|
|
19
19
|
tx.writeVarInt(inputCount);
|
|
@@ -21,7 +21,7 @@ export function extract(psbt: PsbtV2): Buffer {
|
|
|
21
21
|
for (let i = 0; i < inputCount; i++) {
|
|
22
22
|
tx.writeSlice(psbt.getInputPreviousTxid(i));
|
|
23
23
|
tx.writeUInt32(psbt.getInputOutputIndex(i));
|
|
24
|
-
tx.writeVarSlice(psbt.getInputFinalScriptsig(i) ?? Buffer.
|
|
24
|
+
tx.writeVarSlice(psbt.getInputFinalScriptsig(i) ?? Buffer.from([]));
|
|
25
25
|
tx.writeUInt32(psbt.getInputSequence(i));
|
|
26
26
|
if (isSegwit) {
|
|
27
27
|
witnessWriter.writeSlice(psbt.getInputFinalScriptwitness(i));
|
package/src/newops/psbtv2.ts
CHANGED
|
@@ -33,7 +33,7 @@ export enum psbtOut {
|
|
|
33
33
|
TAP_BIP32_DERIVATION = 0x07,
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
const PSBT_MAGIC_BYTES = Buffer.
|
|
36
|
+
const PSBT_MAGIC_BYTES = Buffer.from([0x70, 0x73, 0x62, 0x74, 0xff]);
|
|
37
37
|
|
|
38
38
|
export class NoSuchEntry extends Error {}
|
|
39
39
|
|
|
@@ -321,7 +321,7 @@ export class PsbtV2 {
|
|
|
321
321
|
}
|
|
322
322
|
serialize(): Buffer {
|
|
323
323
|
const buf = new BufferWriter();
|
|
324
|
-
buf.writeSlice(Buffer.
|
|
324
|
+
buf.writeSlice(Buffer.from([0x70, 0x73, 0x62, 0x74, 0xff]));
|
|
325
325
|
serializeMap(buf, this.globalMap);
|
|
326
326
|
this.inputMaps.forEach((map) => {
|
|
327
327
|
serializeMap(buf, map);
|
|
@@ -371,7 +371,7 @@ export class PsbtV2 {
|
|
|
371
371
|
return keyTypes.some((k) => k == keyType);
|
|
372
372
|
}
|
|
373
373
|
private setGlobal(keyType: KeyType, value: Buffer) {
|
|
374
|
-
const key = new Key(keyType, Buffer.
|
|
374
|
+
const key = new Key(keyType, Buffer.from([]));
|
|
375
375
|
this.globalMap.set(key.toString(), value);
|
|
376
376
|
}
|
|
377
377
|
private getGlobal(keyType: KeyType): Buffer {
|
|
@@ -541,7 +541,7 @@ function serializeMap(buf: BufferWriter, map: Map<string, Buffer>) {
|
|
|
541
541
|
}
|
|
542
542
|
|
|
543
543
|
function b(): Buffer {
|
|
544
|
-
return Buffer.
|
|
544
|
+
return Buffer.from([]);
|
|
545
545
|
}
|
|
546
546
|
function set(
|
|
547
547
|
map: Map<string, Buffer>,
|
package/tests/Btc.test.ts
CHANGED
|
@@ -106,14 +106,24 @@ ascii(1NjiCsVBuKDT62LmaUd7WZZZBK2gPAkisb)
|
|
|
106
106
|
8bd937d416de7020952cc8e2c99ce9ac7e01265e31ceb8e47bf9c37f46f8abbd
|
|
107
107
|
*/
|
|
108
108
|
/*eslint-disable */
|
|
109
|
-
const pubkeyParent =
|
|
110
|
-
|
|
111
|
-
const
|
|
109
|
+
const pubkeyParent =
|
|
110
|
+
"045d4a72237572a91e13818fa38cedabe6174569cc9a319012f75150d5c0a0639d54eafd13a68d079b7a67764800c6a981825ef52384f08c3925109188ab21bc09";
|
|
111
|
+
const addrParent = Buffer.from(
|
|
112
|
+
"1NjiCsVBuKDT62LmaUd7WZZZBK2gPAkisb",
|
|
113
|
+
"ascii"
|
|
114
|
+
).toString("hex");
|
|
115
|
+
const ccParent =
|
|
116
|
+
"8bd937d416de7020952cc8e2c99ce9ac7e01265e31ceb8e47bf9c37f46f8abbd";
|
|
112
117
|
const responseParent = `41${pubkeyParent}22${addrParent}${ccParent}`;
|
|
113
118
|
|
|
114
|
-
const pubkeyAcc =
|
|
115
|
-
|
|
116
|
-
const
|
|
119
|
+
const pubkeyAcc =
|
|
120
|
+
"04250dfdfb84c1efd160ed0e10ebac845d0e4b04277174630ba56de96bbd3afb21fc6c04ce0d5a0cbd784fdabc99d16269c27cf3842fe8440f1f21b8af900f0eaa";
|
|
121
|
+
const addrAcc = Buffer.from(
|
|
122
|
+
"16Y97ByhyboePhTYMMmFj1tq5Cy1bDq8jT",
|
|
123
|
+
"ascii"
|
|
124
|
+
).toString("hex");
|
|
125
|
+
const ccAcc =
|
|
126
|
+
"c071c6f2d05cbc9ea9a04951b238086ce1608cf00020c3cab85b36aac5fdd591";
|
|
117
127
|
/*eslint-enable */
|
|
118
128
|
const responseAcc = `41${pubkeyAcc}22${addrAcc}${ccAcc}`;
|
|
119
129
|
const transport = await openTransportReplayer(
|
|
@@ -123,7 +133,7 @@ ascii(1NjiCsVBuKDT62LmaUd7WZZZBK2gPAkisb)
|
|
|
123
133
|
=> e040000009028000002c80000000
|
|
124
134
|
<= ${responseParent}9000
|
|
125
135
|
=> e04000000d038000002c8000000080000011
|
|
126
|
-
<= ${responseAcc}9000
|
|
136
|
+
<= ${responseAcc}9000
|
|
127
137
|
`)
|
|
128
138
|
);
|
|
129
139
|
const btc = new Btc(transport);
|
|
@@ -430,7 +440,7 @@ test("signMessage", async () => {
|
|
|
430
440
|
function testBackend(s: string): any {
|
|
431
441
|
return async () => {
|
|
432
442
|
return { publicKey: s, bitcoinAddress: "", chainCode: "" };
|
|
433
|
-
}
|
|
443
|
+
};
|
|
434
444
|
}
|
|
435
445
|
|
|
436
446
|
class TestBtc extends Btc {
|
|
@@ -461,37 +471,51 @@ class TestBtc extends Btc {
|
|
|
461
471
|
// });
|
|
462
472
|
|
|
463
473
|
test.each`
|
|
464
|
-
app | ver
|
|
465
|
-
${"Bitcoin"} | ${"1.99.99"}
|
|
466
|
-
${"Bitcoin"} | ${"1.99.99"}
|
|
467
|
-
${"Bitcoin"} | ${"2.0.0-alpha1"} | ${"m/44'/0'/1'"}
|
|
468
|
-
${"Bitcoin"} | ${"2.0.0-alpha1"} | ${"m/44'/0'"}
|
|
469
|
-
${"Bitcoin"} | ${"2.0.0-
|
|
470
|
-
${"Bitcoin"} | ${"2.0.0-alpha1"} | ${"m/44'/0'"}
|
|
471
|
-
${"Bitcoin"} | ${"2.0.0-alpha1"} | ${"m/44'/0'"}
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
474
|
+
app | ver | path | format | display | exp
|
|
475
|
+
${"Bitcoin"} | ${"1.99.99"} | ${"m/44'/0'/1'"} | ${"bech32m"} | ${false} | ${""}
|
|
476
|
+
${"Bitcoin"} | ${"1.99.99"} | ${"m/44'/0'"} | ${"bech32m"} | ${false} | ${""}
|
|
477
|
+
${"Bitcoin"} | ${"2.0.0-alpha1"} | ${"m/44'/0'/1'"} | ${"bech32m"} | ${false} | ${"new"}
|
|
478
|
+
${"Bitcoin"} | ${"2.0.0-alpha1"} | ${"m/44'/0'"} | ${"bech32m"} | ${false} | ${"new"}
|
|
479
|
+
${"Bitcoin"} | ${"2.0.0-beta"} | ${"m/84'/1'/0'"} | ${"bech32"} | ${false} | ${"new"}
|
|
480
|
+
${"Bitcoin"} | ${"2.0.0-alpha1"} | ${"m/44'/0'/1'"} | ${"bech32"} | ${false} | ${"new"}
|
|
481
|
+
${"Bitcoin"} | ${"2.0.0-alpha1"} | ${"m/44'/0'"} | ${"bech32"} | ${undefined} | ${"old"}
|
|
482
|
+
${"Bitcoin"} | ${"2.0.0-alpha1"} | ${"m/44'/0'"} | ${"bech32"} | ${true} | ${"new"}
|
|
483
|
+
${"Bitcoin"} | ${"2.0.0-alpha1"} | ${"m/44'/0'/1'/0/0"} | ${"bech32"} | ${false} | ${"new"}
|
|
484
|
+
${"Bitcoin"} | ${"2.0.0-alpha1"} | ${"m/44'/0'/1'/1/0"} | ${"bech32"} | ${false} | ${"new"}
|
|
485
|
+
${"Bitcoin"} | ${"2.0.0-alpha1"} | ${"m/44'/0'/1'/1/0"} | ${"legacy"} | ${false} | ${"new"}
|
|
486
|
+
${"Bitcoin"} | ${"2.0.0-alpha1"} | ${"m/44'/0'/1'/1/0"} | ${"p2sh"} | ${false} | ${"new"}
|
|
487
|
+
${"Bitcoin"} | ${"2.0.0-alpha1"} | ${"m/44'/0'/1'/2/0"} | ${"bech32"} | ${false} | ${"old"}
|
|
488
|
+
`(
|
|
489
|
+
"dispatch $app $ver $path $format $display to $exp",
|
|
490
|
+
async ({ app, ver, path, format, display, exp }) => {
|
|
491
|
+
const appName = Buffer.from([app.length])
|
|
492
|
+
.toString("hex")
|
|
493
|
+
.concat(Buffer.from(app, "ascii").toString("hex"));
|
|
494
|
+
const appVersion = Buffer.from([ver.length])
|
|
495
|
+
.toString("hex")
|
|
496
|
+
.concat(Buffer.from(ver, "ascii").toString("hex"));
|
|
497
|
+
const resp = `01${appName}${appVersion}01029000`;
|
|
498
|
+
const tr = await openTransportReplayer(
|
|
499
|
+
RecordStore.fromString(`=> b001000000\n <= ${resp}`)
|
|
500
|
+
);
|
|
501
|
+
const btc = new TestBtc(tr);
|
|
502
|
+
try {
|
|
503
|
+
const key = await btc.getWalletPublicKey(path, {
|
|
504
|
+
format: format,
|
|
505
|
+
verify: display,
|
|
506
|
+
});
|
|
507
|
+
if (exp === "") {
|
|
508
|
+
expect(1).toEqual(0); // Allways fail. Don't know how to do that properly
|
|
509
|
+
}
|
|
510
|
+
expect(key.publicKey).toEqual(exp);
|
|
511
|
+
} catch (e: any) {
|
|
512
|
+
if (exp != "") {
|
|
513
|
+
throw e;
|
|
514
|
+
}
|
|
515
|
+
expect(exp).toEqual("");
|
|
491
516
|
}
|
|
492
|
-
expect(exp).toEqual("");
|
|
493
517
|
}
|
|
494
|
-
|
|
518
|
+
);
|
|
495
519
|
|
|
496
520
|
// test("getWalletPublicKey compatibility for internal hardened keys", async () => {
|
|
497
521
|
// await testDispatch("Bitcoin", "1.99.99", "m/44'/0'/1'", "bech32m", "");
|
|
@@ -502,5 +526,10 @@ ${"Bitcoin"} | ${"2.0.0-alpha1"} | ${"m/44'/0'"} | ${"bech32"} | ${true} | ${"ne
|
|
|
502
526
|
// await testDispatch("Bitcoin", "2.0.0-alpha1", "m/44'/0'", "bech32", "old");
|
|
503
527
|
// });
|
|
504
528
|
|
|
505
|
-
async function testDispatch(
|
|
506
|
-
|
|
529
|
+
async function testDispatch(
|
|
530
|
+
name: string,
|
|
531
|
+
version: string,
|
|
532
|
+
path: string,
|
|
533
|
+
addressFormat: AddressFormat | undefined,
|
|
534
|
+
exp: string
|
|
535
|
+
): Promise<void> {}
|