@bitcoinerlab/descriptors 2.2.1 → 2.3.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 CHANGED
@@ -77,6 +77,16 @@ For miniscript-based descriptors, the `signersPubKeys` parameter in the constuct
77
77
 
78
78
  The `Output` class [offers various helpful methods](https://bitcoinerlab.com/modules/descriptors/api/classes/_Internal_.Output.html), including `getAddress()`, which returns the address associated with the descriptor, `getScriptPubKey()`, which returns the `scriptPubKey` for the descriptor, `expand()`, which decomposes a descriptor into its elemental parts, `updatePsbtAsInput()` and `updatePsbtAsOutput()`.
79
79
 
80
+ The library supports a wide range of descriptor types, including:
81
+ - Pay-to-Public-Key-Hash (P2PKH): `pkh(KEY)`
82
+ - Pay-to-Witness-Public-Key-Hash (P2WPKH): `wpkh(KEY)`
83
+ - Pay-to-Script-Hash (P2SH): `sh(SCRIPT)`
84
+ - Pay-to-Witness-Script-Hash (P2WSH): `wsh(SCRIPT)`
85
+ - Pay-to-Taproot (P2TR) with single key: `tr(KEY)`
86
+ - Address-based descriptors: `addr(ADDRESS)`, including Taproot addresses
87
+
88
+ These descriptors can be used with various key expressions, including raw public keys, BIP32 derivation paths, and more.
89
+
80
90
  The `updatePsbtAsInput()` method is an essential part of the library, responsible for adding an input to the PSBT corresponding to the UTXO described by the descriptor. Additionally, when the descriptor expresses an absolute time-spending condition, such as "This UTXO can only be spent after block N", `updatePsbtAsInput()` adds timelock information to the PSBT.
81
91
 
82
92
  To call `updatePsbtAsInput()`, use the following syntax:
@@ -0,0 +1,2 @@
1
+ import type { Psbt } from 'bitcoinjs-lib';
2
+ export declare const applyPR2137: (psbt: Psbt) => void;
@@ -0,0 +1,123 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.applyPR2137 = void 0;
4
+ const utils_1 = require("bip174/src/lib/utils");
5
+ const bip341_1 = require("bitcoinjs-lib/src/payments/bip341");
6
+ const bip371_1 = require("bitcoinjs-lib/src/psbt/bip371");
7
+ const crypto_1 = require("bitcoinjs-lib/src/crypto");
8
+ const toXOnly = (pubKey) => pubKey.length === 32 ? pubKey : pubKey.slice(1, 33);
9
+ function range(n) {
10
+ return [...Array(n).keys()];
11
+ }
12
+ function tapBranchHash(a, b) {
13
+ return (0, crypto_1.taggedHash)('TapBranch', Buffer.concat([a, b]));
14
+ }
15
+ function calculateScriptTreeMerkleRoot(leafHashes) {
16
+ if (!leafHashes || leafHashes.length === 0) {
17
+ return undefined;
18
+ }
19
+ // sort the leaf nodes
20
+ leafHashes.sort(Buffer.compare);
21
+ // create the initial hash node
22
+ let currentLevel = leafHashes;
23
+ // build Merkle Tree
24
+ while (currentLevel.length > 1) {
25
+ const nextLevel = [];
26
+ for (let i = 0; i < currentLevel.length; i += 2) {
27
+ const left = currentLevel[i];
28
+ const right = i + 1 < currentLevel.length ? currentLevel[i + 1] : left;
29
+ nextLevel.push(i + 1 < currentLevel.length ? tapBranchHash(left, right) : left);
30
+ }
31
+ currentLevel = nextLevel;
32
+ }
33
+ return currentLevel[0];
34
+ }
35
+ function getTweakSignersFromHD(inputIndex, inputs, hdKeyPair) {
36
+ const input = (0, utils_1.checkForInput)(inputs, inputIndex);
37
+ if (!input.tapBip32Derivation || input.tapBip32Derivation.length === 0) {
38
+ throw new Error('Need tapBip32Derivation to sign with HD');
39
+ }
40
+ const myDerivations = input.tapBip32Derivation
41
+ .map(bipDv => {
42
+ if (bipDv.masterFingerprint.equals(hdKeyPair.fingerprint)) {
43
+ return bipDv;
44
+ }
45
+ else {
46
+ return;
47
+ }
48
+ })
49
+ .filter(v => !!v);
50
+ if (myDerivations.length === 0) {
51
+ throw new Error('Need one tapBip32Derivation masterFingerprint to match the HDSigner fingerprint');
52
+ }
53
+ const signers = myDerivations.map(bipDv => {
54
+ const node = hdKeyPair.derivePath(bipDv.path);
55
+ if (!bipDv.pubkey.equals(toXOnly(node.publicKey))) {
56
+ throw new Error('pubkey did not match tapBip32Derivation');
57
+ }
58
+ const h = calculateScriptTreeMerkleRoot(bipDv.leafHashes);
59
+ const tweakValue = (0, bip341_1.tapTweakHash)(toXOnly(node.publicKey), h);
60
+ return node.tweak(tweakValue);
61
+ });
62
+ return signers;
63
+ }
64
+ function getSignersFromHD(inputIndex, inputs, hdKeyPair) {
65
+ const input = (0, utils_1.checkForInput)(inputs, inputIndex);
66
+ if ((0, bip371_1.isTaprootInput)(input)) {
67
+ return getTweakSignersFromHD(inputIndex, inputs, hdKeyPair);
68
+ }
69
+ if (!input.bip32Derivation || input.bip32Derivation.length === 0) {
70
+ throw new Error('Need bip32Derivation to sign with HD');
71
+ }
72
+ const myDerivations = input.bip32Derivation
73
+ .map(bipDv => {
74
+ if (bipDv.masterFingerprint.equals(hdKeyPair.fingerprint)) {
75
+ return bipDv;
76
+ }
77
+ else {
78
+ return;
79
+ }
80
+ })
81
+ .filter(v => !!v);
82
+ if (myDerivations.length === 0) {
83
+ throw new Error('Need one bip32Derivation masterFingerprint to match the HDSigner fingerprint');
84
+ }
85
+ const signers = myDerivations.map(bipDv => {
86
+ const node = hdKeyPair.derivePath(bipDv.path);
87
+ if (!bipDv.pubkey.equals(node.publicKey)) {
88
+ throw new Error('pubkey did not match bip32Derivation');
89
+ }
90
+ return node;
91
+ });
92
+ return signers;
93
+ }
94
+ const applyPR2137 = (psbt) => {
95
+ psbt.signInputHD = function signInputHD(inputIndex, hdKeyPair, sighashTypes) {
96
+ if (!hdKeyPair || !hdKeyPair.publicKey || !hdKeyPair.fingerprint) {
97
+ throw new Error('Need HDSigner to sign input');
98
+ }
99
+ const signers = getSignersFromHD(inputIndex, this.data.inputs, hdKeyPair);
100
+ signers.forEach(signer => this.signInput(inputIndex, signer, sighashTypes));
101
+ return this;
102
+ };
103
+ psbt.signAllInputsHD = function signAllInputsHD(hdKeyPair, sighashTypes) {
104
+ if (!hdKeyPair || !hdKeyPair.publicKey || !hdKeyPair.fingerprint) {
105
+ throw new Error('Need HDSigner to sign input');
106
+ }
107
+ const results = [];
108
+ for (const i of range(psbt.data.inputs.length)) {
109
+ try {
110
+ psbt.signInputHD(i, hdKeyPair, sighashTypes);
111
+ results.push(true);
112
+ }
113
+ catch (err) {
114
+ results.push(false);
115
+ }
116
+ }
117
+ if (results.every(v => v === false)) {
118
+ throw new Error('No inputs were signed');
119
+ }
120
+ return psbt;
121
+ };
122
+ };
123
+ exports.applyPR2137 = applyPR2137;
@@ -47,6 +47,7 @@ export declare function DescriptorsFactory(ecc: TinySecp256k1Interface): {
47
47
  readonly "__#1@#witnessScript"?: Buffer;
48
48
  readonly "__#1@#redeemScript"?: Buffer;
49
49
  readonly "__#1@#isSegwit"?: boolean;
50
+ readonly "__#1@#isTaproot"?: boolean;
50
51
  readonly "__#1@#expandedExpression"?: string;
51
52
  readonly "__#1@#expandedMiniscript"?: string;
52
53
  readonly "__#1@#expansionMap"?: ExpansionMap;
@@ -125,13 +126,29 @@ export declare function DescriptorsFactory(ecc: TinySecp256k1Interface): {
125
126
  */
126
127
  isSegwit(): boolean | undefined;
127
128
  /**
128
- * Returns the tuple: `{ isPKH: boolean; isWPKH: boolean; isSH: boolean; }`
129
- * for this Output.
129
+ * Whether this `Output` is Taproot.
130
+ */
131
+ isTaproot(): boolean | undefined;
132
+ /**
133
+ * Attempts to determine the type of output script by testing it against
134
+ * various payment types.
135
+ *
136
+ * This method tries to identify if the output is one of the following types:
137
+ * - P2SH (Pay to Script Hash)
138
+ * - P2WSH (Pay to Witness Script Hash)
139
+ * - P2WPKH (Pay to Witness Public Key Hash)
140
+ * - P2PKH (Pay to Public Key Hash)
141
+ * - P2TR (Pay to Taproot)
142
+ *
143
+ * @returns An object { isPKH: boolean; isWPKH: boolean; isSH: boolean; isWSH: boolean; isTR: boolean;}
144
+ * with boolean properties indicating the detected output type
130
145
  */
131
146
  guessOutput(): {
132
147
  isPKH: boolean;
133
148
  isWPKH: boolean;
134
149
  isSH: boolean;
150
+ isWSH: boolean;
151
+ isTR: boolean;
135
152
  };
136
153
  /**
137
154
  * Computes the Weight Unit contributions of this Output as if it were the
@@ -140,8 +157,12 @@ export declare function DescriptorsFactory(ecc: TinySecp256k1Interface): {
140
157
  * *NOTE:* When the descriptor in an input is `addr(address)`, it is assumed
141
158
  * that any `addr(SH_TYPE_ADDRESS)` is in fact a Segwit `SH_WPKH`
142
159
  * (Script Hash-Witness Public Key Hash).
160
+ *, Also any `addr(SINGLE_KEY_ADDRESS)` * is assumed to be a single key Taproot
161
+ * address (like those defined in BIP86).
143
162
  * For inputs using arbitrary scripts (not standard addresses),
144
- * use a descriptor in the format `sh(MINISCRIPT)`.
163
+ * use a descriptor in the format `sh(MINISCRIPT)` or `tr(MINISCRIPT)`.
164
+ * Note however that tr(MINISCRIPT) is not yet supported for non-single-key
165
+ * expressions.
145
166
  */
146
167
  inputWeight(isSegwitTx: boolean, signatures: PartialSig[] | 'DANGEROUSLY_USE_FAKE_SIGNATURES'): number;
147
168
  /**
@@ -339,6 +360,7 @@ export declare function DescriptorsFactory(ecc: TinySecp256k1Interface): {
339
360
  readonly "__#1@#witnessScript"?: Buffer;
340
361
  readonly "__#1@#redeemScript"?: Buffer;
341
362
  readonly "__#1@#isSegwit"?: boolean;
363
+ readonly "__#1@#isTaproot"?: boolean;
342
364
  readonly "__#1@#expandedExpression"?: string;
343
365
  readonly "__#1@#expandedMiniscript"?: string;
344
366
  readonly "__#1@#expansionMap"?: ExpansionMap;
@@ -417,13 +439,29 @@ export declare function DescriptorsFactory(ecc: TinySecp256k1Interface): {
417
439
  */
418
440
  isSegwit(): boolean | undefined;
419
441
  /**
420
- * Returns the tuple: `{ isPKH: boolean; isWPKH: boolean; isSH: boolean; }`
421
- * for this Output.
442
+ * Whether this `Output` is Taproot.
443
+ */
444
+ isTaproot(): boolean | undefined;
445
+ /**
446
+ * Attempts to determine the type of output script by testing it against
447
+ * various payment types.
448
+ *
449
+ * This method tries to identify if the output is one of the following types:
450
+ * - P2SH (Pay to Script Hash)
451
+ * - P2WSH (Pay to Witness Script Hash)
452
+ * - P2WPKH (Pay to Witness Public Key Hash)
453
+ * - P2PKH (Pay to Public Key Hash)
454
+ * - P2TR (Pay to Taproot)
455
+ *
456
+ * @returns An object { isPKH: boolean; isWPKH: boolean; isSH: boolean; isWSH: boolean; isTR: boolean;}
457
+ * with boolean properties indicating the detected output type
422
458
  */
423
459
  guessOutput(): {
424
460
  isPKH: boolean;
425
461
  isWPKH: boolean;
426
462
  isSH: boolean;
463
+ isWSH: boolean;
464
+ isTR: boolean;
427
465
  };
428
466
  /**
429
467
  * Computes the Weight Unit contributions of this Output as if it were the
@@ -432,8 +470,12 @@ export declare function DescriptorsFactory(ecc: TinySecp256k1Interface): {
432
470
  * *NOTE:* When the descriptor in an input is `addr(address)`, it is assumed
433
471
  * that any `addr(SH_TYPE_ADDRESS)` is in fact a Segwit `SH_WPKH`
434
472
  * (Script Hash-Witness Public Key Hash).
473
+ *, Also any `addr(SINGLE_KEY_ADDRESS)` * is assumed to be a single key Taproot
474
+ * address (like those defined in BIP86).
435
475
  * For inputs using arbitrary scripts (not standard addresses),
436
- * use a descriptor in the format `sh(MINISCRIPT)`.
476
+ * use a descriptor in the format `sh(MINISCRIPT)` or `tr(MINISCRIPT)`.
477
+ * Note however that tr(MINISCRIPT) is not yet supported for non-single-key
478
+ * expressions.
437
479
  */
438
480
  inputWeight(isSegwitTx: boolean, signatures: PartialSig[] | 'DANGEROUSLY_USE_FAKE_SIGNATURES'): number;
439
481
  /**
@@ -1,5 +1,5 @@
1
1
  "use strict";
2
- // Copyright (c) 2023 Jose-Luis Landabaso - https://bitcoinerlab.com
2
+ // Copyright (c) 2025 Jose-Luis Landabaso - https://bitcoinerlab.com
3
3
  // Distributed under the MIT software license
4
4
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
5
5
  if (k2 === undefined) k2 = k;
@@ -148,18 +148,31 @@ function evaluate({ descriptor, checksumRequired, index }) {
148
148
  * [@bitcoinerlab/secp256k1](https://github.com/bitcoinerlab/secp256k1).
149
149
  */
150
150
  function DescriptorsFactory(ecc) {
151
- var _Output_instances, _Output_payment, _Output_preimages, _Output_signersPubKeys, _Output_miniscript, _Output_witnessScript, _Output_redeemScript, _Output_isSegwit, _Output_expandedExpression, _Output_expandedMiniscript, _Output_expansionMap, _Output_network, _Output_getTimeConstraints, _Output_assertPsbtInput;
151
+ var _Output_instances, _Output_payment, _Output_preimages, _Output_signersPubKeys, _Output_miniscript, _Output_witnessScript, _Output_redeemScript, _Output_isSegwit, _Output_isTaproot, _Output_expandedExpression, _Output_expandedMiniscript, _Output_expansionMap, _Output_network, _Output_getTimeConstraints, _Output_assertPsbtInput;
152
+ (0, bitcoinjs_lib_1.initEccLib)(ecc); //Taproot requires initEccLib
152
153
  const BIP32 = (0, bip32_1.BIP32Factory)(ecc);
153
154
  const ECPair = (0, ecpair_1.ECPairFactory)(ecc);
154
- const signatureValidator = (pubkey, msghash, signature) => ECPair.fromPublicKey(pubkey).verify(msghash, signature);
155
+ const signatureValidator = (pubkey, msghash, signature) => {
156
+ if (pubkey.length === 32) {
157
+ //x-only
158
+ if (!ecc.verifySchnorr) {
159
+ throw new Error('TinySecp256k1Interface is not initialized properly: verifySchnorr is missing.');
160
+ }
161
+ return ecc.verifySchnorr(msghash, pubkey, signature);
162
+ }
163
+ else {
164
+ return ECPair.fromPublicKey(pubkey).verify(msghash, signature);
165
+ }
166
+ };
155
167
  /**
156
168
  * Takes a string key expression (xpub, xprv, pubkey or wif) and parses it
157
169
  */
158
- const parseKeyExpression = ({ keyExpression, isSegwit, network = bitcoinjs_lib_1.networks.bitcoin }) => {
170
+ const parseKeyExpression = ({ keyExpression, isSegwit, isTaproot, network = bitcoinjs_lib_1.networks.bitcoin }) => {
159
171
  return (0, keyExpressions_1.parseKeyExpression)({
160
172
  keyExpression,
161
173
  network,
162
174
  ...(typeof isSegwit === 'boolean' ? { isSegwit } : {}),
175
+ ...(typeof isTaproot === 'boolean' ? { isTaproot } : {}),
163
176
  ECPair,
164
177
  BIP32
165
178
  });
@@ -179,6 +192,7 @@ function DescriptorsFactory(ecc) {
179
192
  let miniscript;
180
193
  let expansionMap;
181
194
  let isSegwit;
195
+ let isTaproot;
182
196
  let expandedMiniscript;
183
197
  let payment;
184
198
  let witnessScript;
@@ -212,27 +226,32 @@ function DescriptorsFactory(ecc) {
212
226
  try {
213
227
  payment = p2pkh({ output, network });
214
228
  isSegwit = false;
229
+ isTaproot = false;
215
230
  }
216
231
  catch (e) { }
217
232
  try {
218
233
  payment = p2sh({ output, network });
219
234
  // It assumes that an addr(SH_ADDRESS) is always a add(SH_WPKH) address
220
235
  isSegwit = true;
236
+ isTaproot = false;
221
237
  }
222
238
  catch (e) { }
223
239
  try {
224
240
  payment = p2wpkh({ output, network });
225
241
  isSegwit = true;
242
+ isTaproot = false;
226
243
  }
227
244
  catch (e) { }
228
245
  try {
229
246
  payment = p2wsh({ output, network });
230
247
  isSegwit = true;
248
+ isTaproot = false;
231
249
  }
232
250
  catch (e) { }
233
251
  try {
234
252
  payment = p2tr({ output, network });
235
253
  isSegwit = true;
254
+ isTaproot = true;
236
255
  }
237
256
  catch (e) { }
238
257
  if (!payment) {
@@ -242,7 +261,8 @@ function DescriptorsFactory(ecc) {
242
261
  //pk(KEY)
243
262
  else if (canonicalExpression.match(RE.rePkAnchored)) {
244
263
  isSegwit = false;
245
- const keyExpression = canonicalExpression.match(RE.reKeyExp)?.[0];
264
+ isTaproot = false;
265
+ const keyExpression = canonicalExpression.match(RE.reNonSegwitKeyExp)?.[0];
246
266
  if (!keyExpression)
247
267
  throw new Error(`Error: keyExpression could not me extracted`);
248
268
  if (canonicalExpression !== `pk(${keyExpression})`)
@@ -261,7 +281,8 @@ function DescriptorsFactory(ecc) {
261
281
  //pkh(KEY) - legacy
262
282
  else if (canonicalExpression.match(RE.rePkhAnchored)) {
263
283
  isSegwit = false;
264
- const keyExpression = canonicalExpression.match(RE.reKeyExp)?.[0];
284
+ isTaproot = false;
285
+ const keyExpression = canonicalExpression.match(RE.reNonSegwitKeyExp)?.[0];
265
286
  if (!keyExpression)
266
287
  throw new Error(`Error: keyExpression could not me extracted`);
267
288
  if (canonicalExpression !== `pkh(${keyExpression})`)
@@ -279,7 +300,8 @@ function DescriptorsFactory(ecc) {
279
300
  //sh(wpkh(KEY)) - nested segwit
280
301
  else if (canonicalExpression.match(RE.reShWpkhAnchored)) {
281
302
  isSegwit = true;
282
- const keyExpression = canonicalExpression.match(RE.reKeyExp)?.[0];
303
+ isTaproot = false;
304
+ const keyExpression = canonicalExpression.match(RE.reSegwitKeyExp)?.[0];
283
305
  if (!keyExpression)
284
306
  throw new Error(`Error: keyExpression could not me extracted`);
285
307
  if (canonicalExpression !== `sh(wpkh(${keyExpression}))`)
@@ -300,7 +322,8 @@ function DescriptorsFactory(ecc) {
300
322
  //wpkh(KEY) - native segwit
301
323
  else if (canonicalExpression.match(RE.reWpkhAnchored)) {
302
324
  isSegwit = true;
303
- const keyExpression = canonicalExpression.match(RE.reKeyExp)?.[0];
325
+ isTaproot = false;
326
+ const keyExpression = canonicalExpression.match(RE.reSegwitKeyExp)?.[0];
304
327
  if (!keyExpression)
305
328
  throw new Error(`Error: keyExpression could not me extracted`);
306
329
  if (canonicalExpression !== `wpkh(${keyExpression})`)
@@ -318,6 +341,7 @@ function DescriptorsFactory(ecc) {
318
341
  //sh(wsh(miniscript))
319
342
  else if (canonicalExpression.match(RE.reShWshMiniscriptAnchored)) {
320
343
  isSegwit = true;
344
+ isTaproot = false;
321
345
  miniscript = canonicalExpression.match(RE.reShWshMiniscriptAnchored)?.[1]; //[1]-> whatever is found sh(wsh(->HERE<-))
322
346
  if (!miniscript)
323
347
  throw new Error(`Error: could not get miniscript in ${descriptor}`);
@@ -351,6 +375,7 @@ function DescriptorsFactory(ecc) {
351
375
  //isSegwit false because we know it's a P2SH of a miniscript and not a
352
376
  //P2SH that embeds a witness payment.
353
377
  isSegwit = false;
378
+ isTaproot = false;
354
379
  miniscript = canonicalExpression.match(RE.reShMiniscriptAnchored)?.[1]; //[1]-> whatever is found sh(->HERE<-)
355
380
  if (!miniscript)
356
381
  throw new Error(`Error: could not get miniscript in ${descriptor}`);
@@ -383,6 +408,7 @@ function DescriptorsFactory(ecc) {
383
408
  //wsh(miniscript)
384
409
  else if (canonicalExpression.match(RE.reWshMiniscriptAnchored)) {
385
410
  isSegwit = true;
411
+ isTaproot = false;
386
412
  miniscript = canonicalExpression.match(RE.reWshMiniscriptAnchored)?.[1]; //[1]-> whatever is found wsh(->HERE<-)
387
413
  if (!miniscript)
388
414
  throw new Error(`Error: could not get miniscript in ${descriptor}`);
@@ -405,6 +431,36 @@ function DescriptorsFactory(ecc) {
405
431
  payment = p2wsh({ redeem: { output: script, network }, network });
406
432
  }
407
433
  }
434
+ //tr(KEY) - taproot - TODO: tr(KEY,TREE) not yet supported
435
+ else if (canonicalExpression.match(RE.reTrSingleKeyAnchored)) {
436
+ isSegwit = true;
437
+ isTaproot = true;
438
+ const keyExpression = canonicalExpression.match(RE.reTaprootKeyExp)?.[0];
439
+ if (!keyExpression)
440
+ throw new Error(`Error: keyExpression could not me extracted`);
441
+ if (canonicalExpression !== `tr(${keyExpression})`)
442
+ throw new Error(`Error: invalid expression ${expression}`);
443
+ expandedExpression = 'tr(@0)';
444
+ const pKE = parseKeyExpression({
445
+ keyExpression,
446
+ network,
447
+ isSegwit,
448
+ isTaproot
449
+ });
450
+ expansionMap = { '@0': pKE };
451
+ if (!isCanonicalRanged) {
452
+ const pubkey = pKE.pubkey;
453
+ if (!pubkey)
454
+ throw new Error(`Error: could not extract a pubkey from ${expression}`);
455
+ payment = p2tr({ internalPubkey: pubkey, network });
456
+ //console.log('TRACE', {
457
+ // pKE,
458
+ // pubkey: pubkey?.toString('hex'),
459
+ // payment,
460
+ // paymentPubKey: payment.pubkey
461
+ //});
462
+ }
463
+ }
408
464
  else {
409
465
  throw new Error(`Error: Could not parse descriptor ${descriptor}`);
410
466
  }
@@ -414,6 +470,7 @@ function DescriptorsFactory(ecc) {
414
470
  ...(miniscript !== undefined ? { miniscript } : {}),
415
471
  ...(expansionMap !== undefined ? { expansionMap } : {}),
416
472
  ...(isSegwit !== undefined ? { isSegwit } : {}),
473
+ ...(isTaproot !== undefined ? { isTaproot } : {}),
417
474
  ...(expandedMiniscript !== undefined ? { expandedMiniscript } : {}),
418
475
  ...(redeemScript !== undefined ? { redeemScript } : {}),
419
476
  ...(witnessScript !== undefined ? { witnessScript } : {}),
@@ -432,6 +489,7 @@ function DescriptorsFactory(ecc) {
432
489
  return (0, miniscript_1.expandMiniscript)({
433
490
  miniscript,
434
491
  isSegwit,
492
+ isTaproot: false,
435
493
  network,
436
494
  BIP32,
437
495
  ECPair
@@ -459,6 +517,7 @@ function DescriptorsFactory(ecc) {
459
517
  //isSegwit true if witnesses are needed to the spend coins sent to this descriptor.
460
518
  //may be unset because we may get addr(P2SH) which we don't know if they have segwit.
461
519
  _Output_isSegwit.set(this, void 0);
520
+ _Output_isTaproot.set(this, void 0);
462
521
  _Output_expandedExpression.set(this, void 0);
463
522
  _Output_expandedMiniscript.set(this, void 0);
464
523
  _Output_expansionMap.set(this, void 0);
@@ -487,6 +546,8 @@ function DescriptorsFactory(ecc) {
487
546
  __classPrivateFieldSet(this, _Output_expansionMap, expandedResult.expansionMap, "f");
488
547
  if (expandedResult.isSegwit !== undefined)
489
548
  __classPrivateFieldSet(this, _Output_isSegwit, expandedResult.isSegwit, "f");
549
+ if (expandedResult.isTaproot !== undefined)
550
+ __classPrivateFieldSet(this, _Output_isTaproot, expandedResult.isTaproot, "f");
490
551
  if (expandedResult.expandedMiniscript !== undefined)
491
552
  __classPrivateFieldSet(this, _Output_expandedMiniscript, expandedResult.expandedMiniscript, "f");
492
553
  if (expandedResult.redeemScript !== undefined)
@@ -671,8 +732,24 @@ function DescriptorsFactory(ecc) {
671
732
  return __classPrivateFieldGet(this, _Output_isSegwit, "f");
672
733
  }
673
734
  /**
674
- * Returns the tuple: `{ isPKH: boolean; isWPKH: boolean; isSH: boolean; }`
675
- * for this Output.
735
+ * Whether this `Output` is Taproot.
736
+ */
737
+ isTaproot() {
738
+ return __classPrivateFieldGet(this, _Output_isTaproot, "f");
739
+ }
740
+ /**
741
+ * Attempts to determine the type of output script by testing it against
742
+ * various payment types.
743
+ *
744
+ * This method tries to identify if the output is one of the following types:
745
+ * - P2SH (Pay to Script Hash)
746
+ * - P2WSH (Pay to Witness Script Hash)
747
+ * - P2WPKH (Pay to Witness Public Key Hash)
748
+ * - P2PKH (Pay to Public Key Hash)
749
+ * - P2TR (Pay to Taproot)
750
+ *
751
+ * @returns An object { isPKH: boolean; isWPKH: boolean; isSH: boolean; isWSH: boolean; isTR: boolean;}
752
+ * with boolean properties indicating the detected output type
676
753
  */
677
754
  guessOutput() {
678
755
  function guessSH(output) {
@@ -684,6 +761,15 @@ function DescriptorsFactory(ecc) {
684
761
  return false;
685
762
  }
686
763
  }
764
+ function guessWSH(output) {
765
+ try {
766
+ bitcoinjs_lib_1.payments.p2wsh({ output });
767
+ return true;
768
+ }
769
+ catch (err) {
770
+ return false;
771
+ }
772
+ }
687
773
  function guessWPKH(output) {
688
774
  try {
689
775
  bitcoinjs_lib_1.payments.p2wpkh({ output });
@@ -702,18 +788,30 @@ function DescriptorsFactory(ecc) {
702
788
  return false;
703
789
  }
704
790
  }
791
+ function guessTR(output) {
792
+ try {
793
+ bitcoinjs_lib_1.payments.p2tr({ output });
794
+ return true;
795
+ }
796
+ catch (err) {
797
+ return false;
798
+ }
799
+ }
705
800
  const isPKH = guessPKH(this.getScriptPubKey());
706
801
  const isWPKH = guessWPKH(this.getScriptPubKey());
707
802
  const isSH = guessSH(this.getScriptPubKey());
708
- if ([isPKH, isWPKH, isSH].filter(Boolean).length > 1)
803
+ const isWSH = guessWSH(this.getScriptPubKey());
804
+ const isTR = guessTR(this.getScriptPubKey());
805
+ if ([isPKH, isWPKH, isSH, isWSH, isTR].filter(Boolean).length > 1)
709
806
  throw new Error('Cannot have multiple output types.');
710
- return { isPKH, isWPKH, isSH };
807
+ return { isPKH, isWPKH, isSH, isWSH, isTR };
711
808
  }
712
809
  // References for inputWeight & outputWeight:
713
810
  // https://gist.github.com/junderw/b43af3253ea5865ed52cb51c200ac19c
714
811
  // https://bitcoinops.org/en/tools/calc-size/
715
812
  // Look for byteLength: https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/ts_src/transaction.ts
716
813
  // https://github.com/bitcoinjs/coinselect/blob/master/utils.js
814
+ // https://bitcoin.stackexchange.com/questions/111395/what-is-the-weight-of-a-p2tr-input
717
815
  /**
718
816
  * Computes the Weight Unit contributions of this Output as if it were the
719
817
  * input in a tx.
@@ -721,8 +819,12 @@ function DescriptorsFactory(ecc) {
721
819
  * *NOTE:* When the descriptor in an input is `addr(address)`, it is assumed
722
820
  * that any `addr(SH_TYPE_ADDRESS)` is in fact a Segwit `SH_WPKH`
723
821
  * (Script Hash-Witness Public Key Hash).
822
+ *, Also any `addr(SINGLE_KEY_ADDRESS)` * is assumed to be a single key Taproot
823
+ * address (like those defined in BIP86).
724
824
  * For inputs using arbitrary scripts (not standard addresses),
725
- * use a descriptor in the format `sh(MINISCRIPT)`.
825
+ * use a descriptor in the format `sh(MINISCRIPT)` or `tr(MINISCRIPT)`.
826
+ * Note however that tr(MINISCRIPT) is not yet supported for non-single-key
827
+ * expressions.
726
828
  */
727
829
  inputWeight(
728
830
  /**
@@ -737,21 +839,24 @@ function DescriptorsFactory(ecc) {
737
839
  /*
738
840
  * Array of `PartialSig`. Each `PartialSig` includes
739
841
  * a public key and its corresponding signature. This parameter
740
- * enables the accurate calculation of signature sizes.
741
- * Pass 'DANGEROUSLY_USE_FAKE_SIGNATURES' to assume 72 bytes in length.
842
+ * enables the accurate calculation of signature sizes for ECDSA.
843
+ * Pass 'DANGEROUSLY_USE_FAKE_SIGNATURES' to assume 72 bytes in length
844
+ * for ECDSA.
845
+ * Schnorr signatures are always 64 bytes.
742
846
  * Mainly used for testing.
743
847
  */
744
848
  signatures) {
745
849
  if (this.isSegwit() && !isSegwitTx)
746
850
  throw new Error(`a tx is segwit if at least one input is segwit`);
747
- const errorMsg = 'Input type not implemented. Currently supported: pkh(KEY), wpkh(KEY), \
748
- sh(wpkh(KEY)), sh(wsh(MINISCRIPT)), sh(MINISCRIPT), wsh(MINISCRIPT), \
749
- addr(PKH_ADDRESS), addr(WPKH_ADDRESS), addr(SH_WPKH_ADDRESS).';
750
851
  //expand any miniscript-based descriptor. If not miniscript-based, then it's
751
852
  //an addr() descriptor. For those, we can only guess their type.
752
853
  const expansion = this.expand().expandedExpression;
753
- const { isPKH, isWPKH, isSH } = this.guessOutput();
754
- if (!expansion && !isPKH && !isWPKH && !isSH)
854
+ const { isPKH, isWPKH, isSH, isTR } = this.guessOutput();
855
+ const errorMsg = `Input type not implemented. Currently supported: pkh(KEY), wpkh(KEY), tr(KEY), \
856
+ sh(wpkh(KEY)), sh(wsh(MINISCRIPT)), sh(MINISCRIPT), wsh(MINISCRIPT), \
857
+ addr(PKH_ADDRESS), addr(WPKH_ADDRESS), addr(SH_WPKH_ADDRESS), addr(SINGLE_KEY_ADDRESS). \
858
+ expansion=${expansion}, isPKH=${isPKH}, isWPKH=${isWPKH}, isSH=${isSH}, isTR=${isTR}.`;
859
+ if (!expansion && !isPKH && !isWPKH && !isSH && !isTR)
755
860
  throw new Error(errorMsg);
756
861
  const firstSignature = signatures && typeof signatures[0] === 'object'
757
862
  ? signatures[0]
@@ -842,6 +947,17 @@ function DescriptorsFactory(ecc) {
842
947
  4 * (40 + varSliceSize(payment.input)) +
843
948
  //Segwit
844
949
  vectorSize(payment.witness));
950
+ //when addr(SINGLE_KEY_ADDRESS) or tr(KEY) (single key):
951
+ //TODO: only single-key taproot outputs currently supported
952
+ }
953
+ else if (isTR && (!expansion || expansion === 'tr(@0)')) {
954
+ if (!isSegwitTx)
955
+ throw new Error('Should be SegwitTx');
956
+ return (
957
+ // Non-segwit: (txid:32) + (vout:4) + (sequence:4) + (script_len:1)
958
+ 41 * 4 +
959
+ // Segwit: (push_count:1) + (sig_length(1) + schnorr_sig(64): 65)
960
+ (1 + 65));
845
961
  }
846
962
  else {
847
963
  throw new Error(errorMsg);
@@ -852,31 +968,30 @@ function DescriptorsFactory(ecc) {
852
968
  * output in a tx.
853
969
  */
854
970
  outputWeight() {
855
- const errorMsg = 'Output type not implemented. Currently supported: pkh(KEY), wpkh(KEY), \
856
- sh(ANYTHING), wsh(ANYTHING), addr(PKH_ADDRESS), addr(WPKH_ADDRESS), \
857
- addr(SH_WPKH_ADDRESS)';
858
971
  //expand any miniscript-based descriptor. If not miniscript-based, then it's
859
972
  //an addr() descriptor. For those, we can only guess their type.
860
- const expansion = this.expand().expandedExpression;
861
- const { isPKH, isWPKH, isSH } = this.guessOutput();
862
- if (!expansion && !isPKH && !isWPKH && !isSH)
863
- throw new Error(errorMsg);
864
- if (expansion ? expansion.startsWith('pkh(') : isPKH) {
973
+ const { isPKH, isWPKH, isSH, isWSH, isTR } = this.guessOutput();
974
+ const errorMsg = `Output type not implemented. Currently supported: pkh=${isPKH}, wpkh=${isWPKH}, tr=${isTR}, sh=${isSH} and wsh=${isWSH}.`;
975
+ if (isPKH) {
865
976
  // (p2pkh:26) + (amount:8)
866
977
  return 34 * 4;
867
978
  }
868
- else if (expansion ? expansion.startsWith('wpkh(') : isWPKH) {
979
+ else if (isWPKH) {
869
980
  // (p2wpkh:23) + (amount:8)
870
981
  return 31 * 4;
871
982
  }
872
- else if (expansion ? expansion.startsWith('sh(') : isSH) {
983
+ else if (isSH) {
873
984
  // (p2sh:24) + (amount:8)
874
985
  return 32 * 4;
875
986
  }
876
- else if (expansion?.startsWith('wsh(')) {
987
+ else if (isWSH) {
877
988
  // (p2wsh:35) + (amount:8)
878
989
  return 43 * 4;
879
990
  }
991
+ else if (isTR) {
992
+ // (script_pubKey_length:1) + (p2t2(OP_1 OP_PUSH32 <schnorr_public_key>):34) + (amount:8)
993
+ return 43 * 4;
994
+ }
880
995
  else {
881
996
  throw new Error(errorMsg);
882
997
  }
@@ -930,12 +1045,20 @@ function DescriptorsFactory(ecc) {
930
1045
  //This should only happen when using addr() expressions
931
1046
  throw new Error(`Error: could not determine whether this is a segwit descriptor`);
932
1047
  }
1048
+ const isTaproot = this.isTaproot();
1049
+ if (isTaproot === undefined) {
1050
+ //This should only happen when using addr() expressions
1051
+ throw new Error(`Error: could not determine whether this is a taproot descriptor`);
1052
+ }
933
1053
  const index = (0, psbt_1.updatePsbt)({
934
1054
  psbt,
935
1055
  vout,
936
1056
  ...(txHex !== undefined ? { txHex } : {}),
937
1057
  ...(txId !== undefined ? { txId } : {}),
938
1058
  ...(value !== undefined ? { value } : {}),
1059
+ ...(isTaproot
1060
+ ? { tapInternalKey: this.getPayment().internalPubkey }
1061
+ : {}),
939
1062
  sequence: this.getSequence(),
940
1063
  locktime: this.getLockTime(),
941
1064
  keysInfo: __classPrivateFieldGet(this, _Output_expansionMap, "f") ? Object.values(__classPrivateFieldGet(this, _Output_expansionMap, "f")) : [],
@@ -1000,15 +1123,15 @@ function DescriptorsFactory(ecc) {
1000
1123
  //same sequences, ... The descriptor does not store the hash of the previous
1001
1124
  //transaction since it is a general Descriptor object. Indices must be kept
1002
1125
  //out of the scope of this class and then passed.
1003
- const signatures = psbt.data.inputs[index]?.partialSig;
1004
- if (!signatures)
1005
- throw new Error(`Error: cannot finalize without signatures`);
1006
1126
  __classPrivateFieldGet(this, _Output_instances, "m", _Output_assertPsbtInput).call(this, { index, psbt });
1007
1127
  if (!__classPrivateFieldGet(this, _Output_miniscript, "f")) {
1008
1128
  //Use standard finalizers
1009
1129
  psbt.finalizeInput(index);
1010
1130
  }
1011
1131
  else {
1132
+ const signatures = psbt.data.inputs[index]?.partialSig;
1133
+ if (!signatures)
1134
+ throw new Error(`Error: cannot finalize without signatures`);
1012
1135
  const scriptSatisfaction = this.getScriptSatisfaction(signatures);
1013
1136
  psbt.finalizeInput(index, (0, psbt_1.finalScriptsFuncFactory)(scriptSatisfaction, __classPrivateFieldGet(this, _Output_network, "f")));
1014
1137
  }
@@ -1034,7 +1157,7 @@ function DescriptorsFactory(ecc) {
1034
1157
  };
1035
1158
  }
1036
1159
  }
1037
- _Output_payment = new WeakMap(), _Output_preimages = new WeakMap(), _Output_signersPubKeys = new WeakMap(), _Output_miniscript = new WeakMap(), _Output_witnessScript = new WeakMap(), _Output_redeemScript = new WeakMap(), _Output_isSegwit = new WeakMap(), _Output_expandedExpression = new WeakMap(), _Output_expandedMiniscript = new WeakMap(), _Output_expansionMap = new WeakMap(), _Output_network = new WeakMap(), _Output_instances = new WeakSet(), _Output_getTimeConstraints = function _Output_getTimeConstraints() {
1160
+ _Output_payment = new WeakMap(), _Output_preimages = new WeakMap(), _Output_signersPubKeys = new WeakMap(), _Output_miniscript = new WeakMap(), _Output_witnessScript = new WeakMap(), _Output_redeemScript = new WeakMap(), _Output_isSegwit = new WeakMap(), _Output_isTaproot = new WeakMap(), _Output_expandedExpression = new WeakMap(), _Output_expandedMiniscript = new WeakMap(), _Output_expansionMap = new WeakMap(), _Output_network = new WeakMap(), _Output_instances = new WeakSet(), _Output_getTimeConstraints = function _Output_getTimeConstraints() {
1038
1161
  const miniscript = __classPrivateFieldGet(this, _Output_miniscript, "f");
1039
1162
  const preimages = __classPrivateFieldGet(this, _Output_preimages, "f");
1040
1163
  const expandedMiniscript = __classPrivateFieldGet(this, _Output_expandedMiniscript, "f");
@@ -19,7 +19,7 @@ import { LedgerState, LedgerManager } from './ledger';
19
19
  * }
20
20
  * ```
21
21
  */
22
- export declare function parseKeyExpression({ keyExpression, isSegwit, ECPair, BIP32, network }: {
22
+ export declare function parseKeyExpression({ keyExpression, isSegwit, isTaproot, ECPair, BIP32, network }: {
23
23
  keyExpression: string;
24
24
  /** @default networks.bitcoin */
25
25
  network?: Network;
@@ -29,6 +29,13 @@ export declare function parseKeyExpression({ keyExpression, isSegwit, ECPair, BI
29
29
  * expression) is compressed (33 bytes).
30
30
  */
31
31
  isSegwit?: boolean;
32
+ /**
33
+ * Indicates if this key expression belongs to a Taproot output. For Taproot,
34
+ * the key must be represented as an x-only public key (32 bytes).
35
+ * If a 33-byte compressed pubkey is derived, it is converted to its x-only
36
+ * representation.
37
+ */
38
+ isTaproot?: boolean;
32
39
  ECPair: ECPairAPI;
33
40
  BIP32: BIP32API;
34
41
  }): KeyInfo;
@@ -59,7 +59,7 @@ const derivePath = (node, path) => {
59
59
  * }
60
60
  * ```
61
61
  */
62
- function parseKeyExpression({ keyExpression, isSegwit, ECPair, BIP32, network = bitcoinjs_lib_1.networks.bitcoin }) {
62
+ function parseKeyExpression({ keyExpression, isSegwit, isTaproot, ECPair, BIP32, network = bitcoinjs_lib_1.networks.bitcoin }) {
63
63
  let pubkey; //won't be computed for ranged keyExpressions
64
64
  let ecpair;
65
65
  let bip32;
@@ -68,8 +68,18 @@ function parseKeyExpression({ keyExpression, isSegwit, ECPair, BIP32, network =
68
68
  let keyPath;
69
69
  let path;
70
70
  const isRanged = keyExpression.indexOf('*') !== -1;
71
+ const reKeyExp = isTaproot
72
+ ? RE.reTaprootKeyExp
73
+ : isSegwit
74
+ ? RE.reSegwitKeyExp
75
+ : RE.reNonSegwitKeyExp;
76
+ const rePubKey = isTaproot
77
+ ? RE.reTaprootPubKey
78
+ : isSegwit
79
+ ? RE.reSegwitPubKey
80
+ : RE.reNonSegwitPubKey;
71
81
  //Validate the keyExpression:
72
- const keyExpressions = keyExpression.match(RE.reKeyExp);
82
+ const keyExpressions = keyExpression.match(reKeyExp);
73
83
  if (keyExpressions === null || keyExpressions[0] !== keyExpression) {
74
84
  throw new Error(`Error: expected a keyExpression but got ${keyExpression}`);
75
85
  }
@@ -93,8 +103,11 @@ function parseKeyExpression({ keyExpression, isSegwit, ECPair, BIP32, network =
93
103
  const actualKey = keyExpression.replace(reOriginAnchoredStart, '');
94
104
  let mPubKey, mWIF, mXpubKey, mXprvKey;
95
105
  //match pubkey:
96
- if ((mPubKey = actualKey.match(RE.anchorStartAndEnd(RE.rePubKey))) !== null) {
106
+ if ((mPubKey = actualKey.match(RE.anchorStartAndEnd(rePubKey))) !== null) {
97
107
  pubkey = Buffer.from(mPubKey[0], 'hex');
108
+ if (isTaproot && pubkey.length === 32)
109
+ //convert the xonly point to a compressed point assuming even parity
110
+ pubkey = Buffer.concat([Buffer.from([0x02]), pubkey]);
98
111
  ecpair = ECPair.fromPublicKey(pubkey, { network });
99
112
  //Validate the pubkey (compressed or uncompressed)
100
113
  if (!ECPair.isPoint(pubkey) ||
@@ -161,6 +174,9 @@ function parseKeyExpression({ keyExpression, isSegwit, ECPair, BIP32, network =
161
174
  if (originPath || keyPath) {
162
175
  path = `m${originPath ?? ''}${keyPath ?? ''}`;
163
176
  }
177
+ if (pubkey !== undefined && isTaproot && pubkey.length === 33)
178
+ // If we get a 33-byte compressed key, drop the first byte.
179
+ pubkey = pubkey.slice(1, 33);
164
180
  return {
165
181
  keyExpression,
166
182
  ...(pubkey !== undefined ? { pubkey } : {}),
@@ -11,9 +11,10 @@ import type { Preimage, TimeConstraints, ExpansionMap } from './types';
11
11
  * satisfied with satisfier.
12
12
  * Also compute pubkeys from descriptors to use them later.
13
13
  */
14
- export declare function expandMiniscript({ miniscript, isSegwit, network, ECPair, BIP32 }: {
14
+ export declare function expandMiniscript({ miniscript, isSegwit, isTaproot, network, ECPair, BIP32 }: {
15
15
  miniscript: string;
16
16
  isSegwit: boolean;
17
+ isTaproot: boolean;
17
18
  network?: Network;
18
19
  ECPair: ECPairAPI;
19
20
  BIP32: BIP32API;
@@ -37,9 +37,16 @@ const miniscript_1 = require("@bitcoinerlab/miniscript");
37
37
  * satisfied with satisfier.
38
38
  * Also compute pubkeys from descriptors to use them later.
39
39
  */
40
- function expandMiniscript({ miniscript, isSegwit, network = bitcoinjs_lib_1.networks.bitcoin, ECPair, BIP32 }) {
40
+ function expandMiniscript({ miniscript, isSegwit, isTaproot, network = bitcoinjs_lib_1.networks.bitcoin, ECPair, BIP32 }) {
41
+ if (isTaproot)
42
+ throw new Error('Taproot miniscript not yet supported.');
43
+ const reKeyExp = isTaproot
44
+ ? RE.reTaprootKeyExp
45
+ : isSegwit
46
+ ? RE.reSegwitKeyExp
47
+ : RE.reNonSegwitKeyExp;
41
48
  const expansionMap = {};
42
- const expandedMiniscript = miniscript.replace(RegExp(RE.reKeyExp, 'g'), (keyExpression) => {
49
+ const expandedMiniscript = miniscript.replace(RegExp(reKeyExp, 'g'), (keyExpression) => {
43
50
  const key = '@' + Object.keys(expansionMap).length;
44
51
  expansionMap[key] = (0, keyExpressions_1.parseKeyExpression)({
45
52
  keyExpression,
package/dist/psbt.d.ts CHANGED
@@ -21,7 +21,7 @@ export declare function finalScriptsFuncFactory(scriptSatisfaction: Buffer, netw
21
21
  /**
22
22
  * Important: Read comments on descriptor.updatePsbt regarding not passing txHex
23
23
  */
24
- export declare function updatePsbt({ psbt, vout, txHex, txId, value, sequence, locktime, keysInfo, scriptPubKey, isSegwit, witnessScript, redeemScript, rbf }: {
24
+ export declare function updatePsbt({ psbt, vout, txHex, txId, value, sequence, locktime, keysInfo, scriptPubKey, isSegwit, tapInternalKey, witnessScript, redeemScript, rbf }: {
25
25
  psbt: Psbt;
26
26
  vout: number;
27
27
  txHex?: string;
@@ -32,6 +32,8 @@ export declare function updatePsbt({ psbt, vout, txHex, txId, value, sequence, l
32
32
  keysInfo: KeyInfo[];
33
33
  scriptPubKey: Buffer;
34
34
  isSegwit: boolean;
35
+ /** for taproot **/
36
+ tapInternalKey?: Buffer | undefined;
35
37
  witnessScript: Buffer | undefined;
36
38
  redeemScript: Buffer | undefined;
37
39
  rbf: boolean;
package/dist/psbt.js CHANGED
@@ -1,5 +1,5 @@
1
1
  "use strict";
2
- // Copyright (c) 2023 Jose-Luis Landabaso - https://bitcoinerlab.com
2
+ // Copyright (c) 2025 Jose-Luis Landabaso - https://bitcoinerlab.com
3
3
  // Distributed under the MIT software license
4
4
  Object.defineProperty(exports, "__esModule", { value: true });
5
5
  exports.updatePsbt = exports.finalScriptsFuncFactory = void 0;
@@ -86,7 +86,7 @@ exports.finalScriptsFuncFactory = finalScriptsFuncFactory;
86
86
  /**
87
87
  * Important: Read comments on descriptor.updatePsbt regarding not passing txHex
88
88
  */
89
- function updatePsbt({ psbt, vout, txHex, txId, value, sequence, locktime, keysInfo, scriptPubKey, isSegwit, witnessScript, redeemScript, rbf }) {
89
+ function updatePsbt({ psbt, vout, txHex, txId, value, sequence, locktime, keysInfo, scriptPubKey, isSegwit, tapInternalKey, witnessScript, redeemScript, rbf }) {
90
90
  //Some data-sanity checks:
91
91
  if (sequence !== undefined && rbf && sequence > 0xfffffffd)
92
92
  throw new Error(`Error: incompatible sequence and rbf settings`);
@@ -155,20 +155,45 @@ function updatePsbt({ psbt, vout, txHex, txId, value, sequence, locktime, keysIn
155
155
  if (txHex !== undefined) {
156
156
  input.nonWitnessUtxo = bitcoinjs_lib_1.Transaction.fromHex(txHex).toBuffer();
157
157
  }
158
- const bip32Derivation = keysInfo
159
- .filter((keyInfo) => keyInfo.pubkey && keyInfo.masterFingerprint && keyInfo.path)
160
- .map((keyInfo) => {
161
- const pubkey = keyInfo.pubkey;
162
- if (!pubkey)
163
- throw new Error(`key ${keyInfo.keyExpression} missing pubkey`);
164
- return {
165
- masterFingerprint: keyInfo.masterFingerprint,
166
- pubkey,
167
- path: keyInfo.path
168
- };
169
- });
170
- if (bip32Derivation.length)
171
- input.bip32Derivation = bip32Derivation;
158
+ if (tapInternalKey) {
159
+ //Taproot
160
+ const tapBip32Derivation = keysInfo
161
+ .filter((keyInfo) => keyInfo.pubkey && keyInfo.masterFingerprint && keyInfo.path)
162
+ .map((keyInfo) => {
163
+ const pubkey = keyInfo.pubkey;
164
+ if (!pubkey)
165
+ throw new Error(`key ${keyInfo.keyExpression} missing pubkey`);
166
+ return {
167
+ masterFingerprint: keyInfo.masterFingerprint,
168
+ pubkey,
169
+ path: keyInfo.path,
170
+ leafHashes: [] // TODO: Empty array for tr(KEY) taproot key spend - this is the only type currently supported
171
+ };
172
+ });
173
+ if (tapBip32Derivation.length)
174
+ input.tapBip32Derivation = tapBip32Derivation;
175
+ input.tapInternalKey = tapInternalKey;
176
+ //TODO: currently only single-key taproot supported.
177
+ //https://github.com/bitcoinjs/bitcoinjs-lib/blob/6ba8bb3ce20ba533eeaba6939cfc2891576d9969/test/integration/taproot.spec.ts#L243
178
+ if (tapBip32Derivation.length > 1)
179
+ throw new Error('Only single key taproot inputs are currently supported');
180
+ }
181
+ else {
182
+ const bip32Derivation = keysInfo
183
+ .filter((keyInfo) => keyInfo.pubkey && keyInfo.masterFingerprint && keyInfo.path)
184
+ .map((keyInfo) => {
185
+ const pubkey = keyInfo.pubkey;
186
+ if (!pubkey)
187
+ throw new Error(`key ${keyInfo.keyExpression} missing pubkey`);
188
+ return {
189
+ masterFingerprint: keyInfo.masterFingerprint,
190
+ pubkey,
191
+ path: keyInfo.path
192
+ };
193
+ });
194
+ if (bip32Derivation.length)
195
+ input.bip32Derivation = bip32Derivation;
196
+ }
172
197
  if (isSegwit && txHex !== undefined) {
173
198
  //There's no need to put both witnessUtxo and nonWitnessUtxo
174
199
  input.witnessUtxo = { script: scriptPubKey, value };
package/dist/re.d.ts CHANGED
@@ -2,20 +2,25 @@ export declare const reOriginPath: string;
2
2
  export declare const reMasterFingerprint: string;
3
3
  export declare const reOrigin: string;
4
4
  export declare const reChecksum: string;
5
- export declare const rePubKey: string;
5
+ export declare const reNonSegwitPubKey: string;
6
+ export declare const reSegwitPubKey: string;
7
+ export declare const reTaprootPubKey: string;
6
8
  export declare const reWIF: string;
7
9
  export declare const reXpub: string;
8
10
  export declare const reXprv: string;
9
11
  export declare const rePath: string;
10
12
  export declare const reXpubKey: string;
11
13
  export declare const reXprvKey: string;
12
- export declare const reKeyExp: string;
14
+ export declare const reNonSegwitKeyExp: string;
15
+ export declare const reSegwitKeyExp: string;
16
+ export declare const reTaprootKeyExp: string;
13
17
  export declare const anchorStartAndEnd: (re: string) => string;
14
18
  export declare const rePkAnchored: string;
15
19
  export declare const reAddrAnchored: string;
16
20
  export declare const rePkhAnchored: string;
17
21
  export declare const reWpkhAnchored: string;
18
22
  export declare const reShWpkhAnchored: string;
23
+ export declare const reTrSingleKeyAnchored: string;
19
24
  export declare const reShMiniscriptAnchored: string;
20
25
  export declare const reShWshMiniscriptAnchored: string;
21
26
  export declare const reWshMiniscriptAnchored: string;
package/dist/re.js CHANGED
@@ -1,8 +1,8 @@
1
1
  "use strict";
2
- // Copyright (c) 2023 Jose-Luis Landabaso - https://bitcoinerlab.com
2
+ // Copyright (c) 2025 Jose-Luis Landabaso - https://bitcoinerlab.com
3
3
  // Distributed under the MIT software license
4
4
  Object.defineProperty(exports, "__esModule", { value: true });
5
- exports.reWshMiniscriptAnchored = exports.reShWshMiniscriptAnchored = exports.reShMiniscriptAnchored = exports.reShWpkhAnchored = exports.reWpkhAnchored = exports.rePkhAnchored = exports.reAddrAnchored = exports.rePkAnchored = exports.anchorStartAndEnd = exports.reKeyExp = exports.reXprvKey = exports.reXpubKey = exports.rePath = exports.reXprv = exports.reXpub = exports.reWIF = exports.rePubKey = exports.reChecksum = exports.reOrigin = exports.reMasterFingerprint = exports.reOriginPath = void 0;
5
+ exports.reWshMiniscriptAnchored = exports.reShWshMiniscriptAnchored = exports.reShMiniscriptAnchored = exports.reTrSingleKeyAnchored = exports.reShWpkhAnchored = exports.reWpkhAnchored = exports.rePkhAnchored = exports.reAddrAnchored = exports.rePkAnchored = exports.anchorStartAndEnd = exports.reTaprootKeyExp = exports.reSegwitKeyExp = exports.reNonSegwitKeyExp = exports.reXprvKey = exports.reXpubKey = exports.rePath = exports.reXprv = exports.reXpub = exports.reWIF = exports.reTaprootPubKey = exports.reSegwitPubKey = exports.reNonSegwitPubKey = exports.reChecksum = exports.reOrigin = exports.reMasterFingerprint = exports.reOriginPath = void 0;
6
6
  const checksum_1 = require("./checksum");
7
7
  //Regular expressions cheat sheet:
8
8
  //https://www.keycdn.com/support/regex-cheat-sheet
@@ -22,7 +22,10 @@ exports.reChecksum = String.raw `(#[${checksum_1.CHECKSUM_CHARSET}]{8})`;
22
22
  //as explained here: github.com/bitcoin/bitcoin/blob/master/doc/descriptors.md#reference
23
23
  const reCompressedPubKey = String.raw `((02|03)[0-9a-fA-F]{64})`;
24
24
  const reUncompressedPubKey = String.raw `(04[0-9a-fA-F]{128})`;
25
- exports.rePubKey = String.raw `(${reCompressedPubKey}|${reUncompressedPubKey})`;
25
+ const reXOnlyPubKey = String.raw `([0-9a-fA-F]{64})`;
26
+ exports.reNonSegwitPubKey = String.raw `(${reCompressedPubKey}|${reUncompressedPubKey})`;
27
+ exports.reSegwitPubKey = String.raw `(${reCompressedPubKey})`;
28
+ exports.reTaprootPubKey = String.raw `(${reCompressedPubKey}|${reXOnlyPubKey})`;
26
29
  //https://learnmeabitcoin.com/technical/wif
27
30
  //5, K, L for mainnet, 5: uncompressed, {K, L}: compressed
28
31
  //c, 9, testnet, c: compressed, 9: uncompressed
@@ -38,15 +41,20 @@ exports.rePath = String.raw `(\/(${rePathComponent})*(${reRangeLevel}|${reLevel}
38
41
  exports.reXpubKey = String.raw `(${exports.reXpub})(${exports.rePath})?`;
39
42
  exports.reXprvKey = String.raw `(${exports.reXprv})(${exports.rePath})?`;
40
43
  //actualKey is the keyExpression without optional origin
41
- const reActualKey = String.raw `(${exports.reXpubKey}|${exports.reXprvKey}|${exports.rePubKey}|${exports.reWIF})`;
44
+ const reNonSegwitActualKey = String.raw `(${exports.reXpubKey}|${exports.reXprvKey}|${exports.reNonSegwitPubKey}|${exports.reWIF})`;
45
+ const reSegwitActualKey = String.raw `(${exports.reXpubKey}|${exports.reXprvKey}|${exports.reSegwitPubKey}|${exports.reWIF})`;
46
+ const reTaprootActualKey = String.raw `(${exports.reXpubKey}|${exports.reXprvKey}|${exports.reTaprootPubKey}|${exports.reWIF})`;
42
47
  //reOrigin is optional: Optionally, key origin information, consisting of:
43
48
  //Matches a key expression: wif, xpub, xprv or pubkey:
44
- exports.reKeyExp = String.raw `(${exports.reOrigin})?(${reActualKey})`;
49
+ exports.reNonSegwitKeyExp = String.raw `(${exports.reOrigin})?(${reNonSegwitActualKey})`;
50
+ exports.reSegwitKeyExp = String.raw `(${exports.reOrigin})?(${reSegwitActualKey})`;
51
+ exports.reTaprootKeyExp = String.raw `(${exports.reOrigin})?(${reTaprootActualKey})`;
45
52
  const rePk = String.raw `pk\((.*?)\)`; //Matches anything. We assert later in the code that the pubkey is valid.
46
53
  const reAddr = String.raw `addr\((.*?)\)`; //Matches anything. We assert later in the code that the address is valid.
47
- const rePkh = String.raw `pkh\(${exports.reKeyExp}\)`;
48
- const reWpkh = String.raw `wpkh\(${exports.reKeyExp}\)`;
49
- const reShWpkh = String.raw `sh\(wpkh\(${exports.reKeyExp}\)\)`;
54
+ const rePkh = String.raw `pkh\(${exports.reNonSegwitKeyExp}\)`;
55
+ const reWpkh = String.raw `wpkh\(${exports.reSegwitKeyExp}\)`;
56
+ const reShWpkh = String.raw `sh\(wpkh\(${exports.reSegwitKeyExp}\)\)`;
57
+ const reTrSingleKey = String.raw `tr\(${exports.reTaprootKeyExp}\)`; // TODO: tr(KEY,TREE) not yet supported. TrSingleKey used for tr(KEY)
50
58
  const reMiniscript = String.raw `(.*?)`; //Matches anything. We assert later in the code that miniscripts are valid and sane.
51
59
  //RegExp makers:
52
60
  const makeReSh = (re) => String.raw `sh\(${re}\)`;
@@ -60,6 +68,7 @@ exports.reAddrAnchored = (0, exports.anchorStartAndEnd)(composeChecksum(reAddr))
60
68
  exports.rePkhAnchored = (0, exports.anchorStartAndEnd)(composeChecksum(rePkh));
61
69
  exports.reWpkhAnchored = (0, exports.anchorStartAndEnd)(composeChecksum(reWpkh));
62
70
  exports.reShWpkhAnchored = (0, exports.anchorStartAndEnd)(composeChecksum(reShWpkh));
71
+ exports.reTrSingleKeyAnchored = (0, exports.anchorStartAndEnd)(composeChecksum(reTrSingleKey));
63
72
  exports.reShMiniscriptAnchored = (0, exports.anchorStartAndEnd)(composeChecksum(makeReSh(reMiniscript)));
64
73
  exports.reShWshMiniscriptAnchored = (0, exports.anchorStartAndEnd)(composeChecksum(makeReShWsh(reMiniscript)));
65
74
  exports.reWshMiniscriptAnchored = (0, exports.anchorStartAndEnd)(composeChecksum(makeReWsh(reMiniscript)));
@@ -43,6 +43,20 @@ export declare const wpkhBIP32: ({ masterNode, network, keyPath, account, change
43
43
  */
44
44
  isPublic?: boolean;
45
45
  }) => string;
46
+ export declare const trBIP32: ({ masterNode, network, keyPath, account, change, index, isPublic }: {
47
+ masterNode: BIP32Interface;
48
+ /** @default networks.bitcoin */
49
+ network?: Network;
50
+ account: number;
51
+ change?: number | undefined;
52
+ index?: number | undefined | '*';
53
+ keyPath?: string;
54
+ /**
55
+ * Compute an xpub or xprv
56
+ * @default true
57
+ */
58
+ isPublic?: boolean;
59
+ }) => string;
46
60
  export declare const pkhLedger: {
47
61
  ({ ledgerManager, account, keyPath, change, index }: {
48
62
  ledgerManager: LedgerManager;
@@ -100,3 +114,22 @@ export declare const wpkhLedger: {
100
114
  index?: number | undefined | '*';
101
115
  }): Promise<string>;
102
116
  };
117
+ export declare const trLedger: {
118
+ ({ ledgerManager, account, keyPath, change, index }: {
119
+ ledgerManager: LedgerManager;
120
+ account: number;
121
+ keyPath?: string;
122
+ change?: number | undefined;
123
+ index?: number | undefined | '*';
124
+ }): Promise<string>;
125
+ ({ ledgerClient, ledgerState, network, account, keyPath, change, index }: {
126
+ ledgerClient: unknown;
127
+ ledgerState: LedgerState;
128
+ /** @default networks.bitcoin */
129
+ network?: Network;
130
+ account: number;
131
+ keyPath?: string;
132
+ change?: number | undefined;
133
+ index?: number | undefined | '*';
134
+ }): Promise<string>;
135
+ };
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.wpkhLedger = exports.shWpkhLedger = exports.pkhLedger = exports.wpkhBIP32 = exports.shWpkhBIP32 = exports.pkhBIP32 = void 0;
3
+ exports.trLedger = exports.wpkhLedger = exports.shWpkhLedger = exports.pkhLedger = exports.trBIP32 = exports.wpkhBIP32 = exports.shWpkhBIP32 = exports.pkhBIP32 = void 0;
4
4
  const bitcoinjs_lib_1 = require("bitcoinjs-lib");
5
5
  const keyExpressions_1 = require("./keyExpressions");
6
6
  function assertStandardKeyPath(keyPath) {
@@ -43,6 +43,7 @@ function standardExpressionsBIP32Maker(purpose, scriptTemplate) {
43
43
  exports.pkhBIP32 = standardExpressionsBIP32Maker(44, 'pkh(KEYEXPRESSION)');
44
44
  exports.shWpkhBIP32 = standardExpressionsBIP32Maker(49, 'sh(wpkh(KEYEXPRESSION))');
45
45
  exports.wpkhBIP32 = standardExpressionsBIP32Maker(84, 'wpkh(KEYEXPRESSION)');
46
+ exports.trBIP32 = standardExpressionsBIP32Maker(86, 'tr(KEYEXPRESSION)');
46
47
  function standardExpressionsLedgerMaker(purpose, scriptTemplate) {
47
48
  /** @hidden */
48
49
  async function standardScriptExpressionLedger({ ledgerClient, ledgerState, ledgerManager, network, account, keyPath, change, index }) {
@@ -74,3 +75,4 @@ function standardExpressionsLedgerMaker(purpose, scriptTemplate) {
74
75
  exports.pkhLedger = standardExpressionsLedgerMaker(44, 'pkh(KEYEXPRESSION)');
75
76
  exports.shWpkhLedger = standardExpressionsLedgerMaker(49, 'sh(wpkh(KEYEXPRESSION))');
76
77
  exports.wpkhLedger = standardExpressionsLedgerMaker(84, 'wpkh(KEYEXPRESSION)');
78
+ exports.trLedger = standardExpressionsLedgerMaker(86, 'tr(KEYEXPRESSION)');
package/dist/signers.d.ts CHANGED
@@ -3,11 +3,48 @@ import type { ECPairInterface } from 'ecpair';
3
3
  import type { BIP32Interface } from 'bip32';
4
4
  import type { DescriptorInstance } from './descriptors';
5
5
  import { LedgerState, LedgerManager } from './ledger';
6
+ /**
7
+ * Signs a specific input of a PSBT with an ECPair.
8
+ *
9
+ * Unlike bitcoinjs-lib's native `psbt.signInput()`, this function automatically detects
10
+ * if the input is a Taproot input and internally tweaks the key if needed.
11
+ *
12
+ * This behavior matches how `signInputBIP32` works, where the BIP32 node is automatically
13
+ * tweaked for Taproot inputs. In contrast, bitcoinjs-lib's native implementation requires
14
+ * manual pre-tweaking of ECPair signers for Taproot inputs.
15
+ *
16
+ * @see https://github.com/bitcoinjs/bitcoinjs-lib/pull/2137#issuecomment-2713264848
17
+ *
18
+ * @param {Object} params - The parameters object
19
+ * @param {Psbt} params.psbt - The PSBT to sign
20
+ * @param {number} params.index - The input index to sign
21
+ * @param {ECPairInterface} params.ecpair - The ECPair to sign with
22
+ */
6
23
  export declare function signInputECPair({ psbt, index, ecpair }: {
7
24
  psbt: Psbt;
8
25
  index: number;
9
26
  ecpair: ECPairInterface;
10
27
  }): void;
28
+ /**
29
+ * Signs all inputs of a PSBT with an ECPair.
30
+ *
31
+ * This function improves upon bitcoinjs-lib's native `psbt.signAllInputs()` by automatically
32
+ * handling Taproot inputs. For each input, it detects if it's a Taproot input and internally
33
+ * tweaks the key if needed.
34
+ *
35
+ * This creates consistency with the BIP32 signing methods (`signBIP32`/`signInputBIP32`),
36
+ * which also automatically handle key tweaking for Taproot inputs. In contrast, bitcoinjs-lib's
37
+ * native implementation requires users to manually pre-tweak ECPair signers for Taproot inputs.
38
+ *
39
+ * With this implementation, you can use a single ECPair to sign both Taproot and non-Taproot
40
+ * inputs in the same PSBT, similar to how `signBIP32` allows using a common node for both types.
41
+ *
42
+ * @see https://github.com/bitcoinjs/bitcoinjs-lib/pull/2137#issuecomment-2713264848
43
+ *
44
+ * @param {Object} params - The parameters object
45
+ * @param {Psbt} params.psbt - The PSBT to sign
46
+ * @param {ECPairInterface} params.ecpair - The ECPair to sign with
47
+ */
11
48
  export declare function signECPair({ psbt, ecpair }: {
12
49
  psbt: Psbt;
13
50
  ecpair: ECPairInterface;
package/dist/signers.js CHANGED
@@ -1,22 +1,94 @@
1
1
  "use strict";
2
- // Copyright (c) 2023 Jose-Luis Landabaso - https://bitcoinerlab.com
2
+ // Copyright (c) 2025 Jose-Luis Landabaso - https://bitcoinerlab.com
3
3
  // Distributed under the MIT software license
4
4
  Object.defineProperty(exports, "__esModule", { value: true });
5
5
  exports.signLedger = exports.signInputLedger = exports.signBIP32 = exports.signInputBIP32 = exports.signECPair = exports.signInputECPair = void 0;
6
+ const bip371_1 = require("bitcoinjs-lib/src/psbt/bip371");
7
+ const bip341_1 = require("bitcoinjs-lib/src/payments/bip341");
6
8
  const ledger_1 = require("./ledger");
9
+ const applyPR2137_1 = require("./applyPR2137");
10
+ function range(n) {
11
+ return [...Array(n).keys()];
12
+ }
13
+ /**
14
+ * Signs a specific input of a PSBT with an ECPair.
15
+ *
16
+ * Unlike bitcoinjs-lib's native `psbt.signInput()`, this function automatically detects
17
+ * if the input is a Taproot input and internally tweaks the key if needed.
18
+ *
19
+ * This behavior matches how `signInputBIP32` works, where the BIP32 node is automatically
20
+ * tweaked for Taproot inputs. In contrast, bitcoinjs-lib's native implementation requires
21
+ * manual pre-tweaking of ECPair signers for Taproot inputs.
22
+ *
23
+ * @see https://github.com/bitcoinjs/bitcoinjs-lib/pull/2137#issuecomment-2713264848
24
+ *
25
+ * @param {Object} params - The parameters object
26
+ * @param {Psbt} params.psbt - The PSBT to sign
27
+ * @param {number} params.index - The input index to sign
28
+ * @param {ECPairInterface} params.ecpair - The ECPair to sign with
29
+ */
7
30
  function signInputECPair({ psbt, index, ecpair }) {
8
- psbt.signInput(index, ecpair);
31
+ //psbt.signInput(index, ecpair); <- Replaced for the code below
32
+ //that can handle taroot inputs automatically.
33
+ //See https://github.com/bitcoinjs/bitcoinjs-lib/pull/2137#issuecomment-2713264848
34
+ const input = psbt.data.inputs[index];
35
+ if (!input)
36
+ throw new Error('Invalid index');
37
+ if ((0, bip371_1.isTaprootInput)(input)) {
38
+ const hash = (0, bip341_1.tapTweakHash)(Buffer.from(ecpair.publicKey.slice(1, 33)), undefined);
39
+ const tweakedEcpair = ecpair.tweak(hash);
40
+ psbt.signInput(index, tweakedEcpair);
41
+ }
42
+ else
43
+ psbt.signInput(index, ecpair);
9
44
  }
10
45
  exports.signInputECPair = signInputECPair;
46
+ /**
47
+ * Signs all inputs of a PSBT with an ECPair.
48
+ *
49
+ * This function improves upon bitcoinjs-lib's native `psbt.signAllInputs()` by automatically
50
+ * handling Taproot inputs. For each input, it detects if it's a Taproot input and internally
51
+ * tweaks the key if needed.
52
+ *
53
+ * This creates consistency with the BIP32 signing methods (`signBIP32`/`signInputBIP32`),
54
+ * which also automatically handle key tweaking for Taproot inputs. In contrast, bitcoinjs-lib's
55
+ * native implementation requires users to manually pre-tweak ECPair signers for Taproot inputs.
56
+ *
57
+ * With this implementation, you can use a single ECPair to sign both Taproot and non-Taproot
58
+ * inputs in the same PSBT, similar to how `signBIP32` allows using a common node for both types.
59
+ *
60
+ * @see https://github.com/bitcoinjs/bitcoinjs-lib/pull/2137#issuecomment-2713264848
61
+ *
62
+ * @param {Object} params - The parameters object
63
+ * @param {Psbt} params.psbt - The PSBT to sign
64
+ * @param {ECPairInterface} params.ecpair - The ECPair to sign with
65
+ */
11
66
  function signECPair({ psbt, ecpair }) {
12
- psbt.signAllInputs(ecpair);
67
+ //psbt.signAllInputs(ecpair); <- replaced for the code below that handles
68
+ //taptoot automatically.
69
+ //See https://github.com/bitcoinjs/bitcoinjs-lib/pull/2137#issuecomment-2713264848
70
+ const results = [];
71
+ for (const index of range(psbt.data.inputs.length)) {
72
+ try {
73
+ signInputECPair({ psbt, index, ecpair });
74
+ results.push(true);
75
+ }
76
+ catch (err) {
77
+ results.push(false);
78
+ }
79
+ }
80
+ if (results.every(v => v === false)) {
81
+ throw new Error('No inputs were signed');
82
+ }
13
83
  }
14
84
  exports.signECPair = signECPair;
15
85
  function signInputBIP32({ psbt, index, node }) {
86
+ (0, applyPR2137_1.applyPR2137)(psbt);
16
87
  psbt.signInputHD(index, node);
17
88
  }
18
89
  exports.signInputBIP32 = signInputBIP32;
19
90
  function signBIP32({ psbt, masterNode }) {
91
+ (0, applyPR2137_1.applyPR2137)(psbt);
20
92
  psbt.signAllInputsHD(masterNode);
21
93
  }
22
94
  exports.signBIP32 = signBIP32;
@@ -50,6 +122,8 @@ async function signInputLedger({ psbt, index, descriptor, ledgerClient, ledgerSt
50
122
  throw new Error(`Error: pass a valid ledgerClient`);
51
123
  let ledgerSignatures;
52
124
  if (ledgerManager) {
125
+ if (psbt.data.inputs[index]?.tapInternalKey)
126
+ throw new Error('Taproot inputs not yet supported for the Ledger device');
53
127
  const policy = await (0, ledger_1.ledgerPolicyFromPsbtInput)({
54
128
  psbt,
55
129
  index,
@@ -129,6 +203,8 @@ async function signLedger({ psbt, descriptors, ledgerClient, ledgerState, ledger
129
203
  const ledgerPolicies = [];
130
204
  if (ledgerManager)
131
205
  for (let index = 0; index < psbt.data.inputs.length; index++) {
206
+ if (psbt.data.inputs[index]?.tapInternalKey)
207
+ throw new Error('Taproot inputs not yet supported for the Ledger device');
132
208
  const policy = await (0, ledger_1.ledgerPolicyFromPsbtInput)({
133
209
  psbt,
134
210
  index,
package/dist/types.d.ts CHANGED
@@ -88,6 +88,7 @@ export interface TinySecp256k1Interface {
88
88
  verify(h: Uint8Array, Q: Uint8Array, signature: Uint8Array, strict?: boolean): boolean;
89
89
  verifySchnorr?(h: Uint8Array, Q: Uint8Array, signature: Uint8Array): boolean;
90
90
  xOnlyPointAddTweak(p: Uint8Array, tweak: Uint8Array): XOnlyPointAddTweakResult | null;
91
+ isXOnlyPoint(p: Uint8Array): boolean;
91
92
  privateNegate(d: Uint8Array): Uint8Array;
92
93
  }
93
94
  /**
@@ -118,6 +119,10 @@ export type Expansion = {
118
119
  * A boolean indicating whether the descriptor uses SegWit.
119
120
  */
120
121
  isSegwit?: boolean;
122
+ /**
123
+ * A boolean indicating whether the descriptor uses Taproot.
124
+ */
125
+ isTaproot?: boolean;
121
126
  /**
122
127
  * The expanded miniscript, if any.
123
128
  * It corresponds to the `expandedExpression` without the top-level script
@@ -176,6 +181,13 @@ export interface ParseKeyExpression {
176
181
  * expression) is compressed (33 bytes).
177
182
  */
178
183
  isSegwit?: boolean;
184
+ /**
185
+ * Indicates if this key expression belongs to a Taproot output.
186
+ * For Taproot, the key must be represented as an x-only public key
187
+ * (32 bytes). If a 33-byte compressed pubkey is derived, it is converted to
188
+ * its x-only representation.
189
+ */
190
+ isTaproot?: boolean;
179
191
  network?: Network;
180
192
  }): KeyInfo;
181
193
  }
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@bitcoinerlab/descriptors",
3
3
  "description": "This library parses and creates Bitcoin Miniscript Descriptors and generates Partially Signed Bitcoin Transactions (PSBTs). It provides PSBT finalizers and signers for single-signature, BIP32 and Hardware Wallets.",
4
4
  "homepage": "https://github.com/bitcoinerlab/descriptors",
5
- "version": "2.2.1",
5
+ "version": "2.3.1",
6
6
  "author": "Jose-Luis Landabaso",
7
7
  "license": "MIT",
8
8
  "repository": {