@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
package/src/BtcNew.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { crypto } from "bitcoinjs-lib";
|
|
2
|
-
import { pointCompress, pointAddScalar } from "tiny-secp256k1";
|
|
3
2
|
import semver from "semver";
|
|
3
|
+
import { pointCompress } from "tiny-secp256k1";
|
|
4
4
|
import {
|
|
5
5
|
getXpubComponents,
|
|
6
6
|
hardenedPathOf,
|
|
@@ -8,26 +8,29 @@ import {
|
|
|
8
8
|
pathStringToArray,
|
|
9
9
|
pubkeyFromXpub,
|
|
10
10
|
} from "./bip32";
|
|
11
|
-
import { BufferReader
|
|
11
|
+
import { BufferReader } from "./buffertools";
|
|
12
12
|
import type { CreateTransactionArg } from "./createTransaction";
|
|
13
|
+
import { AppAndVersion } from "./getAppAndVersion";
|
|
13
14
|
import type { AddressFormat } from "./getWalletPublicKey";
|
|
14
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
AccountType,
|
|
17
|
+
p2pkh,
|
|
18
|
+
p2tr,
|
|
19
|
+
p2wpkh,
|
|
20
|
+
p2wpkhWrapped,
|
|
21
|
+
SpendingCondition,
|
|
22
|
+
} from "./newops/accounttype";
|
|
15
23
|
import { AppClient as Client } from "./newops/appClient";
|
|
16
|
-
import {
|
|
24
|
+
import {
|
|
25
|
+
createKey,
|
|
26
|
+
DefaultDescriptorTemplate,
|
|
27
|
+
WalletPolicy,
|
|
28
|
+
} from "./newops/policy";
|
|
17
29
|
import { extract } from "./newops/psbtExtractor";
|
|
18
30
|
import { finalize } from "./newops/psbtFinalizer";
|
|
19
31
|
import { psbtIn, PsbtV2 } from "./newops/psbtv2";
|
|
20
32
|
import { serializeTransaction } from "./serializeTransaction";
|
|
21
33
|
import type { Transaction } from "./types";
|
|
22
|
-
import {
|
|
23
|
-
HASH_SIZE,
|
|
24
|
-
OP_CHECKSIG,
|
|
25
|
-
OP_DUP,
|
|
26
|
-
OP_EQUAL,
|
|
27
|
-
OP_EQUALVERIFY,
|
|
28
|
-
OP_HASH160,
|
|
29
|
-
} from "./constants";
|
|
30
|
-
import { AppAndVersion } from "./getAppAndVersion";
|
|
31
34
|
|
|
32
35
|
const newSupportedApps = ["Bitcoin", "Bitcoin Test"];
|
|
33
36
|
|
|
@@ -127,7 +130,7 @@ export default class BtcNew {
|
|
|
127
130
|
|
|
128
131
|
const address = await this.getWalletAddress(
|
|
129
132
|
pathElements,
|
|
130
|
-
|
|
133
|
+
descrTemplFrom(opts?.format ?? "legacy"),
|
|
131
134
|
display
|
|
132
135
|
);
|
|
133
136
|
const components = getXpubComponents(xpub);
|
|
@@ -158,7 +161,7 @@ export default class BtcNew {
|
|
|
158
161
|
*/
|
|
159
162
|
private async getWalletAddress(
|
|
160
163
|
pathElements: number[],
|
|
161
|
-
|
|
164
|
+
descrTempl: DefaultDescriptorTemplate,
|
|
162
165
|
display: boolean
|
|
163
166
|
): Promise<string> {
|
|
164
167
|
const accountPath = hardenedPathOf(pathElements);
|
|
@@ -168,7 +171,7 @@ export default class BtcNew {
|
|
|
168
171
|
const accountXpub = await this.client.getExtendedPubkey(false, accountPath);
|
|
169
172
|
const masterFingerprint = await this.client.getMasterFingerprint();
|
|
170
173
|
const policy = new WalletPolicy(
|
|
171
|
-
|
|
174
|
+
descrTempl,
|
|
172
175
|
createKey(masterFingerprint, accountPath, accountXpub)
|
|
173
176
|
);
|
|
174
177
|
const changeAndIndex = pathElements.slice(-2, pathElements.length);
|
|
@@ -192,27 +195,39 @@ export default class BtcNew {
|
|
|
192
195
|
async createPaymentTransactionNew(
|
|
193
196
|
arg: CreateTransactionArg
|
|
194
197
|
): Promise<string> {
|
|
195
|
-
|
|
198
|
+
const inputCount = arg.inputs.length;
|
|
199
|
+
if (inputCount == 0) {
|
|
196
200
|
throw Error("No inputs");
|
|
197
201
|
}
|
|
198
202
|
const psbt = new PsbtV2();
|
|
203
|
+
// The master fingerprint is needed when adding BIP32 derivation paths on
|
|
204
|
+
// the psbt.
|
|
205
|
+
const masterFp = await this.client.getMasterFingerprint();
|
|
199
206
|
|
|
200
|
-
const accountType = accountTypeFromArg(arg);
|
|
207
|
+
const accountType = accountTypeFromArg(arg, psbt, masterFp);
|
|
201
208
|
|
|
202
209
|
if (arg.lockTime) {
|
|
203
210
|
// The signer will assume locktime 0 if unset
|
|
204
211
|
psbt.setGlobalFallbackLocktime(arg.lockTime);
|
|
205
212
|
}
|
|
206
|
-
psbt.setGlobalInputCount(
|
|
213
|
+
psbt.setGlobalInputCount(inputCount);
|
|
207
214
|
psbt.setGlobalPsbtVersion(2);
|
|
208
215
|
psbt.setGlobalTxVersion(2);
|
|
209
216
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
217
|
+
let notifyCount = 0;
|
|
218
|
+
const progress = () => {
|
|
219
|
+
if (!arg.onDeviceStreaming) return;
|
|
220
|
+
arg.onDeviceStreaming({
|
|
221
|
+
total: 2 * inputCount,
|
|
222
|
+
index: notifyCount,
|
|
223
|
+
progress: ++notifyCount / (2 * inputCount),
|
|
224
|
+
});
|
|
225
|
+
};
|
|
226
|
+
|
|
213
227
|
let accountXpub = "";
|
|
214
228
|
let accountPath: number[] = [];
|
|
215
|
-
for (let i = 0; i <
|
|
229
|
+
for (let i = 0; i < inputCount; i++) {
|
|
230
|
+
progress();
|
|
216
231
|
const pathElems: number[] = pathStringToArray(arg.associatedKeysets[i]);
|
|
217
232
|
if (accountXpub == "") {
|
|
218
233
|
// We assume all inputs belong to the same account so we set
|
|
@@ -226,7 +241,8 @@ export default class BtcNew {
|
|
|
226
241
|
arg.inputs[i],
|
|
227
242
|
pathElems,
|
|
228
243
|
accountType,
|
|
229
|
-
masterFp
|
|
244
|
+
masterFp,
|
|
245
|
+
arg.sigHashType
|
|
230
246
|
);
|
|
231
247
|
}
|
|
232
248
|
|
|
@@ -251,36 +267,41 @@ export default class BtcNew {
|
|
|
251
267
|
// We won't know if we're paying to ourselves, because there's no
|
|
252
268
|
// information in arg to support multiple "change paths". One exception is
|
|
253
269
|
// if there are multiple outputs to the change address.
|
|
254
|
-
const isChange =
|
|
270
|
+
const isChange =
|
|
271
|
+
changeData && outputScript.equals(changeData?.cond.scriptPubKey);
|
|
255
272
|
if (isChange) {
|
|
256
273
|
changeFound = true;
|
|
257
274
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
258
275
|
const changePath = pathStringToArray(arg.changePath!);
|
|
259
276
|
const pubkey = changeData.pubkey;
|
|
260
277
|
|
|
261
|
-
|
|
262
|
-
psbt.setOutputBip32Derivation(i, pubkey, masterFp, changePath);
|
|
263
|
-
} else if (accountType == AccountType.p2wpkh) {
|
|
264
|
-
psbt.setOutputBip32Derivation(i, pubkey, masterFp, changePath);
|
|
265
|
-
} else if (accountType == AccountType.p2wpkhWrapped) {
|
|
266
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
267
|
-
psbt.setOutputRedeemScript(i, changeData.redeemScript!);
|
|
268
|
-
psbt.setOutputBip32Derivation(i, pubkey, masterFp, changePath);
|
|
269
|
-
} else if (accountType == AccountType.p2tr) {
|
|
270
|
-
psbt.setOutputTapBip32Derivation(i, pubkey, [], masterFp, changePath);
|
|
271
|
-
}
|
|
278
|
+
accountType.setOwnOutput(i, changeData.cond, [pubkey], [changePath]);
|
|
272
279
|
}
|
|
273
280
|
}
|
|
274
281
|
if (!changeFound) {
|
|
275
282
|
throw new Error(
|
|
276
283
|
"Change script not found among outputs! " +
|
|
277
|
-
changeData?.
|
|
284
|
+
changeData?.cond.scriptPubKey.toString("hex")
|
|
278
285
|
);
|
|
279
286
|
}
|
|
280
287
|
|
|
281
288
|
const key = createKey(masterFp, accountPath, accountXpub);
|
|
282
|
-
const p = new WalletPolicy(accountType, key);
|
|
283
|
-
|
|
289
|
+
const p = new WalletPolicy(accountType.getDescriptorTemplate(), key);
|
|
290
|
+
// This is cheating, because it's not actually requested on the
|
|
291
|
+
// device yet, but it will be, soonish.
|
|
292
|
+
if (arg.onDeviceSignatureRequested) arg.onDeviceSignatureRequested();
|
|
293
|
+
|
|
294
|
+
let firstSigned = false;
|
|
295
|
+
// This callback will be called once for each signature yielded.
|
|
296
|
+
const progressCallback = () => {
|
|
297
|
+
if (!firstSigned) {
|
|
298
|
+
firstSigned = true;
|
|
299
|
+
arg.onDeviceSignatureGranted && arg.onDeviceSignatureGranted();
|
|
300
|
+
}
|
|
301
|
+
progress();
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
await this.signPsbt(psbt, p, progressCallback);
|
|
284
305
|
finalize(psbt);
|
|
285
306
|
const serializedTx = extract(psbt);
|
|
286
307
|
return serializedTx.toString("hex");
|
|
@@ -298,9 +319,7 @@ export default class BtcNew {
|
|
|
298
319
|
accountPath: number[],
|
|
299
320
|
accountType: AccountType,
|
|
300
321
|
path: string | undefined
|
|
301
|
-
): Promise<
|
|
302
|
-
{ script: Buffer; redeemScript?: Buffer; pubkey: Buffer } | undefined
|
|
303
|
-
> {
|
|
322
|
+
): Promise<{ cond: SpendingCondition; pubkey: Buffer } | undefined> {
|
|
304
323
|
if (!path) return undefined;
|
|
305
324
|
const pathElems = pathStringToArray(path);
|
|
306
325
|
// Make sure path is in our account, otherwise something fishy is probably
|
|
@@ -313,12 +332,9 @@ export default class BtcNew {
|
|
|
313
332
|
}
|
|
314
333
|
}
|
|
315
334
|
const xpub = await this.client.getExtendedPubkey(false, pathElems);
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
}
|
|
320
|
-
const script = outputScriptOf(pubkey, accountType);
|
|
321
|
-
return { ...script, pubkey };
|
|
335
|
+
const pubkey = pubkeyFromXpub(xpub);
|
|
336
|
+
const cond = accountType.spendingCondition([pubkey]);
|
|
337
|
+
return { cond, pubkey };
|
|
322
338
|
}
|
|
323
339
|
|
|
324
340
|
/**
|
|
@@ -337,15 +353,21 @@ export default class BtcNew {
|
|
|
337
353
|
],
|
|
338
354
|
pathElements: number[],
|
|
339
355
|
accountType: AccountType,
|
|
340
|
-
masterFP: Buffer
|
|
356
|
+
masterFP: Buffer,
|
|
357
|
+
sigHashType?: number
|
|
341
358
|
): Promise<void> {
|
|
342
359
|
const inputTx = input[0];
|
|
343
360
|
const spentOutputIndex = input[1];
|
|
344
|
-
|
|
361
|
+
// redeemScript will be null for wrapped p2wpkh, we need to create it
|
|
362
|
+
// ourselves. But if set, it should be used.
|
|
363
|
+
const redeemScript = input[2] ? Buffer.from(input[2], "hex") : undefined;
|
|
345
364
|
const sequence = input[3];
|
|
346
365
|
if (sequence) {
|
|
347
366
|
psbt.setInputSequence(i, sequence);
|
|
348
367
|
}
|
|
368
|
+
if (sigHashType) {
|
|
369
|
+
psbt.setInputSighashType(i, sigHashType);
|
|
370
|
+
}
|
|
349
371
|
const inputTxBuffer = serializeTransaction(inputTx, true);
|
|
350
372
|
const inputTxid = crypto.hash256(inputTxBuffer);
|
|
351
373
|
const xpubBase58 = await this.client.getExtendedPubkey(false, pathElements);
|
|
@@ -353,32 +375,19 @@ export default class BtcNew {
|
|
|
353
375
|
const pubkey = pubkeyFromXpub(xpubBase58);
|
|
354
376
|
if (!inputTx.outputs)
|
|
355
377
|
throw Error("Missing outputs array in transaction to sign");
|
|
356
|
-
const
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
throw new Error("Missing redeemScript for p2wpkhWrapped input");
|
|
370
|
-
}
|
|
371
|
-
const expectedRedeemScript = createRedeemScript(pubkey);
|
|
372
|
-
if (redeemScript != expectedRedeemScript.toString("hex")) {
|
|
373
|
-
throw new Error("Unexpected redeemScript");
|
|
374
|
-
}
|
|
375
|
-
psbt.setInputRedeemScript(i, expectedRedeemScript);
|
|
376
|
-
psbt.setInputWitnessUtxo(i, spentOutput.amount, spentOutput.script);
|
|
377
|
-
} else if (accountType == AccountType.p2tr) {
|
|
378
|
-
const xonly = pubkey.slice(1);
|
|
379
|
-
psbt.setInputTapBip32Derivation(i, xonly, [], masterFP, pathElements);
|
|
380
|
-
psbt.setInputWitnessUtxo(i, spentOutput.amount, spentOutput.script);
|
|
381
|
-
}
|
|
378
|
+
const spentTxOutput = inputTx.outputs[spentOutputIndex];
|
|
379
|
+
const spendCondition: SpendingCondition = {
|
|
380
|
+
scriptPubKey: spentTxOutput.script,
|
|
381
|
+
redeemScript: redeemScript,
|
|
382
|
+
};
|
|
383
|
+
const spentOutput = { cond: spendCondition, amount: spentTxOutput.amount };
|
|
384
|
+
accountType.setInput(
|
|
385
|
+
i,
|
|
386
|
+
inputTxBuffer,
|
|
387
|
+
spentOutput,
|
|
388
|
+
[pubkey],
|
|
389
|
+
[pathElements]
|
|
390
|
+
);
|
|
382
391
|
|
|
383
392
|
psbt.setInputPreviousTxId(i, inputTxid);
|
|
384
393
|
psbt.setInputOutputIndex(i, spentOutputIndex);
|
|
@@ -395,12 +404,14 @@ export default class BtcNew {
|
|
|
395
404
|
*/
|
|
396
405
|
private async signPsbt(
|
|
397
406
|
psbt: PsbtV2,
|
|
398
|
-
walletPolicy: WalletPolicy
|
|
407
|
+
walletPolicy: WalletPolicy,
|
|
408
|
+
progressCallback: () => void
|
|
399
409
|
): Promise<void> {
|
|
400
410
|
const sigs: Map<number, Buffer> = await this.client.signPsbt(
|
|
401
411
|
psbt,
|
|
402
412
|
walletPolicy,
|
|
403
|
-
Buffer.alloc(32, 0)
|
|
413
|
+
Buffer.alloc(32, 0),
|
|
414
|
+
progressCallback
|
|
404
415
|
);
|
|
405
416
|
sigs.forEach((v, k) => {
|
|
406
417
|
// Note: Looking at BIP32 derivation does not work in the generic case,
|
|
@@ -422,103 +433,23 @@ export default class BtcNew {
|
|
|
422
433
|
}
|
|
423
434
|
}
|
|
424
435
|
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
function createRedeemScript(pubkey: Buffer): Buffer {
|
|
433
|
-
const pubkeyHash = hashPublicKey(pubkey);
|
|
434
|
-
return Buffer.concat([Buffer.from("0014", "hex"), pubkeyHash]);
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
/**
|
|
438
|
-
* Generates a single signature scriptPubKey (output script) from a public key.
|
|
439
|
-
* This is done differently depending on account type.
|
|
440
|
-
*
|
|
441
|
-
* If accountType is p2tr, the public key must be a 32 byte x-only taproot
|
|
442
|
-
* pubkey, otherwise it's expected to be a 33 byte ecdsa compressed pubkey.
|
|
443
|
-
*/
|
|
444
|
-
function outputScriptOf(
|
|
445
|
-
pubkey: Buffer,
|
|
446
|
-
accountType: AccountType
|
|
447
|
-
): { script: Buffer; redeemScript?: Buffer } {
|
|
448
|
-
const buf = new BufferWriter();
|
|
449
|
-
const pubkeyHash = hashPublicKey(pubkey);
|
|
450
|
-
let redeemScript: Buffer | undefined;
|
|
451
|
-
if (accountType == AccountType.p2pkh) {
|
|
452
|
-
buf.writeSlice(Buffer.of(OP_DUP, OP_HASH160, HASH_SIZE));
|
|
453
|
-
buf.writeSlice(pubkeyHash);
|
|
454
|
-
buf.writeSlice(Buffer.of(OP_EQUALVERIFY, OP_CHECKSIG));
|
|
455
|
-
} else if (accountType == AccountType.p2wpkhWrapped) {
|
|
456
|
-
redeemScript = createRedeemScript(pubkey);
|
|
457
|
-
const scriptHash = hashPublicKey(redeemScript);
|
|
458
|
-
buf.writeSlice(Buffer.of(OP_HASH160, HASH_SIZE));
|
|
459
|
-
buf.writeSlice(scriptHash);
|
|
460
|
-
buf.writeUInt8(OP_EQUAL);
|
|
461
|
-
} else if (accountType == AccountType.p2wpkh) {
|
|
462
|
-
buf.writeSlice(Buffer.of(0, HASH_SIZE));
|
|
463
|
-
buf.writeSlice(pubkeyHash);
|
|
464
|
-
} else if (accountType == AccountType.p2tr) {
|
|
465
|
-
const outputKey = getTaprootOutputKey(pubkey);
|
|
466
|
-
buf.writeSlice(Buffer.of(0x51, 32)); // push1, pubkeylen
|
|
467
|
-
buf.writeSlice(outputKey);
|
|
468
|
-
}
|
|
469
|
-
return { script: buf.buffer(), redeemScript };
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
function accountTypeFrom(addressFormat: AddressFormat): AccountType {
|
|
473
|
-
if (addressFormat == "legacy") return AccountType.p2pkh;
|
|
474
|
-
if (addressFormat == "p2sh") return AccountType.p2wpkhWrapped;
|
|
475
|
-
if (addressFormat == "bech32") return AccountType.p2wpkh;
|
|
476
|
-
if (addressFormat == "bech32m") return AccountType.p2tr;
|
|
436
|
+
function descrTemplFrom(
|
|
437
|
+
addressFormat: AddressFormat
|
|
438
|
+
): DefaultDescriptorTemplate {
|
|
439
|
+
if (addressFormat == "legacy") return "pkh(@0)";
|
|
440
|
+
if (addressFormat == "p2sh") return "sh(wpkh(@0))";
|
|
441
|
+
if (addressFormat == "bech32") return "wpkh(@0)";
|
|
442
|
+
if (addressFormat == "bech32m") return "tr(@0)";
|
|
477
443
|
throw new Error("Unsupported address format " + addressFormat);
|
|
478
444
|
}
|
|
479
445
|
|
|
480
|
-
function accountTypeFromArg(
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
They should be moved to a library to avoid code reuse.
|
|
490
|
-
*/
|
|
491
|
-
function hashTapTweak(x: Buffer): Buffer {
|
|
492
|
-
// hash_tag(x) = SHA256(SHA256(tag) || SHA256(tag) || x), see BIP340
|
|
493
|
-
// See https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki#specification
|
|
494
|
-
const h = crypto.sha256(Buffer.from("TapTweak", "utf-8"));
|
|
495
|
-
return crypto.sha256(Buffer.concat([h, h, x]));
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
/**
|
|
499
|
-
* Calculates a taproot output key from an internal key. This output key will be
|
|
500
|
-
* used as witness program in a taproot output. The internal key is tweaked
|
|
501
|
-
* according to recommendation in BIP341:
|
|
502
|
-
* https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki#cite_ref-22-0
|
|
503
|
-
*
|
|
504
|
-
* @param internalPubkey A 32 byte x-only taproot internal key
|
|
505
|
-
* @returns The output key
|
|
506
|
-
*/
|
|
507
|
-
function getTaprootOutputKey(internalPubkey: Buffer): Buffer {
|
|
508
|
-
if (internalPubkey.length != 32) {
|
|
509
|
-
throw new Error("Expected 32 byte pubkey. Got " + internalPubkey.length);
|
|
510
|
-
}
|
|
511
|
-
// A BIP32 derived key can be converted to a schnorr pubkey by dropping
|
|
512
|
-
// the first byte, which represent the oddness/evenness. In schnorr all
|
|
513
|
-
// pubkeys are even.
|
|
514
|
-
// https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki#public-key-conversion
|
|
515
|
-
const evenEcdsaPubkey = Buffer.concat([Buffer.of(0x02), internalPubkey]);
|
|
516
|
-
const tweak = hashTapTweak(internalPubkey);
|
|
517
|
-
|
|
518
|
-
// Q = P + int(hash_TapTweak(bytes(P)))G
|
|
519
|
-
const outputEcdsaKey = Buffer.from(pointAddScalar(evenEcdsaPubkey, tweak));
|
|
520
|
-
// Convert to schnorr.
|
|
521
|
-
const outputSchnorrKey = outputEcdsaKey.slice(1);
|
|
522
|
-
// Create address
|
|
523
|
-
return outputSchnorrKey;
|
|
446
|
+
function accountTypeFromArg(
|
|
447
|
+
arg: CreateTransactionArg,
|
|
448
|
+
psbt: PsbtV2,
|
|
449
|
+
masterFp: Buffer
|
|
450
|
+
): AccountType {
|
|
451
|
+
if (arg.additionals.includes("bech32m")) return new p2tr(psbt, masterFp);
|
|
452
|
+
if (arg.additionals.includes("bech32")) return new p2wpkh(psbt, masterFp);
|
|
453
|
+
if (arg.segwit) return new p2wpkhWrapped(psbt, masterFp);
|
|
454
|
+
return new p2pkh(psbt, masterFp);
|
|
524
455
|
}
|