@bitcoinerlab/descriptors 2.3.4 → 2.3.5

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.
@@ -132,6 +132,22 @@ function evaluate({ descriptor, checksumRequired, index }) {
132
132
  }
133
133
  return evaluatedDescriptor;
134
134
  }
135
+ // Helper: parse sortedmulti(M, k1, k2,...)
136
+ function parseSortedMulti(inner) {
137
+ // inner: "2,key1,key2,key3"
138
+ const parts = inner.split(',').map(p => p.trim());
139
+ if (parts.length < 2)
140
+ throw new Error(`sortedmulti(): must contain M and at least one key: ${inner}`);
141
+ const m = Number(parts[0]);
142
+ if (!Number.isInteger(m) || m < 1 || m > 20)
143
+ throw new Error(`sortedmulti(): invalid M=${parts[0]}`);
144
+ const keyExpressions = parts.slice(1);
145
+ if (keyExpressions.length < m)
146
+ throw new Error(`sortedmulti(): M cannot exceed number of keys: ${inner}`);
147
+ if (keyExpressions.length > 20)
148
+ throw new Error(`sortedmulti(): descriptors support up to 20 keys (per BIP 380/383).`);
149
+ return { m, keyExpressions };
150
+ }
135
151
  /**
136
152
  * Constructs the necessary functions and classes for working with descriptors
137
153
  * using an external elliptic curve (ecc) library.
@@ -359,6 +375,97 @@ function DescriptorsFactory(ecc) {
359
375
  payment = p2wpkh({ pubkey, network });
360
376
  }
361
377
  }
378
+ // sortedmulti script expressions
379
+ // sh(sortedmulti())
380
+ else if (canonicalExpression.match(RE.reShSortedMultiAnchored)) {
381
+ isSegwit = false;
382
+ isTaproot = false;
383
+ const inner = canonicalExpression.match(RE.reShSortedMultiAnchored)?.[1];
384
+ if (!inner)
385
+ throw new Error(`Error extracting sortedmulti() in ${descriptor}`);
386
+ const { m, keyExpressions } = parseSortedMulti(inner);
387
+ const pKEs = keyExpressions.map(k => parseKeyExpression({ keyExpression: k, network, isSegwit: false }));
388
+ const map = {};
389
+ pKEs.forEach((pke, i) => (map['@' + i] = pke));
390
+ expansionMap = map;
391
+ expandedExpression =
392
+ 'sh(sortedmulti(' +
393
+ [m, ...Object.keys(expansionMap).map(k => k)].join(',') +
394
+ '))';
395
+ if (!isCanonicalRanged) {
396
+ const pubkeys = pKEs.map(p => {
397
+ if (!p.pubkey)
398
+ throw new Error(`Error: key has no pubkey`);
399
+ return p.pubkey;
400
+ });
401
+ pubkeys.sort((a, b) => a.compare(b));
402
+ const redeem = bitcoinjs_lib_1.payments.p2ms({ m, pubkeys, network });
403
+ redeemScript = redeem.output;
404
+ if (!redeemScript)
405
+ throw new Error(`Error creating redeemScript`);
406
+ payment = bitcoinjs_lib_1.payments.p2sh({ redeem, network });
407
+ }
408
+ }
409
+ // wsh(sortedmulti())
410
+ else if (canonicalExpression.match(RE.reWshSortedMultiAnchored)) {
411
+ isSegwit = true;
412
+ isTaproot = false;
413
+ const inner = canonicalExpression.match(RE.reWshSortedMultiAnchored)?.[1];
414
+ if (!inner)
415
+ throw new Error(`Error extracting sortedmulti() in ${descriptor}`);
416
+ const { m, keyExpressions } = parseSortedMulti(inner);
417
+ const pKEs = keyExpressions.map(k => parseKeyExpression({ keyExpression: k, network, isSegwit: true }));
418
+ const map = {};
419
+ pKEs.forEach((pke, i) => (map['@' + i] = pke));
420
+ expansionMap = map;
421
+ expandedExpression =
422
+ 'wsh(sortedmulti(' +
423
+ [m, ...Object.keys(expansionMap).map(k => k)].join(',') +
424
+ '))';
425
+ if (!isCanonicalRanged) {
426
+ const pubkeys = pKEs.map(p => {
427
+ if (!p.pubkey)
428
+ throw new Error(`Error: key has no pubkey`);
429
+ return p.pubkey;
430
+ });
431
+ pubkeys.sort((a, b) => a.compare(b));
432
+ const redeem = bitcoinjs_lib_1.payments.p2ms({ m, pubkeys, network });
433
+ witnessScript = redeem.output;
434
+ if (!witnessScript)
435
+ throw new Error(`Error computing witnessScript`);
436
+ payment = bitcoinjs_lib_1.payments.p2wsh({ redeem, network });
437
+ }
438
+ }
439
+ // sh(wsh(sortedmulti()))
440
+ else if (canonicalExpression.match(RE.reShWshSortedMultiAnchored)) {
441
+ isSegwit = true;
442
+ isTaproot = false;
443
+ const inner = canonicalExpression.match(RE.reShWshSortedMultiAnchored)?.[1];
444
+ if (!inner)
445
+ throw new Error(`Error extracting sortedmulti() in ${descriptor}`);
446
+ const { m, keyExpressions } = parseSortedMulti(inner);
447
+ const pKEs = keyExpressions.map(k => parseKeyExpression({ keyExpression: k, network, isSegwit: true }));
448
+ const map = {};
449
+ pKEs.forEach((pke, i) => (map['@' + i] = pke));
450
+ expansionMap = map;
451
+ expandedExpression =
452
+ 'sh(wsh(sortedmulti(' +
453
+ [m, ...Object.keys(expansionMap).map(k => k)].join(',') +
454
+ ')))';
455
+ if (!isCanonicalRanged) {
456
+ const pubkeys = pKEs.map(p => {
457
+ if (!p.pubkey)
458
+ throw new Error(`Error: key has no pubkey`);
459
+ return p.pubkey;
460
+ });
461
+ pubkeys.sort((a, b) => a.compare(b));
462
+ const redeem = bitcoinjs_lib_1.payments.p2ms({ m, pubkeys, network });
463
+ const wsh = bitcoinjs_lib_1.payments.p2wsh({ redeem, network });
464
+ witnessScript = redeem.output;
465
+ redeemScript = wsh.output;
466
+ payment = bitcoinjs_lib_1.payments.p2sh({ redeem: wsh, network });
467
+ }
468
+ }
362
469
  //sh(wsh(miniscript))
363
470
  else if (canonicalExpression.match(RE.reShWshMiniscriptAnchored)) {
364
471
  isSegwit = true;
package/dist/re.d.ts CHANGED
@@ -14,6 +14,8 @@ export declare const reXprvKey: string;
14
14
  export declare const reNonSegwitKeyExp: string;
15
15
  export declare const reSegwitKeyExp: string;
16
16
  export declare const reTaprootKeyExp: string;
17
+ export declare const reNonSegwitSortedMulti: string;
18
+ export declare const reSegwitSortedMulti: string;
17
19
  export declare const anchorStartAndEnd: (re: string) => string;
18
20
  export declare const rePkAnchored: string;
19
21
  export declare const reAddrAnchored: string;
@@ -21,6 +23,9 @@ export declare const rePkhAnchored: string;
21
23
  export declare const reWpkhAnchored: string;
22
24
  export declare const reShWpkhAnchored: string;
23
25
  export declare const reTrSingleKeyAnchored: string;
26
+ export declare const reShSortedMultiAnchored: string;
27
+ export declare const reWshSortedMultiAnchored: string;
28
+ export declare const reShWshSortedMultiAnchored: string;
24
29
  export declare const reShMiniscriptAnchored: string;
25
30
  export declare const reShWshMiniscriptAnchored: string;
26
31
  export declare const reWshMiniscriptAnchored: string;
package/dist/re.js CHANGED
@@ -2,7 +2,7 @@
2
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.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;
5
+ exports.reWshMiniscriptAnchored = exports.reShWshMiniscriptAnchored = exports.reShMiniscriptAnchored = exports.reShWshSortedMultiAnchored = exports.reWshSortedMultiAnchored = exports.reShSortedMultiAnchored = exports.reTrSingleKeyAnchored = exports.reShWpkhAnchored = exports.reWpkhAnchored = exports.rePkhAnchored = exports.reAddrAnchored = exports.rePkAnchored = exports.anchorStartAndEnd = exports.reSegwitSortedMulti = exports.reNonSegwitSortedMulti = 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
@@ -55,6 +55,8 @@ const rePkh = String.raw `pkh\(${exports.reNonSegwitKeyExp}\)`;
55
55
  const reWpkh = String.raw `wpkh\(${exports.reSegwitKeyExp}\)`;
56
56
  const reShWpkh = String.raw `sh\(wpkh\(${exports.reSegwitKeyExp}\)\)`;
57
57
  const reTrSingleKey = String.raw `tr\(${exports.reTaprootKeyExp}\)`; // TODO: tr(KEY,TREE) not yet supported. TrSingleKey used for tr(KEY)
58
+ exports.reNonSegwitSortedMulti = String.raw `sortedmulti\(((?:1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20)(?:,${exports.reNonSegwitKeyExp})+)\)`;
59
+ exports.reSegwitSortedMulti = String.raw `sortedmulti\(((?:1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20)(?:,${exports.reSegwitKeyExp})+)\)`;
58
60
  const reMiniscript = String.raw `(.*?)`; //Matches anything. We assert later in the code that miniscripts are valid and sane.
59
61
  //RegExp makers:
60
62
  const makeReSh = (re) => String.raw `sh\(${re}\)`;
@@ -69,6 +71,9 @@ exports.rePkhAnchored = (0, exports.anchorStartAndEnd)(composeChecksum(rePkh));
69
71
  exports.reWpkhAnchored = (0, exports.anchorStartAndEnd)(composeChecksum(reWpkh));
70
72
  exports.reShWpkhAnchored = (0, exports.anchorStartAndEnd)(composeChecksum(reShWpkh));
71
73
  exports.reTrSingleKeyAnchored = (0, exports.anchorStartAndEnd)(composeChecksum(reTrSingleKey));
74
+ exports.reShSortedMultiAnchored = (0, exports.anchorStartAndEnd)(composeChecksum(makeReSh(exports.reNonSegwitSortedMulti)));
75
+ exports.reWshSortedMultiAnchored = (0, exports.anchorStartAndEnd)(composeChecksum(makeReWsh(exports.reSegwitSortedMulti)));
76
+ exports.reShWshSortedMultiAnchored = (0, exports.anchorStartAndEnd)(composeChecksum(makeReShWsh(exports.reSegwitSortedMulti)));
72
77
  exports.reShMiniscriptAnchored = (0, exports.anchorStartAndEnd)(composeChecksum(makeReSh(reMiniscript)));
73
78
  exports.reShWshMiniscriptAnchored = (0, exports.anchorStartAndEnd)(composeChecksum(makeReShWsh(reMiniscript)));
74
79
  exports.reWshMiniscriptAnchored = (0, exports.anchorStartAndEnd)(composeChecksum(makeReWsh(reMiniscript)));
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.3.4",
5
+ "version": "2.3.5",
6
6
  "author": "Jose-Luis Landabaso",
7
7
  "license": "MIT",
8
8
  "repository": {
@@ -36,13 +36,13 @@
36
36
  "build": "npm run build:src && npm run build:test",
37
37
  "lint": "./node_modules/@bitcoinerlab/configs/scripts/lint.sh",
38
38
  "ensureTester": "./node_modules/@bitcoinerlab/configs/scripts/ensureTester.sh",
39
- "test:integration:soft": "npm run ensureTester && node test/integration/standardOutputs.js && echo \"\\n\\n\" && node test/integration/miniscript.js",
39
+ "test:integration:soft": "npm run ensureTester && node test/integration/standardOutputs.js && echo \"\n\n\" && node test/integration/miniscript.js && echo \"\n\n\" && node test/integration/sortedmulti.js",
40
40
  "test:integration:ledger": "npm run ensureTester && node test/integration/ledger.js",
41
- "test:integration:deprecated": "npm run ensureTester && node test/integration/standardOutputs-deprecated.js && echo \"\\n\\n\" && node test/integration/miniscript-deprecated.js && echo \"\\n\\n\" && node test/integration/ledger-deprecated.js",
41
+ "test:integration:deprecated": "npm run ensureTester && node test/integration/standardOutputs-deprecated.js && echo \"\n\n\" && node test/integration/miniscript-deprecated.js && echo \"\n\n\" && node test/integration/ledger-deprecated.js",
42
42
  "test:unit": "jest",
43
43
  "test": "npm run lint && npm run build && npm run test:unit && npm run test:integration:soft",
44
44
  "testledger": "npm run lint && npm run build && npm run test:integration:ledger",
45
- "prepublishOnly": "npm run test && echo \"\\n\\n\" && npm run test:integration:deprecated && npm run test:integration:ledger"
45
+ "prepublishOnly": "npm run test && echo \"\n\n\" && npm run test:integration:deprecated && npm run test:integration:ledger"
46
46
  },
47
47
  "files": [
48
48
  "dist"