@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.
Files changed (69) hide show
  1. package/README.md +110 -47
  2. package/lib/Btc.d.ts.map +1 -1
  3. package/lib/Btc.js +5 -3
  4. package/lib/Btc.js.map +1 -1
  5. package/lib/BtcNew.d.ts +1 -1
  6. package/lib/BtcNew.d.ts.map +1 -1
  7. package/lib/BtcNew.js +71 -174
  8. package/lib/BtcNew.js.map +1 -1
  9. package/lib/newops/accounttype.d.ts +110 -0
  10. package/lib/newops/accounttype.d.ts.map +1 -0
  11. package/lib/newops/accounttype.js +236 -0
  12. package/lib/newops/accounttype.js.map +1 -0
  13. package/lib/newops/appClient.d.ts +1 -1
  14. package/lib/newops/appClient.d.ts.map +1 -1
  15. package/lib/newops/appClient.js +7 -7
  16. package/lib/newops/appClient.js.map +1 -1
  17. package/lib/newops/clientCommands.d.ts +3 -2
  18. package/lib/newops/clientCommands.d.ts.map +1 -1
  19. package/lib/newops/clientCommands.js +19 -12
  20. package/lib/newops/clientCommands.js.map +1 -1
  21. package/lib/newops/merkle.js +2 -2
  22. package/lib/newops/merkle.js.map +1 -1
  23. package/lib/newops/psbtExtractor.js +2 -2
  24. package/lib/newops/psbtExtractor.js.map +1 -1
  25. package/lib/newops/psbtv2.d.ts +3 -0
  26. package/lib/newops/psbtv2.d.ts.map +1 -1
  27. package/lib/newops/psbtv2.js +14 -4
  28. package/lib/newops/psbtv2.js.map +1 -1
  29. package/lib-es/Btc.d.ts.map +1 -1
  30. package/lib-es/Btc.js +5 -3
  31. package/lib-es/Btc.js.map +1 -1
  32. package/lib-es/BtcNew.d.ts +1 -1
  33. package/lib-es/BtcNew.d.ts.map +1 -1
  34. package/lib-es/BtcNew.js +73 -176
  35. package/lib-es/BtcNew.js.map +1 -1
  36. package/lib-es/newops/accounttype.d.ts +110 -0
  37. package/lib-es/newops/accounttype.d.ts.map +1 -0
  38. package/lib-es/newops/accounttype.js +233 -0
  39. package/lib-es/newops/accounttype.js.map +1 -0
  40. package/lib-es/newops/appClient.d.ts +1 -1
  41. package/lib-es/newops/appClient.d.ts.map +1 -1
  42. package/lib-es/newops/appClient.js +7 -7
  43. package/lib-es/newops/appClient.js.map +1 -1
  44. package/lib-es/newops/clientCommands.d.ts +3 -2
  45. package/lib-es/newops/clientCommands.d.ts.map +1 -1
  46. package/lib-es/newops/clientCommands.js +19 -12
  47. package/lib-es/newops/clientCommands.js.map +1 -1
  48. package/lib-es/newops/merkle.js +2 -2
  49. package/lib-es/newops/merkle.js.map +1 -1
  50. package/lib-es/newops/psbtExtractor.js +2 -2
  51. package/lib-es/newops/psbtExtractor.js.map +1 -1
  52. package/lib-es/newops/psbtv2.d.ts +3 -0
  53. package/lib-es/newops/psbtv2.d.ts.map +1 -1
  54. package/lib-es/newops/psbtv2.js +14 -4
  55. package/lib-es/newops/psbtv2.js.map +1 -1
  56. package/package.json +3 -3
  57. package/src/Btc.ts +34 -3
  58. package/src/BtcNew.ts +105 -174
  59. package/src/newops/accounttype.ts +373 -0
  60. package/src/newops/appClient.ts +8 -7
  61. package/src/newops/clientCommands.ts +19 -12
  62. package/src/newops/merkle.ts +2 -2
  63. package/src/newops/psbtExtractor.ts +2 -2
  64. package/src/newops/psbtv2.ts +13 -4
  65. package/tests/Btc.test.ts +68 -39
  66. package/tests/newops/BtcNew.test.ts +47 -20
  67. package/tests/newops/integrationtools.ts +91 -50
  68. package/tests/newops/merkle.test.ts +1 -1
  69. 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, BufferWriter } from "./buffertools";
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 { hashPublicKey } from "./hashPublicKey";
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 { createKey, WalletPolicy } from "./newops/policy";
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
- accountTypeFrom(opts?.format ?? "legacy"),
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
- accountType: AccountType,
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
- accountType,
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
- if (arg.inputs.length == 0) {
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(arg.inputs.length);
213
+ psbt.setGlobalInputCount(inputCount);
207
214
  psbt.setGlobalPsbtVersion(2);
208
215
  psbt.setGlobalTxVersion(2);
209
216
 
210
- // The master fingerprint is needed when adding BIP32 derivation paths on
211
- // the psbt.
212
- const masterFp = await this.client.getMasterFingerprint();
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 < arg.inputs.length; 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 = changeData && outputScript.equals(changeData?.script);
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
- if (accountType == AccountType.p2pkh) {
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?.script.toString("hex")
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
- await this.signPsbt(psbt, p);
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
- let pubkey = pubkeyFromXpub(xpub);
317
- if (accountType == AccountType.p2tr) {
318
- pubkey = pubkey.slice(1);
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
- const redeemScript = input[2];
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 spentOutput = inputTx.outputs[spentOutputIndex];
357
-
358
- if (accountType == AccountType.p2pkh) {
359
- psbt.setInputNonWitnessUtxo(i, inputTxBuffer);
360
- psbt.setInputBip32Derivation(i, pubkey, masterFP, pathElements);
361
- } else if (accountType == AccountType.p2wpkh) {
362
- psbt.setInputNonWitnessUtxo(i, inputTxBuffer);
363
- psbt.setInputBip32Derivation(i, pubkey, masterFP, pathElements);
364
- psbt.setInputWitnessUtxo(i, spentOutput.amount, spentOutput.script);
365
- } else if (accountType == AccountType.p2wpkhWrapped) {
366
- psbt.setInputNonWitnessUtxo(i, inputTxBuffer);
367
- psbt.setInputBip32Derivation(i, pubkey, masterFP, pathElements);
368
- if (!redeemScript) {
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
- enum AccountType {
426
- p2pkh = "pkh(@0)",
427
- p2wpkh = "wpkh(@0)",
428
- p2wpkhWrapped = "sh(wpkh(@0))",
429
- p2tr = "tr(@0)",
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(arg: CreateTransactionArg): AccountType {
481
- if (arg.additionals.includes("bech32m")) return AccountType.p2tr;
482
- if (arg.additionals.includes("bech32")) return AccountType.p2wpkh;
483
- if (arg.segwit) return AccountType.p2wpkhWrapped;
484
- return AccountType.p2pkh;
485
- }
486
-
487
- /*
488
- The following two functions are copied from wallet-btc and adapted.
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
  }