@ledgerhq/hw-app-btc 6.11.0 → 6.12.1
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 +110 -47
- 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 +1 -1
- package/lib/BtcNew.d.ts.map +1 -1
- package/lib/BtcNew.js +71 -174
- 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.d.ts +1 -1
- package/lib/newops/appClient.d.ts.map +1 -1
- package/lib/newops/appClient.js +7 -7
- package/lib/newops/appClient.js.map +1 -1
- package/lib/newops/clientCommands.d.ts +3 -2
- package/lib/newops/clientCommands.d.ts.map +1 -1
- package/lib/newops/clientCommands.js +19 -12
- 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.d.ts +3 -0
- package/lib/newops/psbtv2.d.ts.map +1 -1
- package/lib/newops/psbtv2.js +14 -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 +1 -1
- package/lib-es/BtcNew.d.ts.map +1 -1
- package/lib-es/BtcNew.js +73 -176
- 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.d.ts +1 -1
- package/lib-es/newops/appClient.d.ts.map +1 -1
- package/lib-es/newops/appClient.js +7 -7
- package/lib-es/newops/appClient.js.map +1 -1
- package/lib-es/newops/clientCommands.d.ts +3 -2
- package/lib-es/newops/clientCommands.d.ts.map +1 -1
- package/lib-es/newops/clientCommands.js +19 -12
- 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.d.ts +3 -0
- package/lib-es/newops/psbtv2.d.ts.map +1 -1
- package/lib-es/newops/psbtv2.js +14 -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 +105 -174
- package/src/newops/accounttype.ts +373 -0
- package/src/newops/appClient.ts +8 -7
- package/src/newops/clientCommands.ts +19 -12
- package/src/newops/merkle.ts +2 -2
- package/src/newops/psbtExtractor.ts +2 -2
- package/src/newops/psbtv2.ts +13 -4
- package/tests/Btc.test.ts +68 -39
- package/tests/newops/BtcNew.test.ts +47 -20
- package/tests/newops/integrationtools.ts +91 -50
- package/tests/newops/merkle.test.ts +1 -1
- package/tests/newops/testtx.ts +0 -55
|
@@ -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
|
);
|
|
@@ -96,7 +96,7 @@ export class AppClient {
|
|
|
96
96
|
throw new Error("Invalid HMAC length");
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
-
const clientInterpreter = new ClientCommandInterpreter();
|
|
99
|
+
const clientInterpreter = new ClientCommandInterpreter(() => {});
|
|
100
100
|
clientInterpreter.addKnownList(
|
|
101
101
|
walletPolicy.keys.map((k) => Buffer.from(k, "ascii"))
|
|
102
102
|
);
|
|
@@ -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
|
|
@@ -123,7 +123,8 @@ export class AppClient {
|
|
|
123
123
|
async signPsbt(
|
|
124
124
|
psbt: PsbtV2,
|
|
125
125
|
walletPolicy: WalletPolicy,
|
|
126
|
-
walletHMAC: Buffer | null
|
|
126
|
+
walletHMAC: Buffer | null,
|
|
127
|
+
progressCallback: () => void
|
|
127
128
|
): Promise<Map<number, Buffer>> {
|
|
128
129
|
const merkelizedPsbt = new MerkelizedPsbt(psbt);
|
|
129
130
|
|
|
@@ -131,7 +132,7 @@ export class AppClient {
|
|
|
131
132
|
throw new Error("Invalid HMAC length");
|
|
132
133
|
}
|
|
133
134
|
|
|
134
|
-
const clientInterpreter = new ClientCommandInterpreter();
|
|
135
|
+
const clientInterpreter = new ClientCommandInterpreter(progressCallback);
|
|
135
136
|
|
|
136
137
|
// prepare ClientCommandInterpreter
|
|
137
138
|
clientInterpreter.addKnownList(
|
|
@@ -180,6 +181,6 @@ export class AppClient {
|
|
|
180
181
|
}
|
|
181
182
|
|
|
182
183
|
async getMasterFingerprint(): Promise<Buffer> {
|
|
183
|
-
return this.makeRequest(BitcoinIns.GET_MASTER_FINGERPRINT, Buffer.
|
|
184
|
+
return this.makeRequest(BitcoinIns.GET_MASTER_FINGERPRINT, Buffer.from([]));
|
|
184
185
|
}
|
|
185
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";
|
|
@@ -21,13 +22,14 @@ export class YieldCommand extends ClientCommand {
|
|
|
21
22
|
|
|
22
23
|
code = ClientCommandCode.YIELD;
|
|
23
24
|
|
|
24
|
-
constructor(results: Buffer[]) {
|
|
25
|
+
constructor(results: Buffer[], private progressCallback: () => void) {
|
|
25
26
|
super();
|
|
26
27
|
this.results = results;
|
|
27
28
|
}
|
|
28
29
|
|
|
29
30
|
execute(request: Buffer): Buffer {
|
|
30
31
|
this.results.push(Buffer.from(request.subarray(1)));
|
|
32
|
+
this.progressCallback();
|
|
31
33
|
return Buffer.from("");
|
|
32
34
|
}
|
|
33
35
|
}
|
|
@@ -105,19 +107,24 @@ export class GetMerkleLeafProofCommand extends ClientCommand {
|
|
|
105
107
|
execute(request: Buffer): Buffer {
|
|
106
108
|
const req = request.subarray(1);
|
|
107
109
|
|
|
108
|
-
if (req.length
|
|
109
|
-
throw new Error("Invalid request,
|
|
110
|
+
if (req.length < 32 + 1 + 1) {
|
|
111
|
+
throw new Error("Invalid request, expected at least 34 bytes");
|
|
110
112
|
}
|
|
111
113
|
|
|
112
|
-
|
|
113
|
-
const hash =
|
|
114
|
-
for (let i = 0; i < 32; i++) {
|
|
115
|
-
hash[i] = req.readUInt8(i);
|
|
116
|
-
}
|
|
114
|
+
const reqBuf = new BufferReader(req);
|
|
115
|
+
const hash = reqBuf.readSlice(32);
|
|
117
116
|
const hash_hex = hash.toString("hex");
|
|
118
117
|
|
|
119
|
-
|
|
120
|
-
|
|
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
|
+
}
|
|
121
128
|
|
|
122
129
|
const mt = this.known_trees.get(hash_hex);
|
|
123
130
|
if (!mt) {
|
|
@@ -272,9 +279,9 @@ export class ClientCommandInterpreter {
|
|
|
272
279
|
|
|
273
280
|
private commands: Map<ClientCommandCode, ClientCommand> = new Map();
|
|
274
281
|
|
|
275
|
-
constructor() {
|
|
282
|
+
constructor(progressCallback: () => void) {
|
|
276
283
|
const commands = [
|
|
277
|
-
new YieldCommand(this.yielded),
|
|
284
|
+
new YieldCommand(this.yielded, progressCallback),
|
|
278
285
|
new GetPreimageCommand(this.preimages, this.queue),
|
|
279
286
|
new GetMerkleLeafIndexCommand(this.roots),
|
|
280
287
|
new GetMerkleLeafProofCommand(this.roots, this.queue),
|
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
|
@@ -14,6 +14,7 @@ export enum psbtIn {
|
|
|
14
14
|
NON_WITNESS_UTXO = 0x00,
|
|
15
15
|
WITNESS_UTXO = 0x01,
|
|
16
16
|
PARTIAL_SIG = 0x02,
|
|
17
|
+
SIGHASH_TYPE = 0x03,
|
|
17
18
|
REDEEM_SCRIPT = 0x04,
|
|
18
19
|
BIP32_DERIVATION = 0x06,
|
|
19
20
|
FINAL_SCRIPTSIG = 0x07,
|
|
@@ -32,7 +33,7 @@ export enum psbtOut {
|
|
|
32
33
|
TAP_BIP32_DERIVATION = 0x07,
|
|
33
34
|
}
|
|
34
35
|
|
|
35
|
-
const PSBT_MAGIC_BYTES = Buffer.
|
|
36
|
+
const PSBT_MAGIC_BYTES = Buffer.from([0x70, 0x73, 0x62, 0x74, 0xff]);
|
|
36
37
|
|
|
37
38
|
export class NoSuchEntry extends Error {}
|
|
38
39
|
|
|
@@ -128,6 +129,14 @@ export class PsbtV2 {
|
|
|
128
129
|
getInputPartialSig(inputIndex: number, pubkey: Buffer): Buffer | undefined {
|
|
129
130
|
return this.getInputOptional(inputIndex, psbtIn.PARTIAL_SIG, pubkey);
|
|
130
131
|
}
|
|
132
|
+
setInputSighashType(inputIndex: number, sigHashtype: number) {
|
|
133
|
+
this.setInput(inputIndex, psbtIn.SIGHASH_TYPE, b(), uint32LE(sigHashtype));
|
|
134
|
+
}
|
|
135
|
+
getInputSighashType(inputIndex: number): number | undefined {
|
|
136
|
+
const result = this.getInputOptional(inputIndex, psbtIn.SIGHASH_TYPE, b());
|
|
137
|
+
if (!result) return undefined;
|
|
138
|
+
return result.readUInt32LE(0);
|
|
139
|
+
}
|
|
131
140
|
setInputRedeemScript(inputIndex: number, redeemScript: Buffer) {
|
|
132
141
|
this.setInput(inputIndex, psbtIn.REDEEM_SCRIPT, b(), redeemScript);
|
|
133
142
|
}
|
|
@@ -312,7 +321,7 @@ export class PsbtV2 {
|
|
|
312
321
|
}
|
|
313
322
|
serialize(): Buffer {
|
|
314
323
|
const buf = new BufferWriter();
|
|
315
|
-
buf.writeSlice(Buffer.
|
|
324
|
+
buf.writeSlice(Buffer.from([0x70, 0x73, 0x62, 0x74, 0xff]));
|
|
316
325
|
serializeMap(buf, this.globalMap);
|
|
317
326
|
this.inputMaps.forEach((map) => {
|
|
318
327
|
serializeMap(buf, map);
|
|
@@ -362,7 +371,7 @@ export class PsbtV2 {
|
|
|
362
371
|
return keyTypes.some((k) => k == keyType);
|
|
363
372
|
}
|
|
364
373
|
private setGlobal(keyType: KeyType, value: Buffer) {
|
|
365
|
-
const key = new Key(keyType, Buffer.
|
|
374
|
+
const key = new Key(keyType, Buffer.from([]));
|
|
366
375
|
this.globalMap.set(key.toString(), value);
|
|
367
376
|
}
|
|
368
377
|
private getGlobal(keyType: KeyType): Buffer {
|
|
@@ -532,7 +541,7 @@ function serializeMap(buf: BufferWriter, map: Map<string, Buffer>) {
|
|
|
532
541
|
}
|
|
533
542
|
|
|
534
543
|
function b(): Buffer {
|
|
535
|
-
return Buffer.
|
|
544
|
+
return Buffer.from([]);
|
|
536
545
|
}
|
|
537
546
|
function set(
|
|
538
547
|
map: Map<string, Buffer>,
|