@bitcoinerlab/descriptors 0.2.2 → 0.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
@@ -79,8 +79,8 @@ constructor({
79
79
  // to denote an arbitrary index.
80
80
  index, // The descriptor's index in the case of a range descriptor
81
81
  // (must be an integer >= 0).
82
- checksumRequired = false, // Flag indicating if the descriptor is required
83
- // to include a checksum.
82
+ checksumRequired = false // Optional flag indicating if the descriptor is
83
+ // required to include a checksum. Defaults to false.
84
84
  allowMiniscriptInP2SH = false, // Flag indicating if this instance can parse
85
85
  // and generate script satisfactions for
86
86
  // sh(miniscript) top-level expressions of
@@ -131,6 +131,64 @@ Here, `index` is the `inputIndex` obtained from the `updatePsbt()` method and `p
131
131
 
132
132
  For further information on using the Descriptor class, refer to the [comprehensive guides](https://bitcoinerlab.com/guides) that offer explanations and playgrounds to help learn the module. Additionally, a [Stack Exchange answer](https://bitcoin.stackexchange.com/a/118036/89665) provides a focused explanation on the constructor, specifically the `signersPubKeys` parameter, and the usage of `updatePsbt`, `finalizePsbt`, `getAddress`, and `getScriptPubKey`.
133
133
 
134
+ #### Tip: Parsing descriptors without instantiating a class
135
+
136
+ `DescriptorsFactory` provides a convenient `expand()` function that allows you to parse a descriptor expression without the need to instantiate the `Descriptor` class. This function can be used as follows:
137
+
138
+ ```javascript
139
+ const { expand } = descriptors.DescriptorsFactory(secp256k1);
140
+ const result = expand({
141
+ expression: 'sh(wsh(andor(pk(0252972572d465d016d4c501887b8df303eee3ed602c056b1eb09260dfa0da0ab2),older(8640),pk([d34db33f/49'/0'/0']tpubDCdxmvzJ5QBjTN8oCjjyT2V58AyZvA1fkmCeZRC75QMoaHcVP2m45Bv3hmnR7ttAwkb2UNYyoXdHVt4gwBqRrJqLUU2JrM43HippxiWpHra/1/2/3/4/*))))',
142
+ network: networks.testnet, // One of bitcoinjs-lib `networks`
143
+ // (https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/src/networks.js)
144
+ // or another one with the same interface.
145
+ // Optional (defaults to bitcoin mainnet).
146
+ allowMiniscriptInP2SH: true, // Optional flag to allow miniscript in P2SH.
147
+ // Defaults to false.
148
+ index, // Optional. The descriptor's index in the case of a range descriptor
149
+ // (must be an integer >= 0). If not set for ranged descriptors, then
150
+ // the function will return an expansionMap with ranged keyPaths and
151
+ // won't compute Payment or scripts.
152
+ checksumRequired = false // Optional flag indicating if the descriptor is
153
+ // required to include a checksum. Defaults to false.
154
+ });
155
+ ```
156
+
157
+ The `expand()` function returns an object with the following properties:
158
+
159
+ - `payment: Payment | undefined`: The corresponding [bitcoinjs-lib Payment](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/ts_src/payments/index.ts) for the provided expression, if applicable.
160
+ - `expandedExpression: string | undefined`: The expanded descriptor expression.
161
+ - `miniscript: string | undefined`: The extracted miniscript from the expression, if any.
162
+ - `expansionMap: ExpansionMap | undefined`: A map of key expressions in the descriptor to their corresponding expanded keys.
163
+ - `isSegwit: boolean | undefined`: A boolean indicating whether the descriptor represents a SegWit script.
164
+ - `expandedMiniscript: string | undefined`: The expanded miniscript, if any.
165
+ - `redeemScript: Buffer | undefined`: The redeem script for the descriptor, if applicable.
166
+ - `witnessScript: Buffer | undefined`: The witness script for the descriptor, if applicable.
167
+ - `isRanged: boolean` : Whether the expression represents a ranged descriptor.
168
+ - `canonicalExpression` : This is the preferred or authoritative representation of the descriptor expression. It standardizes the descriptor by replacing indexes on wildcards and eliminating checksums.
169
+
170
+ For the example expression provided, the `expandedExpression` and a portion of the `expansionMap` would be as follows:
171
+
172
+ ```javascript
173
+ // expression: 'sh(wsh(andor(pk(0252972572d465d016d4c501887b8df303eee3ed602c056b1eb09260dfa0da0ab2),older(8640),pk([d34db33f/49'/0'/0']tpubDCdxmvzJ5QBjTN8oCjjyT2V58AyZvA1fkmCeZRC75QMoaHcVP2m45Bv3hmnR7ttAwkb2UNYyoXdHVt4gwBqRrJqLUU2JrM43HippxiWpHra/1/2/3/4/*))))'
174
+
175
+ expandedExpression: 'sh(wsh(andor(pk(@0),older(8640),pk(@1))))',
176
+ expansionMap: {
177
+ '@0': {
178
+ keyExpression:
179
+ '0252972572d465d016d4c501887b8df303eee3ed602c056b1eb09260dfa0da0ab2'
180
+ },
181
+ '@1': {
182
+ keyExpression:
183
+ "[d34db33f/49'/0'/0']tpubDCdxmvzJ5QBjTN8oCjjyT2V58AyZvA1fkmCeZRC75QMoaHcVP2m45Bv3hmnR7ttAwkb2UNYyoXdHVt4gwBqRrJqLUU2JrM43HippxiWpHra/1/2/3/4/*",
184
+ keyPath: '/1/2/3/4/*',
185
+ originPath: "/49'/0'/0'",
186
+ path: "m/49'/0'/0'/1/2/3/4/*",
187
+ // Other relevant properties returned: `pubkey`, `ecpair` & `bip32` interfaces, `masterFingerprint`, etc.
188
+ }
189
+ }
190
+ ```
191
+
134
192
  ### keyExpressions and scriptExpressions
135
193
 
136
194
  This library also includes a set of function helpers that facilitate the generation of the `expression` parameter in the constructor of the `Descriptor` class. These helpers are located under the `scriptExpressions` module, which can be imported using the following statement:
@@ -1,6 +1,6 @@
1
1
  import { BIP32API } from 'bip32';
2
2
  import { ECPairAPI } from 'ecpair';
3
- import type { TinySecp256k1Interface, ParseKeyExpression, DescriptorInterfaceConstructor } from './types';
3
+ import type { TinySecp256k1Interface, ParseKeyExpression, Expand, DescriptorInterfaceConstructor } from './types';
4
4
  /**
5
5
  * Builds the functions needed to operate with descriptors using an external elliptic curve (ecc) library.
6
6
  * @param {Object} ecc - an object containing elliptic curve operations, such as [tiny-secp256k1](https://github.com/bitcoinjs/tiny-secp256k1) or [@bitcoinerlab/secp256k1](https://github.com/bitcoinerlab/secp256k1).
@@ -10,5 +10,6 @@ export declare function DescriptorsFactory(ecc: TinySecp256k1Interface): {
10
10
  Descriptor: DescriptorInterfaceConstructor;
11
11
  ECPair: ECPairAPI;
12
12
  parseKeyExpression: ParseKeyExpression;
13
+ expand: Expand;
13
14
  BIP32: BIP32API;
14
15
  };
@@ -55,7 +55,7 @@ function countNonPushOnlyOPs(script) {
55
55
  const decompile = bitcoinjs_lib_1.script.decompile(script);
56
56
  if (!decompile)
57
57
  throw new Error(`Error: cound not decompile ${script}`);
58
- return decompile.filter(op => op > bitcoinjs_lib_1.script.OPS['OP_16']).length;
58
+ return decompile.filter(op => typeof op === 'number' && op > bitcoinjs_lib_1.script.OPS['OP_16']).length;
59
59
  }
60
60
  /*
61
61
  * Returns a bare descriptor without checksum and particularized for a certain
@@ -75,20 +75,20 @@ function evaluate({ expression, checksumRequired, index }) {
75
75
  throw new Error(`Error: invalid descriptor checksum for ${expression}`);
76
76
  }
77
77
  }
78
- const mWildcard = evaluatedExpression.match(/\*/g);
79
- if (mWildcard && mWildcard.length > 0) {
80
- if (index === undefined)
81
- throw new Error(`Error: index was not provided for ranged descriptor`);
82
- if (!Number.isInteger(index) || index < 0)
83
- throw new Error(`Error: invalid index ${index}`);
84
- //From https://github.com/bitcoin/bitcoin/blob/master/doc/descriptors.md
85
- //To prevent a combinatorial explosion of the search space, if more than
86
- //one of the multi() key arguments is a BIP32 wildcard path ending in /* or
87
- //*', the multi() expression only matches multisig scripts with the ith
88
- //child key from each wildcard path in lockstep, rather than scripts with
89
- //any combination of child keys from each wildcard path.
90
- //We extend this reasoning for musig for all cases
91
- evaluatedExpression = evaluatedExpression.replaceAll('*', index.toString());
78
+ if (index !== undefined) {
79
+ const mWildcard = evaluatedExpression.match(/\*/g);
80
+ if (mWildcard && mWildcard.length > 0) {
81
+ //From https://github.com/bitcoin/bitcoin/blob/master/doc/descriptors.md
82
+ //To prevent a combinatorial explosion of the search space, if more than
83
+ //one of the multi() key arguments is a BIP32 wildcard path ending in /* or
84
+ //*', the multi() expression only matches multisig scripts with the ith
85
+ //child key from each wildcard path in lockstep, rather than scripts with
86
+ //any combination of child keys from each wildcard path.
87
+ //We extend this reasoning for musig for all cases
88
+ evaluatedExpression = evaluatedExpression.replaceAll('*', index.toString());
89
+ }
90
+ else
91
+ throw new Error(`Error: index passed for non-ranged descriptor: ${expression}`);
92
92
  }
93
93
  return evaluatedExpression;
94
94
  }
@@ -114,6 +114,273 @@ function DescriptorsFactory(ecc) {
114
114
  BIP32
115
115
  });
116
116
  };
