@bitcoinerlab/descriptors 2.3.4 → 2.3.6
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/dist/applyPR2137.js +10 -0
- package/dist/descriptors.js +107 -0
- package/dist/re.d.ts +5 -0
- package/dist/re.js +6 -1
- package/dist/signers.js +9 -4
- package/package.json +4 -4
package/dist/applyPR2137.js
CHANGED
|
@@ -55,6 +55,16 @@ function getTweakSignersFromHD(inputIndex, inputs, hdKeyPair) {
|
|
|
55
55
|
if (!bipDv.pubkey.equals(toXOnly(node.publicKey))) {
|
|
56
56
|
throw new Error('pubkey did not match tapBip32Derivation');
|
|
57
57
|
}
|
|
58
|
+
//FIX BITCOINERLAB:
|
|
59
|
+
//The 3 lines below detect script-path spends and disable key-path tweaking.
|
|
60
|
+
//Reasoning:
|
|
61
|
+
//- In Taproot, key-path spends require tweaking the internal key.
|
|
62
|
+
//- Script-path spends MUST NOT tweak the key; signatures use the raw internal key.
|
|
63
|
+
const input = inputs[inputIndex];
|
|
64
|
+
if (!input)
|
|
65
|
+
throw new Error('could not find the input');
|
|
66
|
+
if (input.tapLeafScript && input.tapLeafScript.length > 0)
|
|
67
|
+
return node;
|
|
58
68
|
const h = calculateScriptTreeMerkleRoot(bipDv.leafHashes);
|
|
59
69
|
const tweakValue = (0, bip341_1.tapTweakHash)(toXOnly(node.publicKey), h);
|
|
60
70
|
return node.tweak(tweakValue);
|
package/dist/descriptors.js
CHANGED
|
@@ -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/dist/signers.js
CHANGED
|
@@ -34,15 +34,20 @@ function range(n) {
|
|
|
34
34
|
*/
|
|
35
35
|
function signInputECPair({ psbt, index, ecpair }) {
|
|
36
36
|
//psbt.signInput(index, ecpair); <- Replaced for the code below
|
|
37
|
-
//that can handle
|
|
37
|
+
//that can handle taproot inputs automatically.
|
|
38
38
|
//See https://github.com/bitcoinjs/bitcoinjs-lib/pull/2137#issuecomment-2713264848
|
|
39
39
|
const input = psbt.data.inputs[index];
|
|
40
40
|
if (!input)
|
|
41
41
|
throw new Error('Invalid index');
|
|
42
42
|
if ((0, bip371_1.isTaprootInput)(input)) {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
43
|
+
// If script-path (tapLeafScript present) -> DO NOT TWEAK
|
|
44
|
+
if (input.tapLeafScript && input.tapLeafScript.length > 0)
|
|
45
|
+
psbt.signInput(index, ecpair);
|
|
46
|
+
else {
|
|
47
|
+
const hash = (0, bip341_1.tapTweakHash)(Buffer.from(ecpair.publicKey.slice(1, 33)), undefined);
|
|
48
|
+
const tweakedEcpair = ecpair.tweak(hash);
|
|
49
|
+
psbt.signInput(index, tweakedEcpair);
|
|
50
|
+
}
|
|
46
51
|
}
|
|
47
52
|
else
|
|
48
53
|
psbt.signInput(index, ecpair);
|
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.
|
|
5
|
+
"version": "2.3.6",
|
|
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 \"
|
|
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 \"
|
|
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 \"
|
|
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"
|