@bitcoinerlab/descriptors 2.3.6 → 3.0.0
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 +173 -77
- package/dist/applyPR2137.js +36 -17
- package/dist/bitcoinjs-lib-internals.d.ts +10 -0
- package/dist/bitcoinjs-lib-internals.js +18 -0
- package/dist/descriptors.d.ts +161 -392
- package/dist/descriptors.js +512 -281
- package/dist/index.d.ts +2 -29
- package/dist/index.js +0 -14
- package/dist/keyExpressions.d.ts +4 -13
- package/dist/keyExpressions.js +15 -18
- package/dist/ledger.d.ts +14 -37
- package/dist/ledger.js +118 -100
- package/dist/miniscript.d.ts +20 -6
- package/dist/miniscript.js +59 -17
- package/dist/multipath.d.ts +13 -0
- package/dist/multipath.js +76 -0
- package/dist/networkUtils.d.ts +3 -0
- package/dist/networkUtils.js +16 -0
- package/dist/parseUtils.d.ts +7 -0
- package/dist/parseUtils.js +46 -0
- package/dist/psbt.d.ts +17 -13
- package/dist/psbt.js +34 -50
- package/dist/resourceLimits.d.ts +25 -0
- package/dist/resourceLimits.js +89 -0
- package/dist/scriptExpressions.d.ts +29 -77
- package/dist/scriptExpressions.js +19 -16
- package/dist/signers.d.ts +1 -21
- package/dist/signers.js +85 -129
- package/dist/stackResourceLimits.d.ts +17 -0
- package/dist/stackResourceLimits.js +35 -0
- package/dist/tapMiniscript.d.ts +193 -0
- package/dist/tapMiniscript.js +428 -0
- package/dist/tapTree.d.ts +76 -0
- package/dist/tapTree.js +163 -0
- package/dist/types.d.ts +46 -6
- package/package.json +13 -13
package/dist/descriptors.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
// Copyright (c)
|
|
2
|
+
// Copyright (c) 2026 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;
|
|
@@ -50,9 +50,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
50
50
|
};
|
|
51
51
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
52
52
|
exports.DescriptorsFactory = DescriptorsFactory;
|
|
53
|
-
const lodash_memoize_1 = __importDefault(require("lodash.memoize"));
|
|
53
|
+
const lodash_memoize_1 = __importDefault(require("lodash.memoize")); //TODO: make sure this is propoely used
|
|
54
54
|
const bitcoinjs_lib_1 = require("bitcoinjs-lib");
|
|
55
|
+
const bitcoinjs_lib_internals_1 = require("./bitcoinjs-lib-internals");
|
|
55
56
|
const varuint_bitcoin_1 = require("varuint-bitcoin");
|
|
57
|
+
const uint8array_tools_1 = require("uint8array-tools");
|
|
56
58
|
const { p2sh, p2wpkh, p2pkh, p2pk, p2wsh, p2tr } = bitcoinjs_lib_1.payments;
|
|
57
59
|
const bip32_1 = require("bip32");
|
|
58
60
|
const ecpair_1 = require("ecpair");
|
|
@@ -61,17 +63,13 @@ const checksum_1 = require("./checksum");
|
|
|
61
63
|
const keyExpressions_1 = require("./keyExpressions");
|
|
62
64
|
const RE = __importStar(require("./re"));
|
|
63
65
|
const miniscript_1 = require("./miniscript");
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
const
|
|
67
|
-
const
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
if (!decompile)
|
|
72
|
-
throw new Error(`Error: cound not decompile ${script}`);
|
|
73
|
-
return decompile.filter(op => typeof op === 'number' && op > bitcoinjs_lib_1.script.OPS['OP_16']).length;
|
|
74
|
-
}
|
|
66
|
+
const tapTree_1 = require("./tapTree");
|
|
67
|
+
const tapMiniscript_1 = require("./tapMiniscript");
|
|
68
|
+
const parseUtils_1 = require("./parseUtils");
|
|
69
|
+
const multipath_1 = require("./multipath");
|
|
70
|
+
const resourceLimits_1 = require("./resourceLimits");
|
|
71
|
+
const ECDSA_FAKE_SIGNATURE_SIZE = 72;
|
|
72
|
+
const TAPROOT_FAKE_SIGNATURE_SIZE = 64;
|
|
75
73
|
function vectorSize(someVector) {
|
|
76
74
|
const length = someVector.length;
|
|
77
75
|
return ((0, varuint_bitcoin_1.encodingLength)(length) +
|
|
@@ -83,23 +81,12 @@ function varSliceSize(someScript) {
|
|
|
83
81
|
const length = someScript.length;
|
|
84
82
|
return (0, varuint_bitcoin_1.encodingLength)(length) + length;
|
|
85
83
|
}
|
|
86
|
-
/**
|
|
87
|
-
* This function will typically return 73; since it assumes a signature size of
|
|
88
|
-
* 72 bytes (this is the max size of a DER encoded signature) and it adds 1
|
|
89
|
-
* extra byte for encoding its length
|
|
90
|
-
*/
|
|
91
|
-
function signatureSize(signature) {
|
|
92
|
-
const length = signature === 'DANGEROUSLY_USE_FAKE_SIGNATURES'
|
|
93
|
-
? 72
|
|
94
|
-
: signature.signature.length;
|
|
95
|
-
return (0, varuint_bitcoin_1.encodingLength)(length) + length;
|
|
96
|
-
}
|
|
97
84
|
/*
|
|
98
85
|
* Returns a bare descriptor without checksum and particularized for a certain
|
|
99
86
|
* index (if desc was a range descriptor)
|
|
100
87
|
* @hidden
|
|
101
88
|
*/
|
|
102
|
-
function evaluate({ descriptor, checksumRequired, index }) {
|
|
89
|
+
function evaluate({ descriptor, checksumRequired, index, change }) {
|
|
103
90
|
if (!descriptor)
|
|
104
91
|
throw new Error('You must provide a descriptor.');
|
|
105
92
|
const mChecksum = descriptor.match(String.raw `(${RE.reChecksum})$`);
|
|
@@ -115,6 +102,10 @@ function evaluate({ descriptor, checksumRequired, index }) {
|
|
|
115
102
|
throw new Error(`Error: invalid descriptor checksum for ${descriptor}`);
|
|
116
103
|
}
|
|
117
104
|
}
|
|
105
|
+
evaluatedDescriptor = (0, multipath_1.resolveMultipathDescriptor)({
|
|
106
|
+
descriptor: evaluatedDescriptor,
|
|
107
|
+
...(change !== undefined ? { change } : {})
|
|
108
|
+
});
|
|
118
109
|
if (index !== undefined) {
|
|
119
110
|
const mWildcard = evaluatedDescriptor.match(/\*/g);
|
|
120
111
|
if (mWildcard && mWildcard.length > 0) {
|
|
@@ -148,6 +139,21 @@ function parseSortedMulti(inner) {
|
|
|
148
139
|
throw new Error(`sortedmulti(): descriptors support up to 20 keys (per BIP 380/383).`);
|
|
149
140
|
return { m, keyExpressions };
|
|
150
141
|
}
|
|
142
|
+
function parseTrExpression(expression) {
|
|
143
|
+
if (!expression.startsWith('tr(') || !expression.endsWith(')'))
|
|
144
|
+
throw new Error(`Error: invalid descriptor ${expression}`);
|
|
145
|
+
const innerExpression = expression.slice(3, -1).trim();
|
|
146
|
+
if (!innerExpression)
|
|
147
|
+
throw new Error(`Error: invalid descriptor ${expression}`);
|
|
148
|
+
const splitResult = (0, parseUtils_1.splitTopLevelComma)({
|
|
149
|
+
expression: innerExpression,
|
|
150
|
+
onError: () => new Error(`Error: invalid descriptor ${expression}`)
|
|
151
|
+
});
|
|
152
|
+
//if no commas: innerExpression === keyExpression
|
|
153
|
+
if (!splitResult)
|
|
154
|
+
return { keyExpression: innerExpression };
|
|
155
|
+
return { keyExpression: splitResult.left, treeExpression: splitResult.right };
|
|
156
|
+
}
|
|
151
157
|
/**
|
|
152
158
|
* Constructs the necessary functions and classes for working with descriptors
|
|
153
159
|
* using an external elliptic curve (ecc) library.
|
|
@@ -156,12 +162,8 @@ function parseSortedMulti(inner) {
|
|
|
156
162
|
* provides methods to create, sign, and finalize PSBTs based on descriptor
|
|
157
163
|
* expressions.
|
|
158
164
|
*
|
|
159
|
-
* While this Factory function includes the `Descriptor` class, note that
|
|
160
|
-
* this class was deprecated in v2.0 in favor of `Output`. For backward
|
|
161
|
-
* compatibility, the `Descriptor` class remains, but using `Output` is advised.
|
|
162
|
-
*
|
|
163
165
|
* The Factory also returns utility methods like `expand` (detailed below)
|
|
164
|
-
* and `parseKeyExpression` (see {@link
|
|
166
|
+
* and `parseKeyExpression` (see {@link KeyExpressionParser}).
|
|
165
167
|
*
|
|
166
168
|
* Additionally, for convenience, the function returns `BIP32` and `ECPair`.
|
|
167
169
|
* These are {@link https://github.com/bitcoinjs bitcoinjs-lib} classes designed
|
|
@@ -174,7 +176,7 @@ function parseSortedMulti(inner) {
|
|
|
174
176
|
* [@bitcoinerlab/secp256k1](https://github.com/bitcoinerlab/secp256k1).
|
|
175
177
|
*/
|
|
176
178
|
function DescriptorsFactory(ecc) {
|
|
177
|
-
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,
|
|
179
|
+
var _Output_instances, _Output_payment, _Output_preimages, _Output_signersPubKeys, _Output_miniscript, _Output_witnessScript, _Output_redeemScript, _Output_isSegwit, _Output_isTaproot, _Output_expandedExpression, _Output_expandedMiniscript, _Output_tapTreeExpression, _Output_tapTree, _Output_tapTreeInfo, _Output_taprootSpendPath, _Output_tapLeaf, _Output_expansionMap, _Output_network, _Output_resolveMiniscriptSignersPubKeys, _Output_assertMiniscriptSatisfactionResourceLimits, _Output_resolveTapTreeSignersPubKeys, _Output_getConstraints, _Output_assertPsbtInput;
|
|
178
180
|
(0, bitcoinjs_lib_1.initEccLib)(ecc); //Taproot requires initEccLib
|
|
179
181
|
const BIP32 = (0, bip32_1.BIP32Factory)(ecc);
|
|
180
182
|
const ECPair = (0, ecpair_1.ECPairFactory)(ecc);
|
|
@@ -203,15 +205,7 @@ function DescriptorsFactory(ecc) {
|
|
|
203
205
|
BIP32
|
|
204
206
|
});
|
|
205
207
|
};
|
|
206
|
-
|
|
207
|
-
* @overload
|
|
208
|
-
* To be removed in v3.0 and replaced by the version with the signature that
|
|
209
|
-
* does not accept descriptors
|
|
210
|
-
*/
|
|
211
|
-
function expand({ descriptor, expression, index, checksumRequired = false, network = bitcoinjs_lib_1.networks.bitcoin, allowMiniscriptInP2SH = false }) {
|
|
212
|
-
if (descriptor && expression)
|
|
213
|
-
throw new Error(`expression param has been deprecated`);
|
|
214
|
-
descriptor = descriptor || expression;
|
|
208
|
+
function expand({ descriptor, index, change, checksumRequired = false, network = bitcoinjs_lib_1.networks.bitcoin, allowMiniscriptInP2SH = false }) {
|
|
215
209
|
if (!descriptor)
|
|
216
210
|
throw new Error(`descriptor not provided`);
|
|
217
211
|
let expandedExpression;
|
|
@@ -220,6 +214,9 @@ function DescriptorsFactory(ecc) {
|
|
|
220
214
|
let isSegwit;
|
|
221
215
|
let isTaproot;
|
|
222
216
|
let expandedMiniscript;
|
|
217
|
+
let tapTreeExpression;
|
|
218
|
+
let tapTree;
|
|
219
|
+
let tapTreeInfo;
|
|
223
220
|
let payment;
|
|
224
221
|
let witnessScript;
|
|
225
222
|
let redeemScript;
|
|
@@ -232,6 +229,7 @@ function DescriptorsFactory(ecc) {
|
|
|
232
229
|
const canonicalExpression = evaluate({
|
|
233
230
|
descriptor,
|
|
234
231
|
...(index !== undefined ? { index } : {}),
|
|
232
|
+
...(change !== undefined ? { change } : {}),
|
|
235
233
|
checksumRequired
|
|
236
234
|
});
|
|
237
235
|
const isCanonicalRanged = canonicalExpression.indexOf('*') !== -1;
|
|
@@ -398,7 +396,7 @@ function DescriptorsFactory(ecc) {
|
|
|
398
396
|
throw new Error(`Error: key has no pubkey`);
|
|
399
397
|
return p.pubkey;
|
|
400
398
|
});
|
|
401
|
-
pubkeys.sort((a, b) =>
|
|
399
|
+
pubkeys.sort((a, b) => (0, uint8array_tools_1.compare)(a, b));
|
|
402
400
|
const redeem = bitcoinjs_lib_1.payments.p2ms({ m, pubkeys, network });
|
|
403
401
|
redeemScript = redeem.output;
|
|
404
402
|
if (!redeemScript)
|
|
@@ -428,7 +426,7 @@ function DescriptorsFactory(ecc) {
|
|
|
428
426
|
throw new Error(`Error: key has no pubkey`);
|
|
429
427
|
return p.pubkey;
|
|
430
428
|
});
|
|
431
|
-
pubkeys.sort((a, b) =>
|
|
429
|
+
pubkeys.sort((a, b) => (0, uint8array_tools_1.compare)(a, b));
|
|
432
430
|
const redeem = bitcoinjs_lib_1.payments.p2ms({ m, pubkeys, network });
|
|
433
431
|
witnessScript = redeem.output;
|
|
434
432
|
if (!witnessScript)
|
|
@@ -458,7 +456,7 @@ function DescriptorsFactory(ecc) {
|
|
|
458
456
|
throw new Error(`Error: key has no pubkey`);
|
|
459
457
|
return p.pubkey;
|
|
460
458
|
});
|
|
461
|
-
pubkeys.sort((a, b) =>
|
|
459
|
+
pubkeys.sort((a, b) => (0, uint8array_tools_1.compare)(a, b));
|
|
462
460
|
const redeem = bitcoinjs_lib_1.payments.p2ms({ m, pubkeys, network });
|
|
463
461
|
const wsh = bitcoinjs_lib_1.payments.p2wsh({ redeem, network });
|
|
464
462
|
witnessScript = redeem.output;
|
|
@@ -482,13 +480,10 @@ function DescriptorsFactory(ecc) {
|
|
|
482
480
|
if (!isCanonicalRanged) {
|
|
483
481
|
const script = (0, miniscript_1.miniscript2Script)({ expandedMiniscript, expansionMap });
|
|
484
482
|
witnessScript = script;
|
|
485
|
-
if (script.byteLength > MAX_STANDARD_P2WSH_SCRIPT_SIZE) {
|
|
486
|
-
throw new Error(`Error: script is too large, ${script.byteLength} bytes is larger than ${MAX_STANDARD_P2WSH_SCRIPT_SIZE} bytes`);
|
|
487
|
-
}
|
|
488
|
-
const nonPushOnlyOps = countNonPushOnlyOPs(script);
|
|
489
|
-
if (nonPushOnlyOps > MAX_OPS_PER_SCRIPT) {
|
|
490
|
-
throw new Error(`Error: too many non-push ops, ${nonPushOnlyOps} non-push ops is larger than ${MAX_OPS_PER_SCRIPT}`);
|
|
483
|
+
if (script.byteLength > resourceLimits_1.MAX_STANDARD_P2WSH_SCRIPT_SIZE) {
|
|
484
|
+
throw new Error(`Error: script is too large, ${script.byteLength} bytes is larger than ${resourceLimits_1.MAX_STANDARD_P2WSH_SCRIPT_SIZE} bytes`);
|
|
491
485
|
}
|
|
486
|
+
(0, resourceLimits_1.assertScriptNonPushOnlyOpsLimit)({ script });
|
|
492
487
|
payment = p2sh({
|
|
493
488
|
redeem: p2wsh({ redeem: { output: script, network }, network }),
|
|
494
489
|
network
|
|
@@ -523,13 +518,10 @@ function DescriptorsFactory(ecc) {
|
|
|
523
518
|
if (!isCanonicalRanged) {
|
|
524
519
|
const script = (0, miniscript_1.miniscript2Script)({ expandedMiniscript, expansionMap });
|
|
525
520
|
redeemScript = script;
|
|
526
|
-
if (script.byteLength > MAX_SCRIPT_ELEMENT_SIZE) {
|
|
527
|
-
throw new Error(`Error: P2SH script is too large, ${script.byteLength} bytes is larger than ${MAX_SCRIPT_ELEMENT_SIZE} bytes`);
|
|
528
|
-
}
|
|
529
|
-
const nonPushOnlyOps = countNonPushOnlyOPs(script);
|
|
530
|
-
if (nonPushOnlyOps > MAX_OPS_PER_SCRIPT) {
|
|
531
|
-
throw new Error(`Error: too many non-push ops, ${nonPushOnlyOps} non-push ops is larger than ${MAX_OPS_PER_SCRIPT}`);
|
|
521
|
+
if (script.byteLength > resourceLimits_1.MAX_SCRIPT_ELEMENT_SIZE) {
|
|
522
|
+
throw new Error(`Error: P2SH script is too large, ${script.byteLength} bytes is larger than ${resourceLimits_1.MAX_SCRIPT_ELEMENT_SIZE} bytes`);
|
|
532
523
|
}
|
|
524
|
+
(0, resourceLimits_1.assertScriptNonPushOnlyOpsLimit)({ script });
|
|
533
525
|
payment = p2sh({ redeem: { output: script, network }, network });
|
|
534
526
|
}
|
|
535
527
|
}
|
|
@@ -549,26 +541,21 @@ function DescriptorsFactory(ecc) {
|
|
|
549
541
|
if (!isCanonicalRanged) {
|
|
550
542
|
const script = (0, miniscript_1.miniscript2Script)({ expandedMiniscript, expansionMap });
|
|
551
543
|
witnessScript = script;
|
|
552
|
-
if (script.byteLength > MAX_STANDARD_P2WSH_SCRIPT_SIZE) {
|
|
553
|
-
throw new Error(`Error: script is too large, ${script.byteLength} bytes is larger than ${MAX_STANDARD_P2WSH_SCRIPT_SIZE} bytes`);
|
|
554
|
-
}
|
|
555
|
-
const nonPushOnlyOps = countNonPushOnlyOPs(script);
|
|
556
|
-
if (nonPushOnlyOps > MAX_OPS_PER_SCRIPT) {
|
|
557
|
-
throw new Error(`Error: too many non-push ops, ${nonPushOnlyOps} non-push ops is larger than ${MAX_OPS_PER_SCRIPT}`);
|
|
544
|
+
if (script.byteLength > resourceLimits_1.MAX_STANDARD_P2WSH_SCRIPT_SIZE) {
|
|
545
|
+
throw new Error(`Error: script is too large, ${script.byteLength} bytes is larger than ${resourceLimits_1.MAX_STANDARD_P2WSH_SCRIPT_SIZE} bytes`);
|
|
558
546
|
}
|
|
547
|
+
(0, resourceLimits_1.assertScriptNonPushOnlyOpsLimit)({ script });
|
|
559
548
|
payment = p2wsh({ redeem: { output: script, network }, network });
|
|
560
549
|
}
|
|
561
550
|
}
|
|
562
|
-
//tr(KEY)
|
|
563
|
-
else if (canonicalExpression.
|
|
551
|
+
//tr(KEY) or tr(KEY,TREE) - taproot
|
|
552
|
+
else if (canonicalExpression.startsWith('tr(')) {
|
|
564
553
|
isSegwit = true;
|
|
565
554
|
isTaproot = true;
|
|
566
|
-
const keyExpression = canonicalExpression
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
throw new Error(`Error: invalid expression ${expression}`);
|
|
571
|
-
expandedExpression = 'tr(@0)';
|
|
555
|
+
const { keyExpression, treeExpression } = parseTrExpression(canonicalExpression);
|
|
556
|
+
expandedExpression = treeExpression
|
|
557
|
+
? `tr(@0,${treeExpression})`
|
|
558
|
+
: 'tr(@0)';
|
|
572
559
|
const pKE = parseKeyExpression({
|
|
573
560
|
keyExpression,
|
|
574
561
|
network,
|
|
@@ -576,17 +563,30 @@ function DescriptorsFactory(ecc) {
|
|
|
576
563
|
isTaproot
|
|
577
564
|
});
|
|
578
565
|
expansionMap = { '@0': pKE };
|
|
566
|
+
if (treeExpression) {
|
|
567
|
+
tapTreeExpression = treeExpression;
|
|
568
|
+
tapTree = (0, tapTree_1.parseTapTreeExpression)(treeExpression);
|
|
569
|
+
if (!isCanonicalRanged) {
|
|
570
|
+
tapTreeInfo = (0, tapMiniscript_1.buildTapTreeInfo)({ tapTree, network, BIP32, ECPair });
|
|
571
|
+
}
|
|
572
|
+
}
|
|
579
573
|
if (!isCanonicalRanged) {
|
|
580
574
|
const pubkey = pKE.pubkey;
|
|
581
575
|
if (!pubkey)
|
|
582
|
-
throw new Error(`Error: could not extract a pubkey from ${
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
576
|
+
throw new Error(`Error: could not extract a pubkey from ${descriptor}`);
|
|
577
|
+
const internalPubkey = (0, tapMiniscript_1.normalizeTaprootPubkey)(pubkey);
|
|
578
|
+
if (treeExpression) {
|
|
579
|
+
if (!tapTreeInfo)
|
|
580
|
+
throw new Error(`Error: taproot tree info not available`);
|
|
581
|
+
payment = p2tr({
|
|
582
|
+
internalPubkey,
|
|
583
|
+
scriptTree: (0, tapMiniscript_1.tapTreeInfoToScriptTree)(tapTreeInfo),
|
|
584
|
+
network
|
|
585
|
+
});
|
|
586
|
+
}
|
|
587
|
+
else {
|
|
588
|
+
payment = p2tr({ internalPubkey, network });
|
|
589
|
+
}
|
|
590
590
|
}
|
|
591
591
|
}
|
|
592
592
|
else {
|
|
@@ -600,6 +600,9 @@ function DescriptorsFactory(ecc) {
|
|
|
600
600
|
...(isSegwit !== undefined ? { isSegwit } : {}),
|
|
601
601
|
...(isTaproot !== undefined ? { isTaproot } : {}),
|
|
602
602
|
...(expandedMiniscript !== undefined ? { expandedMiniscript } : {}),
|
|
603
|
+
...(tapTreeExpression !== undefined ? { tapTreeExpression } : {}),
|
|
604
|
+
...(tapTree !== undefined ? { tapTree } : {}),
|
|
605
|
+
...(tapTreeInfo !== undefined ? { tapTreeInfo } : {}),
|
|
603
606
|
...(redeemScript !== undefined ? { redeemScript } : {}),
|
|
604
607
|
...(witnessScript !== undefined ? { witnessScript } : {}),
|
|
605
608
|
isRanged,
|
|
@@ -634,7 +637,7 @@ function DescriptorsFactory(ecc) {
|
|
|
634
637
|
* @param options
|
|
635
638
|
* @throws {Error} - when descriptor is invalid
|
|
636
639
|
*/
|
|
637
|
-
constructor({ descriptor, index, checksumRequired = false, allowMiniscriptInP2SH = false, network = bitcoinjs_lib_1.networks.bitcoin, preimages = [], signersPubKeys }) {
|
|
640
|
+
constructor({ descriptor, index, change, checksumRequired = false, allowMiniscriptInP2SH = false, network = bitcoinjs_lib_1.networks.bitcoin, preimages = [], signersPubKeys, taprootSpendPath, tapLeaf }) {
|
|
638
641
|
_Output_instances.add(this);
|
|
639
642
|
_Output_payment.set(this, void 0);
|
|
640
643
|
_Output_preimages.set(this, []);
|
|
@@ -648,6 +651,11 @@ function DescriptorsFactory(ecc) {
|
|
|
648
651
|
_Output_isTaproot.set(this, void 0);
|
|
649
652
|
_Output_expandedExpression.set(this, void 0);
|
|
650
653
|
_Output_expandedMiniscript.set(this, void 0);
|
|
654
|
+
_Output_tapTreeExpression.set(this, void 0);
|
|
655
|
+
_Output_tapTree.set(this, void 0);
|
|
656
|
+
_Output_tapTreeInfo.set(this, void 0);
|
|
657
|
+
_Output_taprootSpendPath.set(this, void 0);
|
|
658
|
+
_Output_tapLeaf.set(this, void 0);
|
|
651
659
|
_Output_expansionMap.set(this, void 0);
|
|
652
660
|
_Output_network.set(this, void 0);
|
|
653
661
|
__classPrivateFieldSet(this, _Output_network, network, "f");
|
|
@@ -657,10 +665,26 @@ function DescriptorsFactory(ecc) {
|
|
|
657
665
|
const expandedResult = expand({
|
|
658
666
|
descriptor,
|
|
659
667
|
...(index !== undefined ? { index } : {}),
|
|
668
|
+
...(change !== undefined ? { change } : {}),
|
|
660
669
|
checksumRequired,
|
|
661
670
|
network,
|
|
662
671
|
allowMiniscriptInP2SH
|
|
663
672
|
});
|
|
673
|
+
const isTaprootDescriptor = expandedResult.isTaproot === true;
|
|
674
|
+
const hasTapTree = expandedResult.expandedExpression?.startsWith('tr(@0,') ?? false;
|
|
675
|
+
const resolvedTaprootSpendPath = taprootSpendPath ?? (hasTapTree ? 'script' : 'key');
|
|
676
|
+
if (!isTaprootDescriptor) {
|
|
677
|
+
if (taprootSpendPath !== undefined || tapLeaf !== undefined)
|
|
678
|
+
throw new Error(`Error: taprootSpendPath/tapLeaf require a taproot descriptor`);
|
|
679
|
+
}
|
|
680
|
+
else {
|
|
681
|
+
if (taprootSpendPath === 'script' && !hasTapTree)
|
|
682
|
+
throw new Error(`Error: taprootSpendPath=script requires a tr(KEY,TREE) descriptor`);
|
|
683
|
+
if (resolvedTaprootSpendPath === 'key' && tapLeaf !== undefined)
|
|
684
|
+
throw new Error(`Error: tapLeaf cannot be used when taprootSpendPath is key`);
|
|
685
|
+
if (tapLeaf !== undefined && !hasTapTree)
|
|
686
|
+
throw new Error(`Error: tapLeaf can only be used with tr(KEY,TREE) descriptors`);
|
|
687
|
+
}
|
|
664
688
|
if (expandedResult.isRanged && index === undefined)
|
|
665
689
|
throw new Error(`Error: index was not provided for ranged descriptor`);
|
|
666
690
|
if (!expandedResult.payment)
|
|
@@ -678,83 +702,55 @@ function DescriptorsFactory(ecc) {
|
|
|
678
702
|
__classPrivateFieldSet(this, _Output_isTaproot, expandedResult.isTaproot, "f");
|
|
679
703
|
if (expandedResult.expandedMiniscript !== undefined)
|
|
680
704
|
__classPrivateFieldSet(this, _Output_expandedMiniscript, expandedResult.expandedMiniscript, "f");
|
|
705
|
+
if (expandedResult.tapTreeExpression !== undefined)
|
|
706
|
+
__classPrivateFieldSet(this, _Output_tapTreeExpression, expandedResult.tapTreeExpression, "f");
|
|
707
|
+
if (expandedResult.tapTree !== undefined)
|
|
708
|
+
__classPrivateFieldSet(this, _Output_tapTree, expandedResult.tapTree, "f");
|
|
709
|
+
if (expandedResult.tapTreeInfo !== undefined)
|
|
710
|
+
__classPrivateFieldSet(this, _Output_tapTreeInfo, expandedResult.tapTreeInfo, "f");
|
|
681
711
|
if (expandedResult.redeemScript !== undefined)
|
|
682
712
|
__classPrivateFieldSet(this, _Output_redeemScript, expandedResult.redeemScript, "f");
|
|
683
713
|
if (expandedResult.witnessScript !== undefined)
|
|
684
714
|
__classPrivateFieldSet(this, _Output_witnessScript, expandedResult.witnessScript, "f");
|
|
685
|
-
if (signersPubKeys)
|
|
715
|
+
if (signersPubKeys)
|
|
686
716
|
__classPrivateFieldSet(this, _Output_signersPubKeys, signersPubKeys, "f");
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
__classPrivateFieldSet(this, _Output_signersPubKeys, Object.values(__classPrivateFieldGet(this, _Output_expansionMap, "f")).map(keyInfo => {
|
|
691
|
-
const pubkey = keyInfo.pubkey;
|
|
692
|
-
if (!pubkey)
|
|
693
|
-
throw new Error(`Error: could not extract a pubkey from ${descriptor}`);
|
|
694
|
-
return pubkey;
|
|
695
|
-
}), "f");
|
|
696
|
-
}
|
|
697
|
-
else {
|
|
698
|
-
//We should only miss expansionMap in addr() expressions:
|
|
699
|
-
if (!expandedResult.canonicalExpression.match(RE.reAddrAnchored)) {
|
|
700
|
-
throw new Error(`Error: expansionMap not available for expression ${descriptor} that is not an address`);
|
|
701
|
-
}
|
|
702
|
-
__classPrivateFieldSet(this, _Output_signersPubKeys, [this.getScriptPubKey()], "f");
|
|
703
|
-
}
|
|
704
|
-
}
|
|
717
|
+
__classPrivateFieldSet(this, _Output_taprootSpendPath, resolvedTaprootSpendPath, "f");
|
|
718
|
+
if (tapLeaf !== undefined)
|
|
719
|
+
__classPrivateFieldSet(this, _Output_tapLeaf, tapLeaf, "f");
|
|
705
720
|
this.getSequence = (0, lodash_memoize_1.default)(this.getSequence);
|
|
706
721
|
this.getLockTime = (0, lodash_memoize_1.default)(this.getLockTime);
|
|
707
722
|
const getSignaturesKey = (signatures) => signatures === 'DANGEROUSLY_USE_FAKE_SIGNATURES'
|
|
708
723
|
? signatures
|
|
709
724
|
: signatures
|
|
710
|
-
.map(s => `${s.pubkey
|
|
725
|
+
.map(s => `${(0, uint8array_tools_1.toHex)(s.pubkey)}-${(0, uint8array_tools_1.toHex)(s.signature)}`)
|
|
711
726
|
.join('|');
|
|
712
|
-
this.getScriptSatisfaction = (0, lodash_memoize_1.default)(this.getScriptSatisfaction,
|
|
713
|
-
// resolver function:
|
|
714
|
-
getSignaturesKey);
|
|
715
727
|
this.guessOutput = (0, lodash_memoize_1.default)(this.guessOutput);
|
|
716
728
|
this.inputWeight = (0, lodash_memoize_1.default)(this.inputWeight,
|
|
717
729
|
// resolver function:
|
|
718
|
-
(isSegwitTx, signatures) => {
|
|
730
|
+
(isSegwitTx, signatures, options) => {
|
|
719
731
|
const segwitKey = isSegwitTx ? 'segwit' : 'non-segwit';
|
|
720
732
|
const signaturesKey = getSignaturesKey(signatures);
|
|
721
|
-
|
|
733
|
+
const taprootSighashKey = options?.taprootSighash ?? 'SIGHASH_DEFAULT';
|
|
734
|
+
return `${segwitKey}-${signaturesKey}-taprootSighash:${taprootSighashKey}`;
|
|
722
735
|
});
|
|
723
736
|
this.outputWeight = (0, lodash_memoize_1.default)(this.outputWeight);
|
|
724
737
|
}
|
|
725
738
|
/**
|
|
726
|
-
*
|
|
727
|
-
*
|
|
728
|
-
|
|
729
|
-
getPayment() {
|
|
730
|
-
return __classPrivateFieldGet(this, _Output_payment, "f");
|
|
731
|
-
}
|
|
732
|
-
/**
|
|
733
|
-
* Returns the Bitcoin Address of this `Output`.
|
|
734
|
-
*/
|
|
735
|
-
getAddress() {
|
|
736
|
-
if (!__classPrivateFieldGet(this, _Output_payment, "f").address)
|
|
737
|
-
throw new Error(`Error: could extract an address from the payment`);
|
|
738
|
-
return __classPrivateFieldGet(this, _Output_payment, "f").address;
|
|
739
|
-
}
|
|
740
|
-
/**
|
|
741
|
-
* Returns this `Output`'s scriptPubKey.
|
|
742
|
-
*/
|
|
743
|
-
getScriptPubKey() {
|
|
744
|
-
if (!__classPrivateFieldGet(this, _Output_payment, "f").output)
|
|
745
|
-
throw new Error(`Error: could extract output.script from the payment`);
|
|
746
|
-
return __classPrivateFieldGet(this, _Output_payment, "f").output;
|
|
747
|
-
}
|
|
748
|
-
/**
|
|
749
|
-
* Returns the compiled Script Satisfaction if this `Output` was created
|
|
750
|
-
* using a miniscript-based descriptor.
|
|
739
|
+
* Returns the compiled Script Satisfaction for a miniscript-based Output.
|
|
740
|
+
* The satisfaction is the unlocking script, derived by the Satisfier
|
|
741
|
+
* algorithm (https://bitcoin.sipa.be/miniscript/).
|
|
751
742
|
*
|
|
752
|
-
*
|
|
753
|
-
*
|
|
754
|
-
*
|
|
743
|
+
* This method uses a two-pass flow:
|
|
744
|
+
* 1) Planning: constraints (nLockTime/nSequence) are computed using fake
|
|
745
|
+
* signatures. This is done since the final solution may not need all the
|
|
746
|
+
* signatures in signersPubKeys. And we may avoid the user do extra
|
|
747
|
+
* signing (tedious op with HWW).
|
|
748
|
+
* 2) Signing: the provided signatures are used to build the final
|
|
749
|
+
* satisfaction, while enforcing the planned constraints so the same
|
|
750
|
+
* solution is selected. Not all the signatures of signersPubKeys may
|
|
751
|
+
* be required.
|
|
755
752
|
*
|
|
756
|
-
*
|
|
757
|
-
* miniscript descriptors.
|
|
753
|
+
* The return value includes the satisfaction script and the constraints.
|
|
758
754
|
*/
|
|
759
755
|
getScriptSatisfaction(
|
|
760
756
|
/**
|
|
@@ -762,29 +758,9 @@ function DescriptorsFactory(ecc) {
|
|
|
762
758
|
* build the Satisfaction of this miniscript-based `Output`.
|
|
763
759
|
*
|
|
764
760
|
* `signatures` must be passed using this format (pairs of `pubKey/signature`):
|
|
765
|
-
* `interface PartialSig { pubkey:
|
|
766
|
-
*
|
|
767
|
-
* * Alternatively, if you do not have the signatures, you can use the option
|
|
768
|
-
* `'DANGEROUSLY_USE_FAKE_SIGNATURES'`. This will generate script satisfactions
|
|
769
|
-
* using 72-byte zero-padded signatures. While this can be useful in
|
|
770
|
-
* modules like coinselector that require estimating transaction size before
|
|
771
|
-
* signing, it is critical to understand the risks:
|
|
772
|
-
* - Using this option generales invalid unlocking scripts.
|
|
773
|
-
* - It should NEVER be used with real transactions.
|
|
774
|
-
* - Its primary use is for testing and size estimation purposes only.
|
|
775
|
-
*
|
|
776
|
-
* ⚠️ Warning: Misuse of 'DANGEROUSLY_USE_FAKE_SIGNATURES' can lead to security
|
|
777
|
-
* vulnerabilities, including but not limited to invalid transaction generation.
|
|
778
|
-
* Ensure you fully understand the implications before use.
|
|
779
|
-
*
|
|
761
|
+
* `interface PartialSig { pubkey: Uint8Array; signature: Uint8Array; }`
|
|
780
762
|
*/
|
|
781
763
|
signatures) {
|
|
782
|
-
if (signatures === 'DANGEROUSLY_USE_FAKE_SIGNATURES')
|
|
783
|
-
signatures = __classPrivateFieldGet(this, _Output_signersPubKeys, "f").map(pubkey => ({
|
|
784
|
-
pubkey,
|
|
785
|
-
// https://transactionfee.info/charts/bitcoin-script-ecdsa-length/
|
|
786
|
-
signature: Buffer.alloc(72, 0)
|
|
787
|
-
}));
|
|
788
764
|
const miniscript = __classPrivateFieldGet(this, _Output_miniscript, "f");
|
|
789
765
|
const expandedMiniscript = __classPrivateFieldGet(this, _Output_expandedMiniscript, "f");
|
|
790
766
|
const expansionMap = __classPrivateFieldGet(this, _Output_expansionMap, "f");
|
|
@@ -792,12 +768,9 @@ function DescriptorsFactory(ecc) {
|
|
|
792
768
|
expandedMiniscript === undefined ||
|
|
793
769
|
expansionMap === undefined)
|
|
794
770
|
throw new Error(`Error: cannot get satisfaction from not expanded miniscript ${miniscript}`);
|
|
795
|
-
//
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
//that the actual solution given, using real signatures, still meets the
|
|
799
|
-
//same nLockTime and nSequence constraints
|
|
800
|
-
const scriptSatisfaction = (0, miniscript_1.satisfyMiniscript)({
|
|
771
|
+
//This crates the plans using fake signatures
|
|
772
|
+
const constraints = __classPrivateFieldGet(this, _Output_instances, "m", _Output_getConstraints).call(this);
|
|
773
|
+
const satisfaction = (0, miniscript_1.satisfyMiniscript)({
|
|
801
774
|
expandedMiniscript,
|
|
802
775
|
expansionMap,
|
|
803
776
|
signatures,
|
|
@@ -806,25 +779,96 @@ function DescriptorsFactory(ecc) {
|
|
|
806
779
|
//verify that the solutions found using the final signatures have not
|
|
807
780
|
//changed
|
|
808
781
|
timeConstraints: {
|
|
809
|
-
nLockTime:
|
|
810
|
-
nSequence:
|
|
782
|
+
nLockTime: constraints?.nLockTime,
|
|
783
|
+
nSequence: constraints?.nSequence
|
|
811
784
|
}
|
|
812
|
-
})
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
785
|
+
});
|
|
786
|
+
__classPrivateFieldGet(this, _Output_instances, "m", _Output_assertMiniscriptSatisfactionResourceLimits).call(this, satisfaction.scriptSatisfaction);
|
|
787
|
+
return satisfaction;
|
|
788
|
+
}
|
|
789
|
+
/**
|
|
790
|
+
* Returns the taproot script‑path satisfaction for a tap miniscript
|
|
791
|
+
* descriptor. This mirrors {@link getScriptSatisfaction} and uses the same
|
|
792
|
+
* two‑pass plan/sign flow.
|
|
793
|
+
*
|
|
794
|
+
* In addition to nLockTime/nSequence, it returns the selected tapLeafHash
|
|
795
|
+
* (the leaf chosen during planning) and the leaf’s tapscript.
|
|
796
|
+
*/
|
|
797
|
+
getTapScriptSatisfaction(
|
|
798
|
+
/**
|
|
799
|
+
* An array with all the signatures needed to
|
|
800
|
+
* build the Satisfaction of this miniscript-based `Output`.
|
|
801
|
+
*
|
|
802
|
+
* `signatures` must be passed using this format (pairs of `pubKey/signature`):
|
|
803
|
+
* `interface PartialSig { pubkey: Uint8Array; signature: Uint8Array; }`
|
|
804
|
+
*/
|
|
805
|
+
signatures) {
|
|
806
|
+
if (__classPrivateFieldGet(this, _Output_taprootSpendPath, "f") !== 'script')
|
|
807
|
+
throw new Error(`Error: taprootSpendPath is key; script-path satisfaction is not allowed`);
|
|
808
|
+
const tapTreeInfo = __classPrivateFieldGet(this, _Output_tapTreeInfo, "f");
|
|
809
|
+
if (!tapTreeInfo)
|
|
810
|
+
throw new Error(`Error: taproot tree info not available`);
|
|
811
|
+
const constraints = __classPrivateFieldGet(this, _Output_instances, "m", _Output_getConstraints).call(this);
|
|
812
|
+
return (0, tapMiniscript_1.satisfyTapTree)({
|
|
813
|
+
tapTreeInfo,
|
|
814
|
+
preimages: __classPrivateFieldGet(this, _Output_preimages, "f"),
|
|
815
|
+
signatures,
|
|
816
|
+
...(constraints?.tapLeafHash
|
|
817
|
+
? { tapLeaf: constraints.tapLeafHash }
|
|
818
|
+
: {}),
|
|
819
|
+
...(constraints
|
|
820
|
+
? {
|
|
821
|
+
timeConstraints: {
|
|
822
|
+
nLockTime: constraints.nLockTime,
|
|
823
|
+
nSequence: constraints.nSequence
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
: {})
|
|
827
|
+
});
|
|
828
|
+
}
|
|
829
|
+
/**
|
|
830
|
+
* Creates and returns an instance of bitcoinjs-lib
|
|
831
|
+
* [`Payment`](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/ts_src/payments/index.ts)'s interface with the `scriptPubKey` of this `Output`.
|
|
832
|
+
*/
|
|
833
|
+
getPayment() {
|
|
834
|
+
return __classPrivateFieldGet(this, _Output_payment, "f");
|
|
835
|
+
}
|
|
836
|
+
/**
|
|
837
|
+
* Returns the Bitcoin Address of this `Output`.
|
|
838
|
+
*/
|
|
839
|
+
getAddress() {
|
|
840
|
+
if (!__classPrivateFieldGet(this, _Output_payment, "f").address)
|
|
841
|
+
throw new Error(`Error: could extract an address from the payment`);
|
|
842
|
+
return __classPrivateFieldGet(this, _Output_payment, "f").address;
|
|
843
|
+
}
|
|
844
|
+
/**
|
|
845
|
+
* Returns this `Output`'s scriptPubKey.
|
|
846
|
+
*/
|
|
847
|
+
getScriptPubKey() {
|
|
848
|
+
if (!__classPrivateFieldGet(this, _Output_payment, "f").output)
|
|
849
|
+
throw new Error(`Error: could extract output.script from the payment`);
|
|
850
|
+
return __classPrivateFieldGet(this, _Output_payment, "f").output;
|
|
816
851
|
}
|
|
817
852
|
/**
|
|
818
853
|
* Gets the nSequence required to fulfill this `Output`.
|
|
819
854
|
*/
|
|
820
855
|
getSequence() {
|
|
821
|
-
return __classPrivateFieldGet(this, _Output_instances, "m",
|
|
856
|
+
return __classPrivateFieldGet(this, _Output_instances, "m", _Output_getConstraints).call(this)?.nSequence;
|
|
822
857
|
}
|
|
823
858
|
/**
|
|
824
859
|
* Gets the nLockTime required to fulfill this `Output`.
|
|
825
860
|
*/
|
|
826
861
|
getLockTime() {
|
|
827
|
-
return __classPrivateFieldGet(this, _Output_instances, "m",
|
|
862
|
+
return __classPrivateFieldGet(this, _Output_instances, "m", _Output_getConstraints).call(this)?.nLockTime;
|
|
863
|
+
}
|
|
864
|
+
/**
|
|
865
|
+
* Returns the tapleaf hash selected during planning for taproot script-path
|
|
866
|
+
* spends. If signersPubKeys are provided, selection is optimized for those
|
|
867
|
+
* pubkeys. If a specific tapLeaf selector is used in spending calls, this
|
|
868
|
+
* reflects that selection.
|
|
869
|
+
*/
|
|
870
|
+
getTapLeafHash() {
|
|
871
|
+
return __classPrivateFieldGet(this, _Output_instances, "m", _Output_getConstraints).call(this)?.tapLeafHash;
|
|
828
872
|
}
|
|
829
873
|
/**
|
|
830
874
|
* Gets the witnessScript required to fulfill this `Output`. Only applies to
|
|
@@ -955,10 +999,19 @@ function DescriptorsFactory(ecc) {
|
|
|
955
999
|
*, Also any `addr(SINGLE_KEY_ADDRESS)` * is assumed to be a single key Taproot
|
|
956
1000
|
* address (like those defined in BIP86).
|
|
957
1001
|
* For inputs using arbitrary scripts (not standard addresses),
|
|
958
|
-
* use a descriptor in the format `sh(MINISCRIPT)
|
|
959
|
-
*
|
|
960
|
-
* expressions.
|
|
1002
|
+
* use a descriptor in the format `sh(MINISCRIPT)`, `wsh(MINISCRIPT)` or
|
|
1003
|
+
* `tr(KEY,TREE)` for taproot script-path expressions.
|
|
961
1004
|
*/
|
|
1005
|
+
// NOTE(taproot-weight): Output instances are concrete. If descriptor has
|
|
1006
|
+
// wildcards, constructor requires `index`. No ranged-without-index
|
|
1007
|
+
// estimation is attempted here.
|
|
1008
|
+
// TODO(taproot-weight): Remaining items:
|
|
1009
|
+
// - Annex: not modeled; if annex is used, add witness item sizing.
|
|
1010
|
+
// - Taproot sighash defaults: options.taprootSighash currently drives fake
|
|
1011
|
+
// signature sizing; ensure coinselector passes the intended mode.
|
|
1012
|
+
// - After PSBT taproot script-path fields are fully populated, add regtest
|
|
1013
|
+
// integration fixtures comparing real tx vsize with inputWeight/outputWeight
|
|
1014
|
+
// estimates for taproot key-path and script-path spends.
|
|
962
1015
|
inputWeight(
|
|
963
1016
|
/**
|
|
964
1017
|
* Indicates if the transaction is a Segwit transaction.
|
|
@@ -973,12 +1026,23 @@ function DescriptorsFactory(ecc) {
|
|
|
973
1026
|
* Array of `PartialSig`. Each `PartialSig` includes
|
|
974
1027
|
* a public key and its corresponding signature. This parameter
|
|
975
1028
|
* enables the accurate calculation of signature sizes for ECDSA.
|
|
976
|
-
* Pass 'DANGEROUSLY_USE_FAKE_SIGNATURES' to assume
|
|
977
|
-
* for ECDSA.
|
|
978
|
-
*
|
|
1029
|
+
* Pass 'DANGEROUSLY_USE_FAKE_SIGNATURES' to assume
|
|
1030
|
+
* ECDSA_FAKE_SIGNATURE_SIZE bytes for ECDSA.
|
|
1031
|
+
* For taproot, the fake signature size is controlled by
|
|
1032
|
+
* options.taprootSighash (64 for 'SIGHASH_DEFAULT', 65
|
|
1033
|
+
* for 'non-SIGHASH_DEFAULT'). default value is SIGHASH_DEFAULT
|
|
979
1034
|
* Mainly used for testing.
|
|
980
1035
|
*/
|
|
981
|
-
signatures
|
|
1036
|
+
signatures,
|
|
1037
|
+
/*
|
|
1038
|
+
* Options that affect taproot fake signature sizing.
|
|
1039
|
+
* taprootSighash: 'SIGHASH_DEFAULT' | 'non-SIGHASH_DEFAULT' (default: 'SIGHASH_DEFAULT').
|
|
1040
|
+
* This is only used when signatures === 'DANGEROUSLY_USE_FAKE_SIGNATURES'.
|
|
1041
|
+
*/
|
|
1042
|
+
options = {
|
|
1043
|
+
taprootSighash: 'SIGHASH_DEFAULT'
|
|
1044
|
+
}) {
|
|
1045
|
+
const taprootSighash = options.taprootSighash ?? 'SIGHASH_DEFAULT';
|
|
982
1046
|
if (this.isSegwit() && !isSegwitTx)
|
|
983
1047
|
throw new Error(`a tx is segwit if at least one input is segwit`);
|
|
984
1048
|
//expand any miniscript-based descriptor. If not miniscript-based, then it's
|
|
@@ -991,13 +1055,59 @@ addr(PKH_ADDRESS), addr(WPKH_ADDRESS), addr(SH_WPKH_ADDRESS), addr(SINGLE_KEY_AD
|
|
|
991
1055
|
expansion=${expansion}, isPKH=${isPKH}, isWPKH=${isWPKH}, isSH=${isSH}, isTR=${isTR}.`;
|
|
992
1056
|
if (!expansion && !isPKH && !isWPKH && !isSH && !isTR)
|
|
993
1057
|
throw new Error(errorMsg);
|
|
994
|
-
const
|
|
995
|
-
|
|
996
|
-
|
|
1058
|
+
const resolveEcdsaSignatureSize = () => {
|
|
1059
|
+
if (signatures === 'DANGEROUSLY_USE_FAKE_SIGNATURES')
|
|
1060
|
+
return ((0, varuint_bitcoin_1.encodingLength)(ECDSA_FAKE_SIGNATURE_SIZE) +
|
|
1061
|
+
ECDSA_FAKE_SIGNATURE_SIZE);
|
|
1062
|
+
if (signatures.length !== 1)
|
|
1063
|
+
throw new Error('More than one signture was not expected');
|
|
1064
|
+
const singleSignature = signatures[0];
|
|
1065
|
+
if (!singleSignature)
|
|
1066
|
+
throw new Error('Signatures not present');
|
|
1067
|
+
const length = singleSignature.signature.length;
|
|
1068
|
+
return (0, varuint_bitcoin_1.encodingLength)(length) + length;
|
|
1069
|
+
};
|
|
1070
|
+
const resolveMiniscriptSignatures = () => {
|
|
1071
|
+
if (signatures !== 'DANGEROUSLY_USE_FAKE_SIGNATURES')
|
|
1072
|
+
return signatures;
|
|
1073
|
+
return __classPrivateFieldGet(this, _Output_instances, "m", _Output_resolveMiniscriptSignersPubKeys).call(this).map(pubkey => ({
|
|
1074
|
+
pubkey,
|
|
1075
|
+
// https://transactionfee.info/charts/bitcoin-script-ecdsa-length/
|
|
1076
|
+
signature: new Uint8Array(ECDSA_FAKE_SIGNATURE_SIZE)
|
|
1077
|
+
}));
|
|
1078
|
+
};
|
|
1079
|
+
const taprootFakeSignatureSize = taprootSighash === 'SIGHASH_DEFAULT'
|
|
1080
|
+
? TAPROOT_FAKE_SIGNATURE_SIZE
|
|
1081
|
+
: TAPROOT_FAKE_SIGNATURE_SIZE + 1;
|
|
1082
|
+
const resolveTaprootSignatures = () => {
|
|
1083
|
+
if (signatures !== 'DANGEROUSLY_USE_FAKE_SIGNATURES')
|
|
1084
|
+
return signatures;
|
|
1085
|
+
return __classPrivateFieldGet(this, _Output_instances, "m", _Output_resolveTapTreeSignersPubKeys).call(this).map(pubkey => ({
|
|
1086
|
+
pubkey,
|
|
1087
|
+
signature: new Uint8Array(taprootFakeSignatureSize)
|
|
1088
|
+
}));
|
|
1089
|
+
};
|
|
1090
|
+
const resolveTaprootSignatureSize = () => {
|
|
1091
|
+
let length;
|
|
1092
|
+
if (signatures === 'DANGEROUSLY_USE_FAKE_SIGNATURES') {
|
|
1093
|
+
length = taprootFakeSignatureSize;
|
|
1094
|
+
}
|
|
1095
|
+
else {
|
|
1096
|
+
if (signatures.length !== 1)
|
|
1097
|
+
throw new Error('More than one signture was not expected');
|
|
1098
|
+
const singleSignature = signatures[0];
|
|
1099
|
+
if (!singleSignature)
|
|
1100
|
+
throw new Error('Signatures not present');
|
|
1101
|
+
length = singleSignature.signature.length;
|
|
1102
|
+
}
|
|
1103
|
+
return (0, varuint_bitcoin_1.encodingLength)(length) + length;
|
|
1104
|
+
};
|
|
1105
|
+
const taprootSpendPath = __classPrivateFieldGet(this, _Output_taprootSpendPath, "f");
|
|
1106
|
+
const tapLeaf = __classPrivateFieldGet(this, _Output_tapLeaf, "f");
|
|
997
1107
|
if (expansion ? expansion.startsWith('pkh(') : isPKH) {
|
|
998
1108
|
return (
|
|
999
1109
|
// Non-segwit: (txid:32) + (vout:4) + (sequence:4) + (script_len:1) + (sig:73) + (pubkey:34)
|
|
1000
|
-
(32 + 4 + 4 + 1 +
|
|
1110
|
+
(32 + 4 + 4 + 1 + resolveEcdsaSignatureSize() + 34) * 4 +
|
|
1001
1111
|
//Segwit:
|
|
1002
1112
|
(isSegwitTx ? 1 : 0));
|
|
1003
1113
|
}
|
|
@@ -1008,7 +1118,7 @@ expansion=${expansion}, isPKH=${isPKH}, isWPKH=${isWPKH}, isSH=${isSH}, isTR=${i
|
|
|
1008
1118
|
// Non-segwit: (txid:32) + (vout:4) + (sequence:4) + (script_len:1)
|
|
1009
1119
|
41 * 4 +
|
|
1010
1120
|
// Segwit: (push_count:1) + (sig:73) + (pubkey:34)
|
|
1011
|
-
(1 +
|
|
1121
|
+
(1 + resolveEcdsaSignatureSize() + 34));
|
|
1012
1122
|
}
|
|
1013
1123
|
else if (expansion ? expansion.startsWith('sh(wpkh(') : isSH) {
|
|
1014
1124
|
if (!isSegwitTx)
|
|
@@ -1019,7 +1129,7 @@ expansion=${expansion}, isPKH=${isPKH}, isWPKH=${isWPKH}, isSH=${isSH}, isTR=${i
|
|
|
1019
1129
|
// -> p2wpkh: (script_len:1) + (script:22)
|
|
1020
1130
|
64 * 4 +
|
|
1021
1131
|
// Segwit: (push_count:1) + (sig:73) + (pubkey:34)
|
|
1022
|
-
(1 +
|
|
1132
|
+
(1 + resolveEcdsaSignatureSize() + 34));
|
|
1023
1133
|
}
|
|
1024
1134
|
else if (expansion?.startsWith('sh(wsh(')) {
|
|
1025
1135
|
if (!isSegwitTx)
|
|
@@ -1030,7 +1140,8 @@ expansion=${expansion}, isPKH=${isPKH}, isWPKH=${isWPKH}, isSH=${isSH}, isTR=${i
|
|
|
1030
1140
|
const payment = bitcoinjs_lib_1.payments.p2sh({
|
|
1031
1141
|
redeem: bitcoinjs_lib_1.payments.p2wsh({
|
|
1032
1142
|
redeem: {
|
|
1033
|
-
input: this.getScriptSatisfaction(
|
|
1143
|
+
input: this.getScriptSatisfaction(resolveMiniscriptSignatures())
|
|
1144
|
+
.scriptSatisfaction,
|
|
1034
1145
|
output: witnessScript
|
|
1035
1146
|
}
|
|
1036
1147
|
})
|
|
@@ -1049,7 +1160,8 @@ expansion=${expansion}, isPKH=${isPKH}, isWPKH=${isWPKH}, isSH=${isSH}, isTR=${i
|
|
|
1049
1160
|
throw new Error('sh() must provide redeemScript');
|
|
1050
1161
|
const payment = bitcoinjs_lib_1.payments.p2sh({
|
|
1051
1162
|
redeem: {
|
|
1052
|
-
input: this.getScriptSatisfaction(
|
|
1163
|
+
input: this.getScriptSatisfaction(resolveMiniscriptSignatures())
|
|
1164
|
+
.scriptSatisfaction,
|
|
1053
1165
|
output: redeemScript
|
|
1054
1166
|
}
|
|
1055
1167
|
});
|
|
@@ -1069,7 +1181,8 @@ expansion=${expansion}, isPKH=${isPKH}, isWPKH=${isWPKH}, isSH=${isSH}, isTR=${i
|
|
|
1069
1181
|
throw new Error('wsh must provide witnessScript');
|
|
1070
1182
|
const payment = bitcoinjs_lib_1.payments.p2wsh({
|
|
1071
1183
|
redeem: {
|
|
1072
|
-
input: this.getScriptSatisfaction(
|
|
1184
|
+
input: this.getScriptSatisfaction(resolveMiniscriptSignatures())
|
|
1185
|
+
.scriptSatisfaction,
|
|
1073
1186
|
output: witnessScript
|
|
1074
1187
|
}
|
|
1075
1188
|
});
|
|
@@ -1080,8 +1193,24 @@ expansion=${expansion}, isPKH=${isPKH}, isWPKH=${isWPKH}, isSH=${isSH}, isTR=${i
|
|
|
1080
1193
|
4 * (40 + varSliceSize(payment.input)) +
|
|
1081
1194
|
//Segwit
|
|
1082
1195
|
vectorSize(payment.witness));
|
|
1083
|
-
//when
|
|
1084
|
-
//
|
|
1196
|
+
// when tr(KEY,TREE): choose key-path or script-path based on
|
|
1197
|
+
// constructor taprootSpendPath policy.
|
|
1198
|
+
}
|
|
1199
|
+
else if (expansion?.startsWith('tr(@0,')) {
|
|
1200
|
+
if (!isSegwitTx)
|
|
1201
|
+
throw new Error('Should be SegwitTx');
|
|
1202
|
+
if (taprootSpendPath === 'key')
|
|
1203
|
+
return 41 * 4 + (0, varuint_bitcoin_1.encodingLength)(1) + resolveTaprootSignatureSize();
|
|
1204
|
+
const resolvedTapTreeInfo = __classPrivateFieldGet(this, _Output_tapTreeInfo, "f");
|
|
1205
|
+
if (!resolvedTapTreeInfo)
|
|
1206
|
+
throw new Error(`Error: taproot tree info not available`);
|
|
1207
|
+
const taprootSatisfaction = (0, tapMiniscript_1.satisfyTapTree)({
|
|
1208
|
+
tapTreeInfo: resolvedTapTreeInfo,
|
|
1209
|
+
preimages: __classPrivateFieldGet(this, _Output_preimages, "f"),
|
|
1210
|
+
signatures: resolveTaprootSignatures(),
|
|
1211
|
+
...(tapLeaf !== undefined ? { tapLeaf } : {})
|
|
1212
|
+
});
|
|
1213
|
+
return 41 * 4 + taprootSatisfaction.totalWitnessSize;
|
|
1085
1214
|
}
|
|
1086
1215
|
else if (isTR && (!expansion || expansion === 'tr(@0)')) {
|
|
1087
1216
|
if (!isSegwitTx)
|
|
@@ -1089,8 +1218,8 @@ expansion=${expansion}, isPKH=${isPKH}, isWPKH=${isWPKH}, isSH=${isSH}, isTR=${i
|
|
|
1089
1218
|
return (
|
|
1090
1219
|
// Non-segwit: (txid:32) + (vout:4) + (sequence:4) + (script_len:1)
|
|
1091
1220
|
41 * 4 +
|
|
1092
|
-
// Segwit: (push_count:1) + (sig_length(1) + schnorr_sig(64
|
|
1093
|
-
(1 +
|
|
1221
|
+
// Segwit: (push_count:1) + (sig_length(1) + schnorr_sig(64/65))
|
|
1222
|
+
((0, varuint_bitcoin_1.encodingLength)(1) + resolveTaprootSignatureSize()));
|
|
1094
1223
|
}
|
|
1095
1224
|
else {
|
|
1096
1225
|
throw new Error(errorMsg);
|
|
@@ -1129,13 +1258,6 @@ expansion=${expansion}, isPKH=${isPKH}, isWPKH=${isWPKH}, isSH=${isSH}, isTR=${i
|
|
|
1129
1258
|
throw new Error(errorMsg);
|
|
1130
1259
|
}
|
|
1131
1260
|
}
|
|
1132
|
-
/** @deprecated - Use updatePsbtAsInput instead
|
|
1133
|
-
* @hidden
|
|
1134
|
-
*/
|
|
1135
|
-
updatePsbt(params) {
|
|
1136
|
-
this.updatePsbtAsInput(params);
|
|
1137
|
-
return params.psbt.data.inputs.length - 1;
|
|
1138
|
-
}
|
|
1139
1261
|
/**
|
|
1140
1262
|
* Sets this output as an input of the provided `psbt` and updates the
|
|
1141
1263
|
* `psbt` locktime if required by the descriptor.
|
|
@@ -1163,6 +1285,10 @@ expansion=${expansion}, isPKH=${isPKH}, isWPKH=${isWPKH}, isSH=${isSH}, isTR=${i
|
|
|
1163
1285
|
*
|
|
1164
1286
|
* @returns A finalizer function to be used after signing the `psbt`.
|
|
1165
1287
|
* This function ensures that this input is properly finalized.
|
|
1288
|
+
* The finalizer completes the PSBT input by adding the unlocking script
|
|
1289
|
+
* (`scriptWitness` or `scriptSig`) that satisfies this `Output`'s spending
|
|
1290
|
+
* conditions. Because these scripts include signatures, you should finish
|
|
1291
|
+
* all signing operations before calling the finalizer.
|
|
1166
1292
|
* The finalizer has this signature:
|
|
1167
1293
|
*
|
|
1168
1294
|
* `( { psbt, validate = true } : { psbt: Psbt; validate: boolean | undefined } ) => void`
|
|
@@ -1170,6 +1296,10 @@ expansion=${expansion}, isPKH=${isPKH}, isWPKH=${isWPKH}, isSH=${isSH}, isTR=${i
|
|
|
1170
1296
|
*/
|
|
1171
1297
|
updatePsbtAsInput({ psbt, txHex, txId, value, vout, //vector output index
|
|
1172
1298
|
rbf = true }) {
|
|
1299
|
+
if (value !== undefined && typeof value !== 'bigint')
|
|
1300
|
+
throw new Error(`Error: value must be a bigint`);
|
|
1301
|
+
if (value !== undefined && value < 0n)
|
|
1302
|
+
throw new Error(`Error: value must be >= 0n`);
|
|
1173
1303
|
if (txHex === undefined) {
|
|
1174
1304
|
console.warn(`Warning: missing txHex may allow fee attacks`);
|
|
1175
1305
|
}
|
|
@@ -1183,15 +1313,46 @@ expansion=${expansion}, isPKH=${isPKH}, isWPKH=${isWPKH}, isSH=${isSH}, isTR=${i
|
|
|
1183
1313
|
//This should only happen when using addr() expressions
|
|
1184
1314
|
throw new Error(`Error: could not determine whether this is a taproot descriptor`);
|
|
1185
1315
|
}
|
|
1186
|
-
const
|
|
1316
|
+
const paymentInternalPubkey = this.getPayment().internalPubkey;
|
|
1317
|
+
const tapInternalKey = isTaproot
|
|
1318
|
+
? paymentInternalPubkey
|
|
1319
|
+
? paymentInternalPubkey
|
|
1320
|
+
: undefined
|
|
1321
|
+
: undefined;
|
|
1322
|
+
let tapLeafScript;
|
|
1323
|
+
let tapBip32Derivation;
|
|
1324
|
+
if (isTaproot && __classPrivateFieldGet(this, _Output_taprootSpendPath, "f") === 'script') {
|
|
1325
|
+
const tapTreeInfo = __classPrivateFieldGet(this, _Output_tapTreeInfo, "f");
|
|
1326
|
+
if (!tapTreeInfo)
|
|
1327
|
+
throw new Error(`Error: taprootSpendPath=script requires taproot tree info`);
|
|
1328
|
+
if (!tapInternalKey)
|
|
1329
|
+
throw new Error(`Error: taprootSpendPath=script requires taproot internal key`);
|
|
1330
|
+
const taprootLeafMetadata = (0, tapMiniscript_1.buildTaprootLeafPsbtMetadata)({
|
|
1331
|
+
tapTreeInfo,
|
|
1332
|
+
internalPubkey: tapInternalKey
|
|
1333
|
+
});
|
|
1334
|
+
tapLeafScript = taprootLeafMetadata.map(({ leaf, controlBlock }) => ({
|
|
1335
|
+
script: leaf.tapScript,
|
|
1336
|
+
leafVersion: leaf.version,
|
|
1337
|
+
controlBlock
|
|
1338
|
+
}));
|
|
1339
|
+
const internalKeyInfo = __classPrivateFieldGet(this, _Output_expansionMap, "f")?.['@0'];
|
|
1340
|
+
if (!internalKeyInfo)
|
|
1341
|
+
throw new Error(`Error: taproot internal key info not available in expansionMap`);
|
|
1342
|
+
tapBip32Derivation = (0, tapMiniscript_1.buildTaprootBip32Derivations)({
|
|
1343
|
+
tapTreeInfo,
|
|
1344
|
+
internalKeyInfo
|
|
1345
|
+
});
|
|
1346
|
+
}
|
|
1347
|
+
const index = (0, psbt_1.addPsbtInput)({
|
|
1187
1348
|
psbt,
|
|
1188
1349
|
vout,
|
|
1189
1350
|
...(txHex !== undefined ? { txHex } : {}),
|
|
1190
1351
|
...(txId !== undefined ? { txId } : {}),
|
|
1191
1352
|
...(value !== undefined ? { value } : {}),
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1353
|
+
tapInternalKey,
|
|
1354
|
+
tapLeafScript,
|
|
1355
|
+
tapBip32Derivation,
|
|
1195
1356
|
sequence: this.getSequence(),
|
|
1196
1357
|
locktime: this.getLockTime(),
|
|
1197
1358
|
keysInfo: __classPrivateFieldGet(this, _Output_expansionMap, "f") ? Object.values(__classPrivateFieldGet(this, _Output_expansionMap, "f")) : [],
|
|
@@ -1201,7 +1362,64 @@ expansion=${expansion}, isPKH=${isPKH}, isWPKH=${isWPKH}, isSH=${isSH}, isTR=${i
|
|
|
1201
1362
|
redeemScript: this.getRedeemScript(),
|
|
1202
1363
|
rbf
|
|
1203
1364
|
});
|
|
1204
|
-
|
|
1365
|
+
//The finalizer adds the unlocking script (scriptSig/scriptWitness) once
|
|
1366
|
+
//signatures are ready.
|
|
1367
|
+
const finalizer = ({ psbt, validate = true }) => {
|
|
1368
|
+
if (validate &&
|
|
1369
|
+
!psbt.validateSignaturesOfInput(index, signatureValidator)) {
|
|
1370
|
+
throw new Error(`Error: invalid signatures on input ${index}`);
|
|
1371
|
+
}
|
|
1372
|
+
//An index must be passed since finding the index in the psbt cannot be
|
|
1373
|
+
//done:
|
|
1374
|
+
//Imagine the case where you received money twice to
|
|
1375
|
+
//the same miniscript-based address. You would have the same scriptPubKey,
|
|
1376
|
+
//same sequences, ... The descriptor does not store the hash of the previous
|
|
1377
|
+
//transaction since it is a general Output instance. Indices must be kept
|
|
1378
|
+
//out of the scope of this class and then passed.
|
|
1379
|
+
__classPrivateFieldGet(this, _Output_instances, "m", _Output_assertPsbtInput).call(this, { index, psbt });
|
|
1380
|
+
if (__classPrivateFieldGet(this, _Output_isTaproot, "f") &&
|
|
1381
|
+
__classPrivateFieldGet(this, _Output_taprootSpendPath, "f") === 'script' &&
|
|
1382
|
+
!__classPrivateFieldGet(this, _Output_tapTreeInfo, "f"))
|
|
1383
|
+
throw new Error(`Error: taprootSpendPath=script requires taproot tree info`);
|
|
1384
|
+
if (__classPrivateFieldGet(this, _Output_tapTreeInfo, "f") && __classPrivateFieldGet(this, _Output_taprootSpendPath, "f") === 'script') {
|
|
1385
|
+
const input = psbt.data.inputs[index];
|
|
1386
|
+
const tapLeafScript = input?.tapLeafScript;
|
|
1387
|
+
if (!tapLeafScript || tapLeafScript.length === 0)
|
|
1388
|
+
throw new Error(`Error: cannot finalize taproot script-path without tapLeafScript`);
|
|
1389
|
+
const tapScriptSig = input?.tapScriptSig;
|
|
1390
|
+
if (!tapScriptSig || tapScriptSig.length === 0)
|
|
1391
|
+
throw new Error(`Error: cannot finalize taproot script-path without tapScriptSig`);
|
|
1392
|
+
const taprootSatisfaction = this.getTapScriptSatisfaction(tapScriptSig);
|
|
1393
|
+
const matchingLeaf = tapLeafScript.find(leafScript => (0, uint8array_tools_1.compare)((0, bitcoinjs_lib_internals_1.tapleafHash)({
|
|
1394
|
+
output: leafScript.script,
|
|
1395
|
+
version: leafScript.leafVersion
|
|
1396
|
+
}), taprootSatisfaction.tapLeafHash) === 0);
|
|
1397
|
+
if (!matchingLeaf)
|
|
1398
|
+
throw new Error(`Error: tapLeafScript does not match planned tapLeafHash`);
|
|
1399
|
+
if ((0, uint8array_tools_1.compare)(matchingLeaf.script, taprootSatisfaction.leaf.tapScript) !==
|
|
1400
|
+
0 ||
|
|
1401
|
+
matchingLeaf.leafVersion !== taprootSatisfaction.leaf.version)
|
|
1402
|
+
throw new Error(`Error: tapLeafScript does not match planned leaf script`);
|
|
1403
|
+
const witness = [
|
|
1404
|
+
...taprootSatisfaction.stackItems,
|
|
1405
|
+
matchingLeaf.script,
|
|
1406
|
+
matchingLeaf.controlBlock
|
|
1407
|
+
];
|
|
1408
|
+
const finalScriptWitness = (0, bitcoinjs_lib_internals_1.witnessStackToScriptWitness)(witness);
|
|
1409
|
+
psbt.finalizeTaprootInput(index, taprootSatisfaction.tapLeafHash, () => ({ finalScriptWitness }));
|
|
1410
|
+
}
|
|
1411
|
+
else if (!__classPrivateFieldGet(this, _Output_miniscript, "f")) {
|
|
1412
|
+
//Use standard finalizers
|
|
1413
|
+
psbt.finalizeInput(index);
|
|
1414
|
+
}
|
|
1415
|
+
else {
|
|
1416
|
+
const signatures = psbt.data.inputs[index]?.partialSig;
|
|
1417
|
+
if (!signatures)
|
|
1418
|
+
throw new Error(`Error: cannot finalize without signatures`);
|
|
1419
|
+
const { scriptSatisfaction } = this.getScriptSatisfaction(signatures);
|
|
1420
|
+
psbt.finalizeInput(index, (0, psbt_1.finalScriptsFuncFactory)(scriptSatisfaction, __classPrivateFieldGet(this, _Output_network, "f")));
|
|
1421
|
+
}
|
|
1422
|
+
};
|
|
1205
1423
|
return finalizer;
|
|
1206
1424
|
}
|
|
1207
1425
|
/**
|
|
@@ -1212,63 +1430,12 @@ expansion=${expansion}, isPKH=${isPKH}, isWPKH=${isWPKH}, isSH=${isSH}, isTR=${i
|
|
|
1212
1430
|
* @param params.value - The value for the output in satoshis.
|
|
1213
1431
|
*/
|
|
1214
1432
|
updatePsbtAsOutput({ psbt, value }) {
|
|
1433
|
+
if (typeof value !== 'bigint')
|
|
1434
|
+
throw new Error(`Error: value must be a bigint`);
|
|
1435
|
+
if (value < 0n)
|
|
1436
|
+
throw new Error(`Error: value must be >= 0n`);
|
|
1215
1437
|
psbt.addOutput({ script: this.getScriptPubKey(), value });
|
|
1216
1438
|
}
|
|
1217
|
-
/**
|
|
1218
|
-
* Finalizes a PSBT input by adding the necessary unlocking script that satisfies this `Output`'s
|
|
1219
|
-
* spending conditions.
|
|
1220
|
-
*
|
|
1221
|
-
* 🔴 IMPORTANT 🔴
|
|
1222
|
-
* It is STRONGLY RECOMMENDED to use the finalizer function returned by
|
|
1223
|
-
* {@link _Internal_.Output.updatePsbtAsInput | `updatePsbtAsInput`} instead
|
|
1224
|
-
* of calling this method directly.
|
|
1225
|
-
* This approach eliminates the need to manage the `Output` instance and the
|
|
1226
|
-
* input's index, simplifying the process.
|
|
1227
|
-
*
|
|
1228
|
-
* The `finalizePsbtInput` method completes a PSBT input by adding the
|
|
1229
|
-
* unlocking script (`scriptWitness` or `scriptSig`) that satisfies
|
|
1230
|
-
* this `Output`'s spending conditions. Bear in mind that both
|
|
1231
|
-
* `scriptSig` and `scriptWitness` incorporate signatures. As such, you
|
|
1232
|
-
* should complete all necessary signing operations before calling this
|
|
1233
|
-
* method.
|
|
1234
|
-
*
|
|
1235
|
-
* For each unspent output from a previous transaction that you're
|
|
1236
|
-
* referencing in a `psbt` as an input to be spent, apply this method as
|
|
1237
|
-
* follows: `output.finalizePsbtInput({ index, psbt })`.
|
|
1238
|
-
*
|
|
1239
|
-
* It's essential to specify the exact position (or `index`) of the input in
|
|
1240
|
-
* the `psbt` that references this unspent `Output`. This `index` should
|
|
1241
|
-
* align with the value returned by the `updatePsbtAsInput` method.
|
|
1242
|
-
* Note:
|
|
1243
|
-
* The `index` corresponds to the position of the input in the `psbt`.
|
|
1244
|
-
* To get this index, right after calling `updatePsbtAsInput()`, use:
|
|
1245
|
-
* `index = psbt.data.inputs.length - 1`.
|
|
1246
|
-
*/
|
|
1247
|
-
finalizePsbtInput({ index, psbt, validate = true }) {
|
|
1248
|
-
if (validate &&
|
|
1249
|
-
!psbt.validateSignaturesOfInput(index, signatureValidator)) {
|
|
1250
|
-
throw new Error(`Error: invalid signatures on input ${index}`);
|
|
1251
|
-
}
|
|
1252
|
-
//An index must be passed since finding the index in the psbt cannot be
|
|
1253
|
-
//done:
|
|
1254
|
-
//Imagine the case where you received money twice to
|
|
1255
|
-
//the same miniscript-based address. You would have the same scriptPubKey,
|
|
1256
|
-
//same sequences, ... The descriptor does not store the hash of the previous
|
|
1257
|
-
//transaction since it is a general Descriptor object. Indices must be kept
|
|
1258
|
-
//out of the scope of this class and then passed.
|
|
1259
|
-
__classPrivateFieldGet(this, _Output_instances, "m", _Output_assertPsbtInput).call(this, { index, psbt });
|
|
1260
|
-
if (!__classPrivateFieldGet(this, _Output_miniscript, "f")) {
|
|
1261
|
-
//Use standard finalizers
|
|
1262
|
-
psbt.finalizeInput(index);
|
|
1263
|
-
}
|
|
1264
|
-
else {
|
|
1265
|
-
const signatures = psbt.data.inputs[index]?.partialSig;
|
|
1266
|
-
if (!signatures)
|
|
1267
|
-
throw new Error(`Error: cannot finalize without signatures`);
|
|
1268
|
-
const scriptSatisfaction = this.getScriptSatisfaction(signatures);
|
|
1269
|
-
psbt.finalizeInput(index, (0, psbt_1.finalScriptsFuncFactory)(scriptSatisfaction, __classPrivateFieldGet(this, _Output_network, "f")));
|
|
1270
|
-
}
|
|
1271
|
-
}
|
|
1272
1439
|
/**
|
|
1273
1440
|
* Decomposes the descriptor used to form this `Output` into its elemental
|
|
1274
1441
|
* parts. See {@link ExpansionMap ExpansionMap} for a detailed explanation.
|
|
@@ -1284,18 +1451,80 @@ expansion=${expansion}, isPKH=${isPKH}, isWPKH=${isWPKH}, isSH=${isSH}, isTR=${i
|
|
|
1284
1451
|
...(__classPrivateFieldGet(this, _Output_expandedMiniscript, "f") !== undefined
|
|
1285
1452
|
? { expandedMiniscript: __classPrivateFieldGet(this, _Output_expandedMiniscript, "f") }
|
|
1286
1453
|
: {}),
|
|
1454
|
+
...(__classPrivateFieldGet(this, _Output_tapTreeExpression, "f") !== undefined
|
|
1455
|
+
? { tapTreeExpression: __classPrivateFieldGet(this, _Output_tapTreeExpression, "f") }
|
|
1456
|
+
: {}),
|
|
1457
|
+
...(__classPrivateFieldGet(this, _Output_tapTree, "f") !== undefined ? { tapTree: __classPrivateFieldGet(this, _Output_tapTree, "f") } : {}),
|
|
1458
|
+
...(__classPrivateFieldGet(this, _Output_tapTreeInfo, "f") !== undefined
|
|
1459
|
+
? { tapTreeInfo: __classPrivateFieldGet(this, _Output_tapTreeInfo, "f") }
|
|
1460
|
+
: {}),
|
|
1287
1461
|
...(__classPrivateFieldGet(this, _Output_expansionMap, "f") !== undefined
|
|
1288
1462
|
? { expansionMap: __classPrivateFieldGet(this, _Output_expansionMap, "f") }
|
|
1289
1463
|
: {})
|
|
1290
1464
|
};
|
|
1291
1465
|
}
|
|
1292
1466
|
}
|
|
1293
|
-
_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(),
|
|
1467
|
+
_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_tapTreeExpression = new WeakMap(), _Output_tapTree = new WeakMap(), _Output_tapTreeInfo = new WeakMap(), _Output_taprootSpendPath = new WeakMap(), _Output_tapLeaf = new WeakMap(), _Output_expansionMap = new WeakMap(), _Output_network = new WeakMap(), _Output_instances = new WeakSet(), _Output_resolveMiniscriptSignersPubKeys = function _Output_resolveMiniscriptSignersPubKeys() {
|
|
1468
|
+
//If the user did not provide a pubkey subset (signersPubKeys), assume all
|
|
1469
|
+
//miniscript pubkeys can sign.
|
|
1470
|
+
if (__classPrivateFieldGet(this, _Output_signersPubKeys, "f"))
|
|
1471
|
+
return __classPrivateFieldGet(this, _Output_signersPubKeys, "f");
|
|
1472
|
+
const expansionMap = __classPrivateFieldGet(this, _Output_expansionMap, "f");
|
|
1473
|
+
if (!expansionMap)
|
|
1474
|
+
throw new Error(`Error: expansionMap not available for miniscript`);
|
|
1475
|
+
return Object.values(expansionMap).map(keyInfo => {
|
|
1476
|
+
const pubkey = keyInfo.pubkey;
|
|
1477
|
+
if (!pubkey)
|
|
1478
|
+
throw new Error(`Error: miniscript key missing pubkey`);
|
|
1479
|
+
return pubkey;
|
|
1480
|
+
});
|
|
1481
|
+
}, _Output_assertMiniscriptSatisfactionResourceLimits = function _Output_assertMiniscriptSatisfactionResourceLimits(scriptSatisfaction) {
|
|
1482
|
+
if (!__classPrivateFieldGet(this, _Output_miniscript, "f"))
|
|
1483
|
+
return;
|
|
1484
|
+
const satisfactionStackItems = bitcoinjs_lib_1.script.toStack(scriptSatisfaction);
|
|
1485
|
+
// For wsh(...) and sh(wsh(...)), enforce witness stack limits.
|
|
1486
|
+
if (__classPrivateFieldGet(this, _Output_isSegwit, "f") && !__classPrivateFieldGet(this, _Output_isTaproot, "f")) {
|
|
1487
|
+
(0, resourceLimits_1.assertWitnessV0SatisfactionResourceLimits)({
|
|
1488
|
+
stackItems: satisfactionStackItems
|
|
1489
|
+
});
|
|
1490
|
+
return;
|
|
1491
|
+
}
|
|
1492
|
+
if (!__classPrivateFieldGet(this, _Output_isSegwit, "f")) {
|
|
1493
|
+
// In legacy P2SH, after scriptSig execution, the stack is:
|
|
1494
|
+
// [ ...satisfactionStackItems, redeemScript ]
|
|
1495
|
+
// Consensus limits apply here: each element must be <= 520 bytes and total
|
|
1496
|
+
// stack items must be <= 1000.
|
|
1497
|
+
// redeemScript size is already validated during script construction
|
|
1498
|
+
// to fail early (look for MAX_SCRIPT_ELEMENT_SIZE checks in this file).
|
|
1499
|
+
// Below we re-validate again the redeemScript + the rest of stack items.
|
|
1500
|
+
const redeemScript = __classPrivateFieldGet(this, _Output_redeemScript, "f");
|
|
1501
|
+
if (!redeemScript)
|
|
1502
|
+
throw new Error(`Error: redeemScript not available for P2SH spend`);
|
|
1503
|
+
(0, resourceLimits_1.assertConsensusStackResourceLimits)({
|
|
1504
|
+
stackItems: [...satisfactionStackItems, redeemScript]
|
|
1505
|
+
});
|
|
1506
|
+
(0, resourceLimits_1.assertP2shScriptSigStandardSize)({
|
|
1507
|
+
scriptSatisfaction,
|
|
1508
|
+
redeemScript,
|
|
1509
|
+
network: __classPrivateFieldGet(this, _Output_network, "f")
|
|
1510
|
+
});
|
|
1511
|
+
}
|
|
1512
|
+
}, _Output_resolveTapTreeSignersPubKeys = function _Output_resolveTapTreeSignersPubKeys() {
|
|
1513
|
+
//If the user did not provide a pubkey subset (signersPubKeys), assume all
|
|
1514
|
+
//taproot leaf pubkeys can sign.
|
|
1515
|
+
const tapTreeInfo = __classPrivateFieldGet(this, _Output_tapTreeInfo, "f");
|
|
1516
|
+
if (!tapTreeInfo)
|
|
1517
|
+
throw new Error(`Error: taproot tree info not available`);
|
|
1518
|
+
const candidatePubkeys = __classPrivateFieldGet(this, _Output_signersPubKeys, "f")
|
|
1519
|
+
? __classPrivateFieldGet(this, _Output_signersPubKeys, "f").map(tapMiniscript_1.normalizeTaprootPubkey)
|
|
1520
|
+
: (0, tapMiniscript_1.collectTapTreePubkeys)(tapTreeInfo);
|
|
1521
|
+
return Array.from(new Set(candidatePubkeys.map(pubkey => (0, uint8array_tools_1.toHex)(pubkey)))).map(hex => (0, uint8array_tools_1.fromHex)(hex));
|
|
1522
|
+
}, _Output_getConstraints = function _Output_getConstraints() {
|
|
1294
1523
|
const miniscript = __classPrivateFieldGet(this, _Output_miniscript, "f");
|
|
1295
1524
|
const preimages = __classPrivateFieldGet(this, _Output_preimages, "f");
|
|
1296
1525
|
const expandedMiniscript = __classPrivateFieldGet(this, _Output_expandedMiniscript, "f");
|
|
1297
1526
|
const expansionMap = __classPrivateFieldGet(this, _Output_expansionMap, "f");
|
|
1298
|
-
const
|
|
1527
|
+
const tapTreeInfo = __classPrivateFieldGet(this, _Output_tapTreeInfo, "f");
|
|
1299
1528
|
//Create a method. solvePreimages to solve them.
|
|
1300
1529
|
if (miniscript) {
|
|
1301
1530
|
if (expandedMiniscript === undefined || expansionMap === undefined)
|
|
@@ -1303,21 +1532,34 @@ expansion=${expansion}, isPKH=${isPKH}, isWPKH=${isWPKH}, isSH=${isSH}, isTR=${i
|
|
|
1303
1532
|
//We create some fakeSignatures since we may not have them yet.
|
|
1304
1533
|
//We only want to retrieve the nLockTime and nSequence of the satisfaction and
|
|
1305
1534
|
//signatures don't matter
|
|
1306
|
-
const fakeSignatures =
|
|
1535
|
+
const fakeSignatures = __classPrivateFieldGet(this, _Output_instances, "m", _Output_resolveMiniscriptSignersPubKeys).call(this).map(pubkey => ({
|
|
1307
1536
|
pubkey,
|
|
1308
|
-
|
|
1309
|
-
signature: Buffer.alloc(72, 0)
|
|
1537
|
+
signature: new Uint8Array(ECDSA_FAKE_SIGNATURE_SIZE)
|
|
1310
1538
|
}));
|
|
1311
|
-
const
|
|
1539
|
+
const satisfaction = (0, miniscript_1.satisfyMiniscript)({
|
|
1312
1540
|
expandedMiniscript,
|
|
1313
1541
|
expansionMap,
|
|
1314
1542
|
signatures: fakeSignatures,
|
|
1315
1543
|
preimages
|
|
1316
1544
|
});
|
|
1317
|
-
|
|
1545
|
+
__classPrivateFieldGet(this, _Output_instances, "m", _Output_assertMiniscriptSatisfactionResourceLimits).call(this, satisfaction.scriptSatisfaction);
|
|
1546
|
+
const { nLockTime, nSequence } = satisfaction;
|
|
1547
|
+
return { nLockTime, nSequence, tapLeafHash: undefined };
|
|
1318
1548
|
}
|
|
1319
|
-
else
|
|
1320
|
-
|
|
1549
|
+
else if (tapTreeInfo && __classPrivateFieldGet(this, _Output_taprootSpendPath, "f") === 'script') {
|
|
1550
|
+
const fakeSignatures = __classPrivateFieldGet(this, _Output_instances, "m", _Output_resolveTapTreeSignersPubKeys).call(this).map(pubkey => ({
|
|
1551
|
+
pubkey,
|
|
1552
|
+
signature: new Uint8Array(TAPROOT_FAKE_SIGNATURE_SIZE)
|
|
1553
|
+
}));
|
|
1554
|
+
const { nLockTime, nSequence, tapLeafHash } = (0, tapMiniscript_1.satisfyTapTree)({
|
|
1555
|
+
tapTreeInfo,
|
|
1556
|
+
preimages: __classPrivateFieldGet(this, _Output_preimages, "f"),
|
|
1557
|
+
signatures: fakeSignatures,
|
|
1558
|
+
...(__classPrivateFieldGet(this, _Output_tapLeaf, "f") !== undefined ? { tapLeaf: __classPrivateFieldGet(this, _Output_tapLeaf, "f") } : {})
|
|
1559
|
+
});
|
|
1560
|
+
return { nLockTime, nSequence, tapLeafHash };
|
|
1561
|
+
}
|
|
1562
|
+
return undefined;
|
|
1321
1563
|
}, _Output_assertPsbtInput = function _Output_assertPsbtInput({ psbt, index }) {
|
|
1322
1564
|
const input = psbt.data.inputs[index];
|
|
1323
1565
|
const txInput = psbt.txInputs[index];
|
|
@@ -1346,29 +1588,18 @@ expansion=${expansion}, isPKH=${isPKH}, isWPKH=${isWPKH}, isSH=${isSH}, isTR=${i
|
|
|
1346
1588
|
? 0xffffffff
|
|
1347
1589
|
: 0xfffffffe;
|
|
1348
1590
|
const sequenceRBF = sequence !== undefined ? sequence : 0xfffffffd;
|
|
1349
|
-
const
|
|
1350
|
-
?
|
|
1351
|
-
:
|
|
1352
|
-
if (
|
|
1591
|
+
const eqBytes = (bytes1, bytes2) => bytes1 === undefined || bytes2 === undefined
|
|
1592
|
+
? bytes1 === bytes2
|
|
1593
|
+
: (0, uint8array_tools_1.compare)(bytes1, bytes2) === 0;
|
|
1594
|
+
if ((0, uint8array_tools_1.compare)(scriptPubKey, this.getScriptPubKey()) !== 0 ||
|
|
1353
1595
|
(sequenceRBF !== inputSequence && sequenceNoRBF !== inputSequence) ||
|
|
1354
1596
|
locktime !== psbt.locktime ||
|
|
1355
|
-
!
|
|
1356
|
-
!
|
|
1597
|
+
!eqBytes(this.getWitnessScript(), input.witnessScript) ||
|
|
1598
|
+
!eqBytes(this.getRedeemScript(), input.redeemScript)) {
|
|
1357
1599
|
throw new Error(`Error: cannot finalize psbt index ${index} since it does not correspond to this descriptor`);
|
|
1358
1600
|
}
|
|
1359
1601
|
};
|
|
1360
|
-
/**
|
|
1361
|
-
* @hidden
|
|
1362
|
-
* @deprecated Use `Output` instead
|
|
1363
|
-
*/
|
|
1364
|
-
class Descriptor extends Output {
|
|
1365
|
-
constructor({ expression, ...rest }) {
|
|
1366
|
-
super({ descriptor: expression, ...rest });
|
|
1367
|
-
}
|
|
1368
|
-
}
|
|
1369
1602
|
return {
|
|
1370
|
-
// deprecated TAG must also be below so it is exported to descriptors.d.ts
|
|
1371
|
-
/** @deprecated @hidden */ Descriptor,
|
|
1372
1603
|
Output,
|
|
1373
1604
|
parseKeyExpression,
|
|
1374
1605
|
expand,
|