117
+ /**
118
+ * Takes a descriptor (expression) and expands it to its corresponding Bitcoin script and other relevant details.
119
+ *
120
+ * @param {Object} params The parameters for the function.
121
+ * @param {string} params.expression The descriptor expression to be expanded.
122
+ * @param {string} [params.loggedExpression] The descriptor expression used for logging error messages. If not provided, defaults to the original expression.
123
+ * @param {Object} [params.network=networks.bitcoin] The Bitcoin network to use. If not provided, defaults to Bitcoin mainnet.
124
+ * @param {boolean} [params.allowMiniscriptInP2SH=false] Flag to allow miniscript in P2SH. If not provided, defaults to false.
125
+ *
126
+ * @returns {Object} An object containing various details about the expanded descriptor:
127
+ * - payment: The corresponding [bitcoinjs-lib Payment](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/ts_src/payments/index.ts) for the provided expression, if applicable.
128
+ * - expandedExpression: The expanded descriptor expression.
129
+ * - miniscript: The extracted miniscript from the expression, if any.
130
+ * - expansionMap: A map of key expressions in the descriptor to their corresponding expanded keys.
131
+ * - isSegwit: A boolean indicating whether the descriptor represents a SegWit script.
132
+ * - expandedMiniscript: The expanded miniscript, if any.
133
+ * - redeemScript: The redeem script for the descriptor, if applicable.
134
+ * - witnessScript: The witness script for the descriptor, if applicable.
135
+ * - isRanged: Whether this expression representas a ranged-descriptor
136
+ * - canonicalExpression: This is the preferred or authoritative
137
+ * representation of the descriptor expression. It standardizes the
138
+ * descriptor by replacing indexes on wildcards and eliminating checksums.
139
+ * This helps ensure consistency and facilitates efficient interpretation and handling by systems or software.
140
+ *
141
+ * @throws {Error} Throws an error if the descriptor cannot be parsed or does not conform to the expected format.
142
+ */
143
+ const expand = ({ expression, index, checksumRequired = false, network = bitcoinjs_lib_1.networks.bitcoin, allowMiniscriptInP2SH = false }) => {
144
+ let expandedExpression;
145
+ let miniscript;
146
+ let expansionMap;
147
+ let isSegwit;
148
+ let expandedMiniscript;
149
+ let payment;
150
+ let witnessScript;
151
+ let redeemScript;
152
+ const isRanged = expression.indexOf('*') !== -1;
153
+ if (index !== undefined)
154
+ if (!Number.isInteger(index) || index < 0)
155
+ throw new Error(`Error: invalid index ${index}`);
156
+ //Verify and remove checksum (if exists) and
157
+ //particularize range descriptor for index (if desc is range descriptor)
158
+ const canonicalExpression = evaluate({
159
+ expression,
160
+ ...(index !== undefined ? { index } : {}),
161
+ checksumRequired
162
+ });
163
+ const isCanonicalRanged = canonicalExpression.indexOf('*') !== -1;
164
+ //addr(ADDR)
165
+ if (canonicalExpression.match(RE.reAddrAnchored)) {
166
+ if (isRanged)
167
+ throw new Error(`Error: addr() cannot be ranged`);
168
+ const matchedAddress = canonicalExpression.match(RE.reAddrAnchored)?.[1]; //[1]-> whatever is found addr(->HERE<-)
169
+ if (!matchedAddress)
170
+ throw new Error(`Error: could not get an address in ${expression}`);
171
+ let output;
172
+ try {
173
+ output = bitcoinjs_lib_1.address.toOutputScript(matchedAddress, network);
174
+ }
175
+ catch (e) {
176
+ throw new Error(`Error: invalid address ${matchedAddress}`);
177
+ }
178
+ try {
179
+ payment = p2pkh({ output, network });
180
+ }
181
+ catch (e) { }
182
+ try {
183
+ payment = p2sh({ output, network });
184
+ }
185
+ catch (e) { }
186
+ try {
187
+ payment = p2wpkh({ output, network });
188
+ }
189
+ catch (e) { }
190
+ try {
191
+ payment = p2wsh({ output, network });
192
+ }
193
+ catch (e) { }
194
+ try {
195
+ payment = p2tr({ output, network });
196
+ }
197
+ catch (e) { }
198
+ if (!payment) {
199
+ throw new Error(`Error: invalid address ${matchedAddress}`);
200
+ }
201
+ }
202
+ //pk(KEY)
203
+ else if (canonicalExpression.match(RE.rePkAnchored)) {
204
+ isSegwit = false;
205
+ const keyExpression = canonicalExpression.match(RE.reKeyExp)?.[0];
206
+ if (!keyExpression)
207
+ throw new Error(`Error: keyExpression could not me extracted`);
208
+ if (canonicalExpression !== `pk(${keyExpression})`)
209
+ throw new Error(`Error: invalid expression ${expression}`);
210
+ expandedExpression = 'pk(@0)';
211
+ const pKE = parseKeyExpression({ keyExpression, network, isSegwit });
212
+ expansionMap = { '@0': pKE };
213
+ if (!isCanonicalRanged) {
214
+ const pubkey = pKE.pubkey;
215
+ //Note there exists no address for p2pk, but we can still use the script
216
+ if (!pubkey)
217
+ throw new Error(`Error: could not extract a pubkey from ${expression}`);
218
+ payment = p2pk({ pubkey, network });
219
+ }
220
+ }
221
+ //pkh(KEY) - legacy
222
+ else if (canonicalExpression.match(RE.rePkhAnchored)) {
223
+ isSegwit = false;
224
+ const keyExpression = canonicalExpression.match(RE.reKeyExp)?.[0];
225
+ if (!keyExpression)
226
+ throw new Error(`Error: keyExpression could not me extracted`);
227
+ if (canonicalExpression !== `pkh(${keyExpression})`)
228
+ throw new Error(`Error: invalid expression ${expression}`);
229
+ expandedExpression = 'pkh(@0)';
230
+ const pKE = parseKeyExpression({ keyExpression, network, isSegwit });
231
+ expansionMap = { '@0': pKE };
232
+ if (!isCanonicalRanged) {
233
+ const pubkey = pKE.pubkey;
234
+ if (!pubkey)
235
+ throw new Error(`Error: could not extract a pubkey from ${expression}`);
236
+ payment = p2pkh({ pubkey, network });
237
+ }
238
+ }
239
+ //sh(wpkh(KEY)) - nested segwit
240
+ else if (canonicalExpression.match(RE.reShWpkhAnchored)) {
241
+ isSegwit = true;
242
+ const keyExpression = canonicalExpression.match(RE.reKeyExp)?.[0];
243
+ if (!keyExpression)
244
+ throw new Error(`Error: keyExpression could not me extracted`);
245
+ if (canonicalExpression !== `sh(wpkh(${keyExpression}))`)
246
+ throw new Error(`Error: invalid expression ${expression}`);
247
+ expandedExpression = 'sh(wpkh(@0))';
248
+ const pKE = parseKeyExpression({ keyExpression, network, isSegwit });
249
+ expansionMap = { '@0': pKE };
250
+ if (!isCanonicalRanged) {
251
+ const pubkey = pKE.pubkey;
252
+ if (!pubkey)
253
+ throw new Error(`Error: could not extract a pubkey from ${expression}`);
254
+ payment = p2sh({ redeem: p2wpkh({ pubkey, network }), network });
255
+ redeemScript = payment.redeem?.output;
256
+ if (!redeemScript)
257
+ throw new Error(`Error: could not calculate redeemScript for ${expression}`);
258
+ }
259
+ }
260
+ //wpkh(KEY) - native segwit
261
+ else if (canonicalExpression.match(RE.reWpkhAnchored)) {
262
+ isSegwit = true;
263
+ const keyExpression = canonicalExpression.match(RE.reKeyExp)?.[0];
264
+ if (!keyExpression)
265
+ throw new Error(`Error: keyExpression could not me extracted`);
266
+ if (canonicalExpression !== `wpkh(${keyExpression})`)
267
+ throw new Error(`Error: invalid expression ${expression}`);
268
+ expandedExpression = 'wpkh(@0)';
269
+ const pKE = parseKeyExpression({ keyExpression, network, isSegwit });
270
+ expansionMap = { '@0': pKE };
271
+ if (!isCanonicalRanged) {
272
+ const pubkey = pKE.pubkey;
273
+ if (!pubkey)
274
+ throw new Error(`Error: could not extract a pubkey from ${expression}`);
275
+ payment = p2wpkh({ pubkey, network });
276
+ }
277
+ }
278
+ //sh(wsh(miniscript))
279
+ else if (canonicalExpression.match(RE.reShWshMiniscriptAnchored)) {
280
+ isSegwit = true;
281
+ miniscript = canonicalExpression.match(RE.reShWshMiniscriptAnchored)?.[1]; //[1]-> whatever is found sh(wsh(->HERE<-))
282
+ if (!miniscript)
283
+ throw new Error(`Error: could not get miniscript in ${expression}`);
284
+ ({ expandedMiniscript, expansionMap } = expandMiniscript({
285
+ miniscript,
286
+ isSegwit,
287
+ network
288
+ }));
289
+ expandedExpression = `sh(wsh(${expandedMiniscript}))`;
290
+ if (!isCanonicalRanged) {
291
+ const script = (0, miniscript_1.miniscript2Script)({ expandedMiniscript, expansionMap });
292
+ witnessScript = script;
293
+ if (script.byteLength > MAX_STANDARD_P2WSH_SCRIPT_SIZE) {
294
+ throw new Error(`Error: script is too large, ${script.byteLength} bytes is larger than ${MAX_STANDARD_P2WSH_SCRIPT_SIZE} bytes`);
295
+ }
296
+ const nonPushOnlyOps = countNonPushOnlyOPs(script);
297
+ if (nonPushOnlyOps > MAX_OPS_PER_SCRIPT) {
298
+ throw new Error(`Error: too many non-push ops, ${nonPushOnlyOps} non-push ops is larger than ${MAX_OPS_PER_SCRIPT}`);
299
+ }
300
+ payment = p2sh({
301
+ redeem: p2wsh({ redeem: { output: script, network }, network }),
302
+ network
303
+ });
304
+ redeemScript = payment.redeem?.output;
305
+ if (!redeemScript)
306
+ throw new Error(`Error: could not calculate redeemScript for ${expression}`);
307
+ }
308
+ }
309
+ //sh(miniscript)
310
+ else if (canonicalExpression.match(RE.reShMiniscriptAnchored)) {
311
+ //isSegwit false because we know it's a P2SH of a miniscript and not a
312
+ //P2SH that embeds a witness payment.
313
+ isSegwit = false;
314
+ miniscript = canonicalExpression.match(RE.reShMiniscriptAnchored)?.[1]; //[1]-> whatever is found sh(->HERE<-)
315
+ if (!miniscript)
316
+ throw new Error(`Error: could not get miniscript in ${expression}`);
317
+ if (allowMiniscriptInP2SH === false &&
318
+ //These top-level expressions within sh are allowed within sh.
319
+ //They can be parsed with miniscript2Script, but first we must make sure
320
+ //that other expressions are not accepted (unless forced with allowMiniscriptInP2SH).
321
+ miniscript.search(/^(pk\(|pkh\(|wpkh\(|combo\(|multi\(|sortedmulti\(|multi_a\(|sortedmulti_a\()/) !== 0) {
322
+ throw new Error(`Error: Miniscript expressions can only be used in wsh`);
323
+ }
324
+ ({ expandedMiniscript, expansionMap } = expandMiniscript({
325
+ miniscript,
326
+ isSegwit,
327
+ network
328
+ }));
329
+ expandedExpression = `sh(${expandedMiniscript})`;
330
+ if (!isCanonicalRanged) {
331
+ const script = (0, miniscript_1.miniscript2Script)({ expandedMiniscript, expansionMap });
332
+ redeemScript = script;
333
+ if (script.byteLength > MAX_SCRIPT_ELEMENT_SIZE) {
334
+ throw new Error(`Error: P2SH script is too large, ${script.byteLength} bytes is larger than ${MAX_SCRIPT_ELEMENT_SIZE} bytes`);
335
+ }
336
+ const nonPushOnlyOps = countNonPushOnlyOPs(script);
337
+ if (nonPushOnlyOps > MAX_OPS_PER_SCRIPT) {
338
+ throw new Error(`Error: too many non-push ops, ${nonPushOnlyOps} non-push ops is larger than ${MAX_OPS_PER_SCRIPT}`);
339
+ }
340
+ payment = p2sh({ redeem: { output: script, network }, network });
341
+ }
342
+ }
343
+ //wsh(miniscript)
344
+ else if (canonicalExpression.match(RE.reWshMiniscriptAnchored)) {
345
+ isSegwit = true;
346
+ miniscript = canonicalExpression.match(RE.reWshMiniscriptAnchored)?.[1]; //[1]-> whatever is found wsh(->HERE<-)
347
+ if (!miniscript)
348
+ throw new Error(`Error: could not get miniscript in ${expression}`);
349
+ ({ expandedMiniscript, expansionMap } = expandMiniscript({
350
+ miniscript,
351
+ isSegwit,
352
+ network
353
+ }));
354
+ expandedExpression = `wsh(${expandedMiniscript})`;
355
+ if (!isCanonicalRanged) {
356
+ const script = (0, miniscript_1.miniscript2Script)({ expandedMiniscript, expansionMap });
357
+ witnessScript = script;
358
+ if (script.byteLength > MAX_STANDARD_P2WSH_SCRIPT_SIZE) {
359
+ throw new Error(`Error: script is too large, ${script.byteLength} bytes is larger than ${MAX_STANDARD_P2WSH_SCRIPT_SIZE} bytes`);
360
+ }
361
+ const nonPushOnlyOps = countNonPushOnlyOPs(script);
362
+ if (nonPushOnlyOps > MAX_OPS_PER_SCRIPT) {
363
+ throw new Error(`Error: too many non-push ops, ${nonPushOnlyOps} non-push ops is larger than ${MAX_OPS_PER_SCRIPT}`);
364
+ }
365
+ payment = p2wsh({ redeem: { output: script, network }, network });
366
+ }
367
+ }
368
+ else {
369
+ throw new Error(`Error: Could not parse descriptor ${expression}`);
370
+ }
371
+ return {
372
+ ...(payment !== undefined ? { payment } : {}),
373
+ ...(expandedExpression !== undefined ? { expandedExpression } : {}),
374
+ ...(miniscript !== undefined ? { miniscript } : {}),
375
+ ...(expansionMap !== undefined ? { expansionMap } : {}),
376
+ ...(isSegwit !== undefined ? { isSegwit } : {}),
377
+ ...(expandedMiniscript !== undefined ? { expandedMiniscript } : {}),
378
+ ...(redeemScript !== undefined ? { redeemScript } : {}),
379
+ ...(witnessScript !== undefined ? { witnessScript } : {}),
380
+ isRanged,
381
+ canonicalExpression
382
+ };
383
+ };
117
384
  /**
118
385
  * Expand a miniscript to a generalized form using variables instead of key
119
386
  * expressions. Variables will be of this form: @0, @1, ...
@@ -144,7 +411,6 @@ function DescriptorsFactory(ecc) {
144
411
  * @throws {Error} - when descriptor is invalid
145
412
  */
146
413
  constructor({ expression, index, checksumRequired = false, allowMiniscriptInP2SH = false, network = bitcoinjs_lib_1.networks.bitcoin, preimages = [], signersPubKeys }) {
147
- var _a, _b, _c, _d, _e, _f;
148
414
  _Descriptor_instances.add(this);
149
415
  _Descriptor_payment.set(this, void 0);
150
416
  _Descriptor_preimages.set(this, []);
@@ -163,253 +429,47 @@ function DescriptorsFactory(ecc) {
163
429
  __classPrivateFieldSet(this, _Descriptor_preimages, preimages, "f");
164
430
  if (typeof expression !== 'string')
165
431
  throw new Error(`Error: invalid descriptor type`);
166
- //Verify and remove checksum (if exists) and
167
- //particularize range descriptor for index (if desc is range descriptor)
168
- const evaluatedExpression = evaluate({
432
+ const expandedResult = expand({
169
433
  expression,
170
434
  ...(index !== undefined ? { index } : {}),
171
- checksumRequired
435
+ checksumRequired,
436
+ network,
437
+ allowMiniscriptInP2SH
172
438
  });
173
- //addr(ADDR)
174
- if (evaluatedExpression.match(RE.reAddrAnchored)) {
175
- const matchedAddress = evaluatedExpression.match(RE.reAddrAnchored)?.[1]; //[1]-> whatever is found addr(->HERE<-)
176
- if (!matchedAddress)
177
- throw new Error(`Error: could not get an address in ${evaluatedExpression}`);
178
- let output;
179
- let payment;
180
- try {
181
- output = bitcoinjs_lib_1.address.toOutputScript(matchedAddress, network);
182
- }
183
- catch (e) {
184
- throw new Error(`Error: invalid address ${matchedAddress}`);
185
- }
186
- try {
187
- payment = p2pkh({ output, network });
188
- }
189
- catch (e) { }
190
- try {
191
- payment = p2sh({ output, network });
192
- }
193
- catch (e) { }
194
- try {
195
- payment = p2wpkh({ output, network });
196
- }
197
- catch (e) { }
198
- try {
199
- payment = p2wsh({ output, network });
200
- }
201
- catch (e) { }
202
- try {
203
- payment = p2tr({ output, network });
204
- }
205
- catch (e) { }
206
- if (!payment) {
207
- throw new Error(`Error: invalid address ${matchedAddress}`);
208
- }
209
- __classPrivateFieldSet(this, _Descriptor_payment, payment, "f");
210
- }
211
- //pk(KEY)
212
- else if (evaluatedExpression.match(RE.rePkAnchored)) {
213
- __classPrivateFieldSet(this, _Descriptor_isSegwit, false, "f");
214
- const keyExpression = evaluatedExpression.match(RE.reKeyExp)?.[0];
215
- if (!keyExpression)
216
- throw new Error(`Error: keyExpression could not me extracted`);
217
- if (evaluatedExpression !== `pk(${keyExpression})`)
218
- throw new Error(`Error: invalid expression ${expression}`);
219
- __classPrivateFieldSet(this, _Descriptor_expandedExpression, 'pk(@0)', "f");
220
- __classPrivateFieldSet(this, _Descriptor_expansionMap, {
221
- '@0': parseKeyExpression({
222
- keyExpression,
223
- network,
224
- isSegwit: __classPrivateFieldGet(this, _Descriptor_isSegwit, "f")
225
- })
226
- }, "f");
227
- const pubkey = __classPrivateFieldGet(this, _Descriptor_expansionMap, "f")['@0'].pubkey;
228
- //Note there exists no address for p2pk, but we can still use the script
229
- __classPrivateFieldSet(this, _Descriptor_payment, p2pk({ pubkey, network }), "f");
230
- }
231
- //pkh(KEY) - legacy
232
- else if (evaluatedExpression.match(RE.rePkhAnchored)) {
233
- __classPrivateFieldSet(this, _Descriptor_isSegwit, false, "f");
234
- const keyExpression = evaluatedExpression.match(RE.reKeyExp)?.[0];
235
- if (!keyExpression)
236
- throw new Error(`Error: keyExpression could not me extracted`);
237
- if (evaluatedExpression !== `pkh(${keyExpression})`)
238
- throw new Error(`Error: invalid expression ${expression}`);
239
- __classPrivateFieldSet(this, _Descriptor_expandedExpression, 'pkh(@0)', "f");
240
- __classPrivateFieldSet(this, _Descriptor_expansionMap, {
241
- '@0': parseKeyExpression({
242
- keyExpression,
243
- network,
244
- isSegwit: __classPrivateFieldGet(this, _Descriptor_isSegwit, "f")
245
- })
246
- }, "f");
247
- const pubkey = __classPrivateFieldGet(this, _Descriptor_expansionMap, "f")['@0'].pubkey;
248
- __classPrivateFieldSet(this, _Descriptor_payment, p2pkh({ pubkey, network }), "f");
249
- }
250
- //sh(wpkh(KEY)) - nested segwit
251
- else if (evaluatedExpression.match(RE.reShWpkhAnchored)) {
252
- __classPrivateFieldSet(this, _Descriptor_isSegwit, true, "f");
253
- const keyExpression = evaluatedExpression.match(RE.reKeyExp)?.[0];
254
- if (!keyExpression)
255
- throw new Error(`Error: keyExpression could not me extracted`);
256
- if (evaluatedExpression !== `sh(wpkh(${keyExpression}))`)
257
- throw new Error(`Error: invalid expression ${expression}`);
258
- __classPrivateFieldSet(this, _Descriptor_expandedExpression, 'sh(wpkh(@0))', "f");
259
- __classPrivateFieldSet(this, _Descriptor_expansionMap, {
260
- '@0': parseKeyExpression({
261
- keyExpression,
262
- network,
263
- isSegwit: __classPrivateFieldGet(this, _Descriptor_isSegwit, "f")
264
- })
265
- }, "f");
266
- const pubkey = __classPrivateFieldGet(this, _Descriptor_expansionMap, "f")['@0'].pubkey;
267
- __classPrivateFieldSet(this, _Descriptor_payment, p2sh({ redeem: p2wpkh({ pubkey, network }), network }), "f");
268
- const redeemScript = __classPrivateFieldGet(this, _Descriptor_payment, "f").redeem?.output;
269
- if (!redeemScript)
270
- throw new Error(`Error: could not calculate redeemScript for ${expression}`);
271
- __classPrivateFieldSet(this, _Descriptor_redeemScript, redeemScript, "f");
272
- }
273
- //wpkh(KEY) - native segwit
274
- else if (evaluatedExpression.match(RE.reWpkhAnchored)) {
275
- __classPrivateFieldSet(this, _Descriptor_isSegwit, true, "f");
276
- const keyExpression = evaluatedExpression.match(RE.reKeyExp)?.[0];
277
- if (!keyExpression)
278
- throw new Error(`Error: keyExpression could not me extracted`);
279
- if (evaluatedExpression !== `wpkh(${keyExpression})`)
280
- throw new Error(`Error: invalid expression ${expression}`);
281
- __classPrivateFieldSet(this, _Descriptor_expandedExpression, 'wpkh(@0)', "f");
282
- __classPrivateFieldSet(this, _Descriptor_expansionMap, {
283
- '@0': parseKeyExpression({
284
- keyExpression,
285
- network,
286
- isSegwit: __classPrivateFieldGet(this, _Descriptor_isSegwit, "f")
287
- })
288
- }, "f");
289
- const pubkey = __classPrivateFieldGet(this, _Descriptor_expansionMap, "f")['@0'].pubkey;
290
- __classPrivateFieldSet(this, _Descriptor_payment, p2wpkh({ pubkey, network }), "f");
291
- }
292
- //sh(wsh(miniscript))
293
- else if (evaluatedExpression.match(RE.reShWshMiniscriptAnchored)) {
294
- __classPrivateFieldSet(this, _Descriptor_isSegwit, true, "f");
295
- const miniscript = evaluatedExpression.match(RE.reShWshMiniscriptAnchored)?.[1]; //[1]-> whatever is found sh(wsh(->HERE<-))
296
- if (!miniscript)
297
- throw new Error(`Error: could not get miniscript in ${evaluatedExpression}`);
298
- __classPrivateFieldSet(this, _Descriptor_miniscript, miniscript, "f");
299
- (_a = this, _b = this, {
300
- expandedMiniscript: ({ set value(_g) { __classPrivateFieldSet(_a, _Descriptor_expandedMiniscript, _g, "f"); } }).value,
301
- expansionMap: ({ set value(_g) { __classPrivateFieldSet(_b, _Descriptor_expansionMap, _g, "f"); } }).value
302
- } = expandMiniscript({
303
- miniscript,
304
- isSegwit: __classPrivateFieldGet(this, _Descriptor_isSegwit, "f"),
305
- network
306
- }));
307
- __classPrivateFieldSet(this, _Descriptor_expandedExpression, `sh(wsh(${__classPrivateFieldGet(this, _Descriptor_expandedMiniscript, "f")}))`, "f");
308
- const script = (0, miniscript_1.miniscript2Script)({
309
- expandedMiniscript: __classPrivateFieldGet(this, _Descriptor_expandedMiniscript, "f"),
310
- expansionMap: __classPrivateFieldGet(this, _Descriptor_expansionMap, "f")
311
- });
312
- __classPrivateFieldSet(this, _Descriptor_witnessScript, script, "f");
313
- if (script.byteLength > MAX_STANDARD_P2WSH_SCRIPT_SIZE) {
314
- throw new Error(`Error: script is too large, ${script.byteLength} bytes is larger than ${MAX_STANDARD_P2WSH_SCRIPT_SIZE} bytes`);
315
- }
316
- const nonPushOnlyOps = countNonPushOnlyOPs(script);
317
- if (nonPushOnlyOps > MAX_OPS_PER_SCRIPT) {
318
- throw new Error(`Error: too many non-push ops, ${nonPushOnlyOps} non-push ops is larger than ${MAX_OPS_PER_SCRIPT}`);
319
- }
320
- __classPrivateFieldSet(this, _Descriptor_payment, p2sh({
321
- redeem: p2wsh({ redeem: { output: script, network }, network }),
322
- network
323
- }), "f");
324
- const redeemScript = __classPrivateFieldGet(this, _Descriptor_payment, "f").redeem?.output;
325
- if (!redeemScript)
326
- throw new Error(`Error: could not calculate redeemScript for ${expression}`);
327
- __classPrivateFieldSet(this, _Descriptor_redeemScript, redeemScript, "f");
328
- }
329
- //sh(miniscript)
330
- else if (evaluatedExpression.match(RE.reShMiniscriptAnchored)) {
331
- //isSegwit false because we know it's a P2SH of a miniscript and not a
332
- //P2SH that embeds a witness payment.
333
- __classPrivateFieldSet(this, _Descriptor_isSegwit, false, "f");
334
- const miniscript = evaluatedExpression.match(RE.reShMiniscriptAnchored)?.[1]; //[1]-> whatever is found sh(->HERE<-)
335
- if (!miniscript)
336
- throw new Error(`Error: could not get miniscript in ${evaluatedExpression}`);
337
- if (allowMiniscriptInP2SH === false &&
338
- //These top-level expressions within sh are allowed within sh.
339
- //They can be parsed with miniscript2Script, but first we must make sure
340
- //that other expressions are not accepted (unless forced with allowMiniscriptInP2SH).
341
- miniscript.search(/^(pk\(|pkh\(|wpkh\(|combo\(|multi\(|sortedmulti\(|multi_a\(|sortedmulti_a\()/) !== 0) {
342
- throw new Error(`Error: Miniscript expressions can only be used in wsh`);
343
- }
344
- __classPrivateFieldSet(this, _Descriptor_miniscript, miniscript, "f");
345
- (_c = this, _d = this, {
346
- expandedMiniscript: ({ set value(_g) { __classPrivateFieldSet(_c, _Descriptor_expandedMiniscript, _g, "f"); } }).value,
347
- expansionMap: ({ set value(_g) { __classPrivateFieldSet(_d, _Descriptor_expansionMap, _g, "f"); } }).value
348
- } = expandMiniscript({
349
- miniscript,
350
- isSegwit: __classPrivateFieldGet(this, _Descriptor_isSegwit, "f"),
351
- network
352
- }));
353
- __classPrivateFieldSet(this, _Descriptor_expandedExpression, `sh(${__classPrivateFieldGet(this, _Descriptor_expandedMiniscript, "f")})`, "f");
354
- const script = (0, miniscript_1.miniscript2Script)({
355
- expandedMiniscript: __classPrivateFieldGet(this, _Descriptor_expandedMiniscript, "f"),
356
- expansionMap: __classPrivateFieldGet(this, _Descriptor_expansionMap, "f")
357
- });
358
- __classPrivateFieldSet(this, _Descriptor_redeemScript, script, "f");
359
- if (script.byteLength > MAX_SCRIPT_ELEMENT_SIZE) {
360
- throw new Error(`Error: P2SH script is too large, ${script.byteLength} bytes is larger than ${MAX_SCRIPT_ELEMENT_SIZE} bytes`);
361
- }
362
- const nonPushOnlyOps = countNonPushOnlyOPs(script);
363
- if (nonPushOnlyOps > MAX_OPS_PER_SCRIPT) {
364
- throw new Error(`Error: too many non-push ops, ${nonPushOnlyOps} non-push ops is larger than ${MAX_OPS_PER_SCRIPT}`);
365
- }
366
- __classPrivateFieldSet(this, _Descriptor_payment, p2sh({ redeem: { output: script, network }, network }), "f");
367
- if (Buffer.compare(script, this.getRedeemScript()) !== 0)
368
- throw new Error(`Error: redeemScript was not correctly set to the payment in expression ${expression}`);
369
- }
370
- //wsh(miniscript)
371
- else if (evaluatedExpression.match(RE.reWshMiniscriptAnchored)) {
372
- __classPrivateFieldSet(this, _Descriptor_isSegwit, true, "f");
373
- const miniscript = evaluatedExpression.match(RE.reWshMiniscriptAnchored)?.[1]; //[1]-> whatever is found wsh(->HERE<-)
374
- if (!miniscript)
375
- throw new Error(`Error: could not get miniscript in ${evaluatedExpression}`);
376
- __classPrivateFieldSet(this, _Descriptor_miniscript, miniscript, "f");
377
- (_e = this, _f = this, {
378
- expandedMiniscript: ({ set value(_g) { __classPrivateFieldSet(_e, _Descriptor_expandedMiniscript, _g, "f"); } }).value,
379
- expansionMap: ({ set value(_g) { __classPrivateFieldSet(_f, _Descriptor_expansionMap, _g, "f"); } }).value
380
- } = expandMiniscript({
381
- miniscript,
382
- isSegwit: __classPrivateFieldGet(this, _Descriptor_isSegwit, "f"),
383
- network
384
- }));
385
- __classPrivateFieldSet(this, _Descriptor_expandedExpression, `wsh(${__classPrivateFieldGet(this, _Descriptor_expandedMiniscript, "f")})`, "f");
386
- const script = (0, miniscript_1.miniscript2Script)({
387
- expandedMiniscript: __classPrivateFieldGet(this, _Descriptor_expandedMiniscript, "f"),
388
- expansionMap: __classPrivateFieldGet(this, _Descriptor_expansionMap, "f")
389
- });
390
- __classPrivateFieldSet(this, _Descriptor_witnessScript, script, "f");
391
- if (script.byteLength > MAX_STANDARD_P2WSH_SCRIPT_SIZE) {
392
- throw new Error(`Error: script is too large, ${script.byteLength} bytes is larger than ${MAX_STANDARD_P2WSH_SCRIPT_SIZE} bytes`);
393
- }
394
- const nonPushOnlyOps = countNonPushOnlyOPs(script);
395
- if (nonPushOnlyOps > MAX_OPS_PER_SCRIPT) {
396
- throw new Error(`Error: too many non-push ops, ${nonPushOnlyOps} non-push ops is larger than ${MAX_OPS_PER_SCRIPT}`);
397
- }
398
- __classPrivateFieldSet(this, _Descriptor_payment, p2wsh({ redeem: { output: script, network }, network }), "f");
399
- }
400
- else {
401
- throw new Error(`Error: Could not parse descriptor ${expression}`);
402
- }
439
+ if (expandedResult.isRanged && index === undefined)
440
+ throw new Error(`Error: index was not provided for ranged descriptor`);
441
+ if (!expandedResult.payment)
442
+ throw new Error(`Error: could not extract a payment from ${expression}`);
443
+ __classPrivateFieldSet(this, _Descriptor_payment, expandedResult.payment, "f");
444
+ if (expandedResult.expandedExpression !== undefined)
445
+ __classPrivateFieldSet(this, _Descriptor_expandedExpression, expandedResult.expandedExpression, "f");
446
+ if (expandedResult.miniscript !== undefined)
447
+ __classPrivateFieldSet(this, _Descriptor_miniscript, expandedResult.miniscript, "f");
448
+ if (expandedResult.expansionMap !== undefined)
449
+ __classPrivateFieldSet(this, _Descriptor_expansionMap, expandedResult.expansionMap, "f");
450
+ if (expandedResult.isSegwit !== undefined)
451
+ __classPrivateFieldSet(this, _Descriptor_isSegwit, expandedResult.isSegwit, "f");
452
+ if (expandedResult.expandedMiniscript !== undefined)
453
+ __classPrivateFieldSet(this, _Descriptor_expandedMiniscript, expandedResult.expandedMiniscript, "f");
454
+ if (expandedResult.redeemScript !== undefined)
455
+ __classPrivateFieldSet(this, _Descriptor_redeemScript, expandedResult.redeemScript, "f");
456
+ if (expandedResult.witnessScript !== undefined)
457
+ __classPrivateFieldSet(this, _Descriptor_witnessScript, expandedResult.witnessScript, "f");
403
458
  if (signersPubKeys) {
404
459
  __classPrivateFieldSet(this, _Descriptor_signersPubKeys, signersPubKeys, "f");
405
460
  }
406
461
  else {
407
462
  if (__classPrivateFieldGet(this, _Descriptor_expansionMap, "f")) {
408
- __classPrivateFieldSet(this, _Descriptor_signersPubKeys, Object.values(__classPrivateFieldGet(this, _Descriptor_expansionMap, "f")).map(keyInfo => keyInfo.pubkey), "f");
463
+ __classPrivateFieldSet(this, _Descriptor_signersPubKeys, Object.values(__classPrivateFieldGet(this, _Descriptor_expansionMap, "f")).map(keyInfo => {
464
+ const pubkey = keyInfo.pubkey;
465
+ if (!pubkey)
466
+ throw new Error(`Error: could not extract a pubkey from ${expression}`);
467
+ return pubkey;
468
+ }), "f");
409
469
  }
410
470
  else {
411
471
  //We should only miss expansionMap in addr() expressions:
412
- if (!evaluatedExpression.match(RE.reAddrAnchored)) {
472
+ if (!expandedResult.canonicalExpression.match(RE.reAddrAnchored)) {
413
473
  throw new Error(`Error: expansionMap not available for expression ${expression} that is not an address`);
414
474
  }
415
475
  __classPrivateFieldSet(this, _Descriptor_signersPubKeys, [this.getScriptPubKey()], "f");
@@ -631,6 +691,6 @@ function DescriptorsFactory(ecc) {
631
691
  throw new Error(`Error: cannot finalize psbt index ${index} since it does not correspond to this descriptor`);
632
692
  }
633
693
  };
634
- return { Descriptor, parseKeyExpression, ECPair, BIP32 };
694
+ return { Descriptor, parseKeyExpression, expand, ECPair, BIP32 };
635
695
  }
636
696
  exports.DescriptorsFactory = DescriptorsFactory;
@@ -44,17 +44,17 @@ const derivePath = (node, path) => {
44
44
  return node.derivePath(parsedPath);
45
45
  };
46
46
  /*
47
- * Takes a key expression (xpub, xprv, pubkey or wif) and returns a pubkey in
48
- * binary format
47
+ * Parses a key expression (xpub, xprv, pubkey or wif) into KeyInfo
49
48
  */
50
49
  function parseKeyExpression({ keyExpression, isSegwit, ECPair, BIP32, network = bitcoinjs_lib_1.networks.bitcoin }) {
51
- let pubkey;
50
+ let pubkey; //won't be computed for ranged keyExpressions
52
51
  let ecpair;
53
52
  let bip32;
54
53
  let masterFingerprint;
55
54
  let originPath;
56
55
  let keyPath;
57
56
  let path;
57
+ const isRanged = keyExpression.indexOf('*') !== -1;
58
58
  //Validate the keyExpression:
59
59
  const keyExpressions = keyExpression.match(RE.reKeyExp);
60
60
  if (keyExpressions === null || keyExpressions[0] !== keyExpression) {
@@ -115,7 +115,8 @@ function parseKeyExpression({ keyExpression, isSegwit, ECPair, BIP32, network =
115
115
  if (!keyPath)
116
116
  throw new Error(`Error: could not extract a path`);
117
117
  //fromBase58 and derivePath will throw if xPub or path are not valid
118
- pubkey = derivePath(bip32, keyPath).publicKey;
118
+ if (!isRanged)
119
+ pubkey = derivePath(bip32, keyPath).publicKey;
119
120
  }
120
121
  else {
121
122
  pubkey = bip32.publicKey;
@@ -134,7 +135,8 @@ function parseKeyExpression({ keyExpression, isSegwit, ECPair, BIP32, network =
134
135
  if (!keyPath)
135
136
  throw new Error(`Error: could not extract a path`);
136
137
  //fromBase58 and derivePath will throw if xPrv or path are not valid
137
- pubkey = derivePath(bip32, keyPath).publicKey;
138
+ if (!isRanged)
139
+ pubkey = derivePath(bip32, keyPath).publicKey;
138
140
  }
139
141
  else {
140
142
  pubkey = bip32.publicKey;
@@ -143,12 +145,12 @@ function parseKeyExpression({ keyExpression, isSegwit, ECPair, BIP32, network =
143
145
  else {
144
146
  throw new Error(`Error: could not get pubkey for keyExpression ${keyExpression}`);
145
147
  }
146
- if (masterFingerprint && (originPath || keyPath)) {
147
- path = 'm' + originPath + keyPath;
148
+ if (originPath || keyPath) {
149
+ path = `m${originPath ?? ''}${keyPath ?? ''}`;
148
150
  }
149
151
  return {
150
- pubkey,
151
152
  keyExpression,
153
+ ...(pubkey !== undefined ? { pubkey } : {}),
152
154
  ...(ecpair !== undefined ? { ecpair } : {}),
153
155
  ...(bip32 !== undefined ? { bip32 } : {}),
154
156
  ...(masterFingerprint !== undefined ? { masterFingerprint } : {}),
@@ -51,8 +51,11 @@ function expandMiniscript({ miniscript, isSegwit, network = bitcoinjs_lib_1.netw
51
51
  return key;
52
52
  });
53
53
  //Do some assertions. Miniscript must not have duplicate keys, also all
54
- //keyExpressions must produce a valid pubkey
55
- const pubkeysHex = Object.values(expansionMap).map(keyInfo => {
54
+ //keyExpressions must produce a valid pubkey (unless it's ranged and we want
55
+ //to expand a generalized form, then we don't check)
56
+ const pubkeysHex = Object.values(expansionMap)
57
+ .filter(keyInfo => keyInfo.keyExpression.indexOf('*') === -1)
58
+ .map(keyInfo => {
56
59
  if (!keyInfo.pubkey)
57
60
  throw new Error(`Error: keyExpression ${keyInfo.keyExpression} does not have a pubkey`);
58
61
  return keyInfo.pubkey.toString('hex');
@@ -133,7 +136,7 @@ function satisfyMiniscript({ expandedMiniscript, expansionMap, signatures = [],
133
136
  const expandedSignatureMap = {};
134
137
  signatures.forEach(signature => {
135
138
  const pubkeyHex = signature.pubkey.toString('hex');
136
- const keyExpression = Object.keys(expansionMap).find(k => expansionMap[k]?.pubkey.toString('hex') === pubkeyHex);
139
+ const keyExpression = Object.keys(expansionMap).find(k => expansionMap[k]?.pubkey?.toString('hex') === pubkeyHex);
137
140
  expandedSignatureMap['<sig(' + keyExpression + ')>'] =
138
141
  '<' + signature.signature.toString('hex') + '>';
139
142
  });
package/dist/psbt.js CHANGED
@@ -168,11 +168,16 @@ function updatePsbt({ psbt, vout, txHex, txId, value, sequence, locktime, keysIn
168
168
  }
169
169
  const bip32Derivation = keysInfo
170
170
  .filter((keyInfo) => keyInfo.pubkey && keyInfo.masterFingerprint && keyInfo.path)
171
- .map((keyInfo) => ({
172
- masterFingerprint: keyInfo.masterFingerprint,
173
- pubkey: keyInfo.pubkey,
174
- path: keyInfo.path
175
- }));
171
+ .map((keyInfo) => {
172
+ const pubkey = keyInfo.pubkey;
173
+ if (!pubkey)
174
+ throw new Error(`key ${keyInfo.keyExpression} missing pubkey`);
175
+ return {
176
+ masterFingerprint: keyInfo.masterFingerprint,
177
+ pubkey,
178
+ path: keyInfo.path
179
+ };
180
+ });
176
181
  if (bip32Derivation.length)
177
182
  input.bip32Derivation = bip32Derivation;
178
183
  if (isSegwit && txHex !== undefined) {
package/dist/types.d.ts CHANGED
@@ -26,7 +26,7 @@ export type TimeConstraints = {
26
26
  };
27
27
  export type KeyInfo = {
28
28
  keyExpression: string;
29
- pubkey: Buffer;
29
+ pubkey?: Buffer;
30
30
  ecpair?: ECPairInterface;
31
31
  bip32?: BIP32Interface;
32
32
  masterFingerprint?: Buffer;
@@ -44,6 +44,26 @@ export interface ParseKeyExpression {
44
44
  network?: Network;
45
45
  }): KeyInfo;
46
46
  }
47
+ export interface Expand {
48
+ (params: {
49
+ expression: string;
50
+ index?: number;
51
+ checksumRequired?: boolean;
52
+ network?: Network;
53
+ allowMiniscriptInP2SH?: boolean;
54
+ }): {
55
+ payment?: Payment;
56
+ expandedExpression?: string;
57
+ miniscript?: string;
58
+ expansionMap?: ExpansionMap;
59
+ isSegwit?: boolean;
60
+ expandedMiniscript?: string;
61
+ redeemScript?: Buffer;
62
+ witnessScript?: Buffer;
63
+ isRanged: boolean;
64
+ canonicalExpression: string;
65
+ };
66
+ }
47
67
  interface XOnlyPointAddTweakResult {
48
68
  parity: 1 | 0;
49
69
  xOnlyPubkey: Uint8Array;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@bitcoinerlab/descriptors",
3
3
  "homepage": "https://github.com/bitcoinerlab/descriptors",
4
- "version": "0.2.2",
4
+ "version": "0.3.1",
5
5
  "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.",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",
@@ -64,8 +64,8 @@
64
64
  "devDependencies": {
65
65
  "@babel/plugin-transform-modules-commonjs": "^7.20.11",
66
66
  "@ledgerhq/hw-transport-node-hid": "^6.27.12",
67
- "@typescript-eslint/eslint-plugin": "^5.53.0",
68
- "@typescript-eslint/parser": "^5.53.0",
67
+ "@typescript-eslint/eslint-plugin": "^5.60.1",
68
+ "@typescript-eslint/parser": "^5.60.1",
69
69
  "babel-plugin-transform-import-meta": "^2.2.0",
70
70
  "better-docs": "^2.7.2",
71
71
  "bip39": "^3.0.4",
@@ -78,8 +78,9 @@
78
78
  "jest": "^29.4.3",
79
79
  "jsdoc": "^3.6.11",
80
80
  "path": "^0.12.7",
81
- "prettier": "^2.8.4",
81
+ "prettier": "^2.8.8",
82
82
  "regtest-client": "^0.2.0",
83
- "ts-node-dev": "^2.0.0"
83
+ "ts-node-dev": "^2.0.0",
84
+ "typescript": "5.0"
84
85
  }
85
86
  }