@bitcoinerlab/descriptors 2.3.6 → 3.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +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 +608 -283
- 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 +147 -119
- package/dist/miniscript.d.ts +20 -6
- package/dist/miniscript.js +64 -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/tapMiniscript.d.ts +215 -0
- package/dist/tapMiniscript.js +515 -0
- package/dist/tapTree.d.ts +86 -0
- package/dist/tapTree.js +167 -0
- package/dist/types.d.ts +52 -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,37 @@ 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
|
+
const MAX_PUBKEYS_PER_MULTI_A = 999;
|
|
143
|
+
// Helper: parse sortedmulti_a(M, k1, k2,...)
|
|
144
|
+
function parseSortedMultiA(inner) {
|
|
145
|
+
const parts = inner.split(',').map(p => p.trim());
|
|
146
|
+
if (parts.length < 2)
|
|
147
|
+
throw new Error(`sortedmulti_a(): must contain M and at least one key: ${inner}`);
|
|
148
|
+
const m = Number(parts[0]);
|
|
149
|
+
if (!Number.isInteger(m) || m < 1)
|
|
150
|
+
throw new Error(`sortedmulti_a(): invalid M=${parts[0]}`);
|
|
151
|
+
const keyExpressions = parts.slice(1);
|
|
152
|
+
if (keyExpressions.length < m)
|
|
153
|
+
throw new Error(`sortedmulti_a(): M cannot exceed number of keys: ${inner}`);
|
|
154
|
+
if (keyExpressions.length > MAX_PUBKEYS_PER_MULTI_A)
|
|
155
|
+
throw new Error(`sortedmulti_a(): descriptors support up to ${MAX_PUBKEYS_PER_MULTI_A} keys.`);
|
|
156
|
+
return { m, keyExpressions };
|
|
157
|
+
}
|
|
158
|
+
function parseTrExpression(expression) {
|
|
159
|
+
if (!expression.startsWith('tr(') || !expression.endsWith(')'))
|
|
160
|
+
throw new Error(`Error: invalid descriptor ${expression}`);
|
|
161
|
+
const innerExpression = expression.slice(3, -1).trim();
|
|
162
|
+
if (!innerExpression)
|
|
163
|
+
throw new Error(`Error: invalid descriptor ${expression}`);
|
|
164
|
+
const splitResult = (0, parseUtils_1.splitTopLevelComma)({
|
|
165
|
+
expression: innerExpression,
|
|
166
|
+
onError: () => new Error(`Error: invalid descriptor ${expression}`)
|
|
167
|
+
});
|
|
168
|
+
//if no commas: innerExpression === keyExpression
|
|
169
|
+
if (!splitResult)
|
|
170
|
+
return { keyExpression: innerExpression };
|
|
171
|
+
return { keyExpression: splitResult.left, treeExpression: splitResult.right };
|
|
172
|
+
}
|
|
151
173
|
/**
|
|
152
174
|
* Constructs the necessary functions and classes for working with descriptors
|
|
153
175
|
* using an external elliptic curve (ecc) library.
|
|
@@ -156,12 +178,8 @@ function parseSortedMulti(inner) {
|
|
|
156
178
|
* provides methods to create, sign, and finalize PSBTs based on descriptor
|
|
157
179
|
* expressions.
|
|
158
180
|
*
|
|
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
181
|
* The Factory also returns utility methods like `expand` (detailed below)
|
|
164
|
-
* and `parseKeyExpression` (see {@link
|
|
182
|
+
* and `parseKeyExpression` (see {@link KeyExpressionParser}).
|
|
165
183
|
*
|
|
166
184
|
* Additionally, for convenience, the function returns `BIP32` and `ECPair`.
|
|
167
185
|
* These are {@link https://github.com/bitcoinjs bitcoinjs-lib} classes designed
|
|
@@ -174,7 +192,7 @@ function parseSortedMulti(inner) {
|
|
|
174
192
|
* [@bitcoinerlab/secp256k1](https://github.com/bitcoinerlab/secp256k1).
|
|
175
193
|
*/
|
|
176
194
|
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,
|
|
195
|
+
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
196
|
(0, bitcoinjs_lib_1.initEccLib)(ecc); //Taproot requires initEccLib
|
|
179
197
|
const BIP32 = (0, bip32_1.BIP32Factory)(ecc);
|
|
180
198
|
const ECPair = (0, ecpair_1.ECPairFactory)(ecc);
|
|
@@ -204,14 +222,69 @@ function DescriptorsFactory(ecc) {
|
|
|
204
222
|
});
|
|
205
223
|
};
|
|
206
224
|
/**
|
|
207
|
-
*
|
|
208
|
-
*
|
|
209
|
-
*
|
|
225
|
+
* Builds a taproot leaf expansion override for descriptor-level
|
|
226
|
+
* `sortedmulti_a(...)`.
|
|
227
|
+
*
|
|
228
|
+
* Why this exists:
|
|
229
|
+
* - `sortedmulti_a` is a descriptor script expression (not a Miniscript
|
|
230
|
+
* fragment).
|
|
231
|
+
*
|
|
232
|
+
* What this does:
|
|
233
|
+
* - Resolves each key expression to a concrete pubkey and builds a leaf-local
|
|
234
|
+
* placeholder map (`@0`, `@1`, ... in input order).
|
|
235
|
+
* - Derives the internal compilation form by sorting placeholders by pubkey
|
|
236
|
+
* bytes and lowering to `multi_a(...)`.
|
|
237
|
+
* - Compiles tapscript from that lowered form and returns it as override
|
|
238
|
+
* data.
|
|
239
|
+
*
|
|
240
|
+
* Returns `undefined` for non-`sortedmulti_a` leaves so normal taproot miniscript
|
|
241
|
+
* expansion/compilation is used.
|
|
210
242
|
*/
|
|
211
|
-
function
|
|
212
|
-
if (
|
|
213
|
-
|
|
214
|
-
|
|
243
|
+
function buildTapLeafSortedMultiAOverride({ expression, network = bitcoinjs_lib_1.networks.bitcoin }) {
|
|
244
|
+
if (!/\bsortedmulti_a\(/.test(expression))
|
|
245
|
+
return undefined;
|
|
246
|
+
const trimmed = expression.trim();
|
|
247
|
+
const match = trimmed.match(/^sortedmulti_a\((.*)\)$/);
|
|
248
|
+
if (!match)
|
|
249
|
+
throw new Error(`Error: sortedmulti_a() must be a standalone taproot leaf expression`);
|
|
250
|
+
const inner = match[1];
|
|
251
|
+
if (!inner)
|
|
252
|
+
throw new Error(`Error: invalid sortedmulti_a() expression: ${expression}`);
|
|
253
|
+
const { m, keyExpressions } = parseSortedMultiA(inner);
|
|
254
|
+
const keyInfos = keyExpressions.map(keyExpression => {
|
|
255
|
+
const keyInfo = parseKeyExpression({
|
|
256
|
+
keyExpression,
|
|
257
|
+
isSegwit: true,
|
|
258
|
+
isTaproot: true,
|
|
259
|
+
network
|
|
260
|
+
});
|
|
261
|
+
if (!keyInfo.pubkey)
|
|
262
|
+
throw new Error(`Error: sortedmulti_a() key must resolve to a concrete pubkey: ${keyExpression}`);
|
|
263
|
+
return keyInfo;
|
|
264
|
+
});
|
|
265
|
+
const expansionMap = {};
|
|
266
|
+
keyInfos.forEach((keyInfo, index) => {
|
|
267
|
+
expansionMap[`@${index}`] = keyInfo;
|
|
268
|
+
});
|
|
269
|
+
// sortedmulti_a is descriptor-level sugar. We preserve it in
|
|
270
|
+
// expandedExpression, but compile tapscript from its internal multi_a
|
|
271
|
+
// lowering with sorted placeholders.
|
|
272
|
+
const expandedExpression = `sortedmulti_a(${[
|
|
273
|
+
m,
|
|
274
|
+
...Object.keys(expansionMap)
|
|
275
|
+
].join(',')})`;
|
|
276
|
+
const compileExpandedMiniscript = (0, tapMiniscript_1.compileSortedMultiAExpandedExpression)({
|
|
277
|
+
expandedExpression,
|
|
278
|
+
expansionMap
|
|
279
|
+
});
|
|
280
|
+
const tapScript = (0, miniscript_1.miniscript2Script)({
|
|
281
|
+
expandedMiniscript: compileExpandedMiniscript,
|
|
282
|
+
expansionMap,
|
|
283
|
+
tapscript: true
|
|
284
|
+
});
|
|
285
|
+
return { expandedExpression, expansionMap, tapScript };
|
|
286
|
+
}
|
|
287
|
+
function expand({ descriptor, index, change, checksumRequired = false, network = bitcoinjs_lib_1.networks.bitcoin, allowMiniscriptInP2SH = false }) {
|
|
215
288
|
if (!descriptor)
|
|
216
289
|
throw new Error(`descriptor not provided`);
|
|
217
290
|
let expandedExpression;
|
|
@@ -220,6 +293,9 @@ function DescriptorsFactory(ecc) {
|
|
|
220
293
|
let isSegwit;
|
|
221
294
|
let isTaproot;
|
|
222
295
|
let expandedMiniscript;
|
|
296
|
+
let tapTreeExpression;
|
|
297
|
+
let tapTree;
|
|
298
|
+
let tapTreeInfo;
|
|
223
299
|
let payment;
|
|
224
300
|
let witnessScript;
|
|
225
301
|
let redeemScript;
|
|
@@ -232,6 +308,7 @@ function DescriptorsFactory(ecc) {
|
|
|
232
308
|
const canonicalExpression = evaluate({
|
|
233
309
|
descriptor,
|
|
234
310
|
...(index !== undefined ? { index } : {}),
|
|
311
|
+
...(change !== undefined ? { change } : {}),
|
|
235
312
|
checksumRequired
|
|
236
313
|
});
|
|
237
314
|
const isCanonicalRanged = canonicalExpression.indexOf('*') !== -1;
|
|
@@ -398,7 +475,7 @@ function DescriptorsFactory(ecc) {
|
|
|
398
475
|
throw new Error(`Error: key has no pubkey`);
|
|
399
476
|
return p.pubkey;
|
|
400
477
|
});
|
|
401
|
-
pubkeys.sort((a, b) =>
|
|
478
|
+
pubkeys.sort((a, b) => (0, uint8array_tools_1.compare)(a, b));
|
|
402
479
|
const redeem = bitcoinjs_lib_1.payments.p2ms({ m, pubkeys, network });
|
|
403
480
|
redeemScript = redeem.output;
|
|
404
481
|
if (!redeemScript)
|
|
@@ -428,7 +505,7 @@ function DescriptorsFactory(ecc) {
|
|
|
428
505
|
throw new Error(`Error: key has no pubkey`);
|
|
429
506
|
return p.pubkey;
|
|
430
507
|
});
|
|
431
|
-
pubkeys.sort((a, b) =>
|
|
508
|
+
pubkeys.sort((a, b) => (0, uint8array_tools_1.compare)(a, b));
|
|
432
509
|
const redeem = bitcoinjs_lib_1.payments.p2ms({ m, pubkeys, network });
|
|
433
510
|
witnessScript = redeem.output;
|
|
434
511
|
if (!witnessScript)
|
|
@@ -458,7 +535,7 @@ function DescriptorsFactory(ecc) {
|
|
|
458
535
|
throw new Error(`Error: key has no pubkey`);
|
|
459
536
|
return p.pubkey;
|
|
460
537
|
});
|
|
461
|
-
pubkeys.sort((a, b) =>
|
|
538
|
+
pubkeys.sort((a, b) => (0, uint8array_tools_1.compare)(a, b));
|
|
462
539
|
const redeem = bitcoinjs_lib_1.payments.p2ms({ m, pubkeys, network });
|
|
463
540
|
const wsh = bitcoinjs_lib_1.payments.p2wsh({ redeem, network });
|
|
464
541
|
witnessScript = redeem.output;
|
|
@@ -482,13 +559,10 @@ function DescriptorsFactory(ecc) {
|
|
|
482
559
|
if (!isCanonicalRanged) {
|
|
483
560
|
const script = (0, miniscript_1.miniscript2Script)({ expandedMiniscript, expansionMap });
|
|
484
561
|
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}`);
|
|
562
|
+
if (script.byteLength > resourceLimits_1.MAX_STANDARD_P2WSH_SCRIPT_SIZE) {
|
|
563
|
+
throw new Error(`Error: script is too large, ${script.byteLength} bytes is larger than ${resourceLimits_1.MAX_STANDARD_P2WSH_SCRIPT_SIZE} bytes`);
|
|
491
564
|
}
|
|
565
|
+
(0, resourceLimits_1.assertScriptNonPushOnlyOpsLimit)({ script });
|
|
492
566
|
payment = p2sh({
|
|
493
567
|
redeem: p2wsh({ redeem: { output: script, network }, network }),
|
|
494
568
|
network
|
|
@@ -508,10 +582,14 @@ function DescriptorsFactory(ecc) {
|
|
|
508
582
|
if (!miniscript)
|
|
509
583
|
throw new Error(`Error: could not get miniscript in ${descriptor}`);
|
|
510
584
|
if (allowMiniscriptInP2SH === false &&
|
|
511
|
-
//These top-level expressions
|
|
512
|
-
//
|
|
513
|
-
//
|
|
514
|
-
|
|
585
|
+
// These top-level script expressions are allowed inside sh(...).
|
|
586
|
+
// The sorted script expressions (`sortedmulti`, `sortedmulti_a`) are
|
|
587
|
+
// handled in dedicated descriptor/taproot branches and are intentionally
|
|
588
|
+
// not part of this miniscript gate.
|
|
589
|
+
// Here we only keep legacy top-level forms to avoid accepting arbitrary
|
|
590
|
+
// miniscript in P2SH unless explicitly enabled.
|
|
591
|
+
miniscript.search(/^(pk\(|pkh\(|wpkh\(|combo\(|multi\(|multi_a\()/) !==
|
|
592
|
+
0) {
|
|
515
593
|
throw new Error(`Error: Miniscript expressions can only be used in wsh`);
|
|
516
594
|
}
|
|
517
595
|
({ expandedMiniscript, expansionMap } = expandMiniscript({
|
|
@@ -523,13 +601,10 @@ function DescriptorsFactory(ecc) {
|
|
|
523
601
|
if (!isCanonicalRanged) {
|
|
524
602
|
const script = (0, miniscript_1.miniscript2Script)({ expandedMiniscript, expansionMap });
|
|
525
603
|
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}`);
|
|
604
|
+
if (script.byteLength > resourceLimits_1.MAX_SCRIPT_ELEMENT_SIZE) {
|
|
605
|
+
throw new Error(`Error: P2SH script is too large, ${script.byteLength} bytes is larger than ${resourceLimits_1.MAX_SCRIPT_ELEMENT_SIZE} bytes`);
|
|
532
606
|
}
|
|
607
|
+
(0, resourceLimits_1.assertScriptNonPushOnlyOpsLimit)({ script });
|
|
533
608
|
payment = p2sh({ redeem: { output: script, network }, network });
|
|
534
609
|
}
|
|
535
610
|
}
|
|
@@ -549,26 +624,21 @@ function DescriptorsFactory(ecc) {
|
|
|
549
624
|
if (!isCanonicalRanged) {
|
|
550
625
|
const script = (0, miniscript_1.miniscript2Script)({ expandedMiniscript, expansionMap });
|
|
551
626
|
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}`);
|
|
627
|
+
if (script.byteLength > resourceLimits_1.MAX_STANDARD_P2WSH_SCRIPT_SIZE) {
|
|
628
|
+
throw new Error(`Error: script is too large, ${script.byteLength} bytes is larger than ${resourceLimits_1.MAX_STANDARD_P2WSH_SCRIPT_SIZE} bytes`);
|
|
558
629
|
}
|
|
630
|
+
(0, resourceLimits_1.assertScriptNonPushOnlyOpsLimit)({ script });
|
|
559
631
|
payment = p2wsh({ redeem: { output: script, network }, network });
|
|
560
632
|
}
|
|
561
633
|
}
|
|
562
|
-
//tr(KEY)
|
|
563
|
-
else if (canonicalExpression.
|
|
634
|
+
//tr(KEY) or tr(KEY,TREE) - taproot
|
|
635
|
+
else if (canonicalExpression.startsWith('tr(')) {
|
|
564
636
|
isSegwit = true;
|
|
565
637
|
isTaproot = true;
|
|
566
|
-
const keyExpression = canonicalExpression
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
throw new Error(`Error: invalid expression ${expression}`);
|
|
571
|
-
expandedExpression = 'tr(@0)';
|
|
638
|
+
const { keyExpression, treeExpression } = parseTrExpression(canonicalExpression);
|
|
639
|
+
expandedExpression = treeExpression
|
|
640
|
+
? `tr(@0,${treeExpression})`
|
|
641
|
+
: 'tr(@0)';
|
|
572
642
|
const pKE = parseKeyExpression({
|
|
573
643
|
keyExpression,
|
|
574
644
|
network,
|
|
@@ -576,17 +646,41 @@ function DescriptorsFactory(ecc) {
|
|
|
576
646
|
isTaproot
|
|
577
647
|
});
|
|
578
648
|
expansionMap = { '@0': pKE };
|
|
649
|
+
if (treeExpression) {
|
|
650
|
+
tapTreeExpression = treeExpression;
|
|
651
|
+
tapTree = (0, tapTree_1.parseTapTreeExpression)(treeExpression);
|
|
652
|
+
if (!isCanonicalRanged) {
|
|
653
|
+
tapTreeInfo = (0, tapMiniscript_1.buildTapTreeInfo)({
|
|
654
|
+
tapTree,
|
|
655
|
+
network,
|
|
656
|
+
BIP32,
|
|
657
|
+
ECPair,
|
|
658
|
+
// `leafExpansionOverride` runs per leaf expression.
|
|
659
|
+
// For non-matching leaves it returns undefined and
|
|
660
|
+
// normal miniscript expansion is used;
|
|
661
|
+
// for sortedmulti_a leaves it returns descriptor-level
|
|
662
|
+
// metadata plus precompiled tapscript bytes.
|
|
663
|
+
leafExpansionOverride: (expression) => buildTapLeafSortedMultiAOverride({ expression, network })
|
|
664
|
+
});
|
|
665
|
+
}
|
|
666
|
+
}
|
|
579
667
|
if (!isCanonicalRanged) {
|
|
580
668
|
const pubkey = pKE.pubkey;
|
|
581
669
|
if (!pubkey)
|
|
582
|
-
throw new Error(`Error: could not extract a pubkey from ${
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
670
|
+
throw new Error(`Error: could not extract a pubkey from ${descriptor}`);
|
|
671
|
+
const internalPubkey = (0, tapMiniscript_1.normalizeTaprootPubkey)(pubkey);
|
|
672
|
+
if (treeExpression) {
|
|
673
|
+
if (!tapTreeInfo)
|
|
674
|
+
throw new Error(`Error: taproot tree info not available`);
|
|
675
|
+
payment = p2tr({
|
|
676
|
+
internalPubkey,
|
|
677
|
+
scriptTree: (0, tapMiniscript_1.tapTreeInfoToScriptTree)(tapTreeInfo),
|
|
678
|
+
network
|
|
679
|
+
});
|
|
680
|
+
}
|
|
681
|
+
else {
|
|
682
|
+
payment = p2tr({ internalPubkey, network });
|
|
683
|
+
}
|
|
590
684
|
}
|
|
591
685
|
}
|
|
592
686
|
else {
|
|
@@ -600,6 +694,9 @@ function DescriptorsFactory(ecc) {
|
|
|
600
694
|
...(isSegwit !== undefined ? { isSegwit } : {}),
|
|
601
695
|
...(isTaproot !== undefined ? { isTaproot } : {}),
|
|
602
696
|
...(expandedMiniscript !== undefined ? { expandedMiniscript } : {}),
|
|
697
|
+
...(tapTreeExpression !== undefined ? { tapTreeExpression } : {}),
|
|
698
|
+
...(tapTree !== undefined ? { tapTree } : {}),
|
|
699
|
+
...(tapTreeInfo !== undefined ? { tapTreeInfo } : {}),
|
|
603
700
|
...(redeemScript !== undefined ? { redeemScript } : {}),
|
|
604
701
|
...(witnessScript !== undefined ? { witnessScript } : {}),
|
|
605
702
|
isRanged,
|
|
@@ -634,7 +731,7 @@ function DescriptorsFactory(ecc) {
|
|
|
634
731
|
* @param options
|
|
635
732
|
* @throws {Error} - when descriptor is invalid
|
|
636
733
|
*/
|
|
637
|
-
constructor({ descriptor, index, checksumRequired = false, allowMiniscriptInP2SH = false, network = bitcoinjs_lib_1.networks.bitcoin, preimages = [], signersPubKeys }) {
|
|
734
|
+
constructor({ descriptor, index, change, checksumRequired = false, allowMiniscriptInP2SH = false, network = bitcoinjs_lib_1.networks.bitcoin, preimages = [], signersPubKeys, taprootSpendPath, tapLeaf }) {
|
|
638
735
|
_Output_instances.add(this);
|
|
639
736
|
_Output_payment.set(this, void 0);
|
|
640
737
|
_Output_preimages.set(this, []);
|
|
@@ -648,6 +745,11 @@ function DescriptorsFactory(ecc) {
|
|
|
648
745
|
_Output_isTaproot.set(this, void 0);
|
|
649
746
|
_Output_expandedExpression.set(this, void 0);
|
|
650
747
|
_Output_expandedMiniscript.set(this, void 0);
|
|
748
|
+
_Output_tapTreeExpression.set(this, void 0);
|
|
749
|
+
_Output_tapTree.set(this, void 0);
|
|
750
|
+
_Output_tapTreeInfo.set(this, void 0);
|
|
751
|
+
_Output_taprootSpendPath.set(this, void 0);
|
|
752
|
+
_Output_tapLeaf.set(this, void 0);
|
|
651
753
|
_Output_expansionMap.set(this, void 0);
|
|
652
754
|
_Output_network.set(this, void 0);
|
|
653
755
|
__classPrivateFieldSet(this, _Output_network, network, "f");
|
|
@@ -657,10 +759,26 @@ function DescriptorsFactory(ecc) {
|
|
|
657
759
|
const expandedResult = expand({
|
|
658
760
|
descriptor,
|
|
659
761
|
...(index !== undefined ? { index } : {}),
|
|
762
|
+
...(change !== undefined ? { change } : {}),
|
|
660
763
|
checksumRequired,
|
|
661
764
|
network,
|
|
662
765
|
allowMiniscriptInP2SH
|
|
663
766
|
});
|
|
767
|
+
const isTaprootDescriptor = expandedResult.isTaproot === true;
|
|
768
|
+
const hasTapTree = expandedResult.expandedExpression?.startsWith('tr(@0,') ?? false;
|
|
769
|
+
const resolvedTaprootSpendPath = taprootSpendPath ?? (hasTapTree ? 'script' : 'key');
|
|
770
|
+
if (!isTaprootDescriptor) {
|
|
771
|
+
if (taprootSpendPath !== undefined || tapLeaf !== undefined)
|
|
772
|
+
throw new Error(`Error: taprootSpendPath/tapLeaf require a taproot descriptor`);
|
|
773
|
+
}
|
|
774
|
+
else {
|
|
775
|
+
if (taprootSpendPath === 'script' && !hasTapTree)
|
|
776
|
+
throw new Error(`Error: taprootSpendPath=script requires a tr(KEY,TREE) descriptor`);
|
|
777
|
+
if (resolvedTaprootSpendPath === 'key' && tapLeaf !== undefined)
|
|
778
|
+
throw new Error(`Error: tapLeaf cannot be used when taprootSpendPath is key`);
|
|
779
|
+
if (tapLeaf !== undefined && !hasTapTree)
|
|
780
|
+
throw new Error(`Error: tapLeaf can only be used with tr(KEY,TREE) descriptors`);
|
|
781
|
+
}
|
|
664
782
|
if (expandedResult.isRanged && index === undefined)
|
|
665
783
|
throw new Error(`Error: index was not provided for ranged descriptor`);
|
|
666
784
|
if (!expandedResult.payment)
|
|
@@ -678,83 +796,55 @@ function DescriptorsFactory(ecc) {
|
|
|
678
796
|
__classPrivateFieldSet(this, _Output_isTaproot, expandedResult.isTaproot, "f");
|
|
679
797
|
if (expandedResult.expandedMiniscript !== undefined)
|
|
680
798
|
__classPrivateFieldSet(this, _Output_expandedMiniscript, expandedResult.expandedMiniscript, "f");
|
|
799
|
+
if (expandedResult.tapTreeExpression !== undefined)
|
|
800
|
+
__classPrivateFieldSet(this, _Output_tapTreeExpression, expandedResult.tapTreeExpression, "f");
|
|
801
|
+
if (expandedResult.tapTree !== undefined)
|
|
802
|
+
__classPrivateFieldSet(this, _Output_tapTree, expandedResult.tapTree, "f");
|
|
803
|
+
if (expandedResult.tapTreeInfo !== undefined)
|
|
804
|
+
__classPrivateFieldSet(this, _Output_tapTreeInfo, expandedResult.tapTreeInfo, "f");
|
|
681
805
|
if (expandedResult.redeemScript !== undefined)
|
|
682
806
|
__classPrivateFieldSet(this, _Output_redeemScript, expandedResult.redeemScript, "f");
|
|
683
807
|
if (expandedResult.witnessScript !== undefined)
|
|
684
808
|
__classPrivateFieldSet(this, _Output_witnessScript, expandedResult.witnessScript, "f");
|
|
685
|
-
if (signersPubKeys)
|
|
809
|
+
if (signersPubKeys)
|
|
686
810
|
__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
|
-
}
|
|
811
|
+
__classPrivateFieldSet(this, _Output_taprootSpendPath, resolvedTaprootSpendPath, "f");
|
|
812
|
+
if (tapLeaf !== undefined)
|
|
813
|
+
__classPrivateFieldSet(this, _Output_tapLeaf, tapLeaf, "f");
|
|
705
814
|
this.getSequence = (0, lodash_memoize_1.default)(this.getSequence);
|
|
706
815
|
this.getLockTime = (0, lodash_memoize_1.default)(this.getLockTime);
|
|
707
816
|
const getSignaturesKey = (signatures) => signatures === 'DANGEROUSLY_USE_FAKE_SIGNATURES'
|
|
708
817
|
? signatures
|
|
709
818
|
: signatures
|
|
710
|
-
.map(s => `${s.pubkey
|
|
819
|
+
.map(s => `${(0, uint8array_tools_1.toHex)(s.pubkey)}-${(0, uint8array_tools_1.toHex)(s.signature)}`)
|
|
711
820
|
.join('|');
|
|
712
|
-
this.getScriptSatisfaction = (0, lodash_memoize_1.default)(this.getScriptSatisfaction,
|
|
713
|
-
// resolver function:
|
|
714
|
-
getSignaturesKey);
|
|
715
821
|
this.guessOutput = (0, lodash_memoize_1.default)(this.guessOutput);
|
|
716
822
|
this.inputWeight = (0, lodash_memoize_1.default)(this.inputWeight,
|
|
717
823
|
// resolver function:
|
|
718
|
-
(isSegwitTx, signatures) => {
|
|
824
|
+
(isSegwitTx, signatures, options) => {
|
|
719
825
|
const segwitKey = isSegwitTx ? 'segwit' : 'non-segwit';
|
|
720
826
|
const signaturesKey = getSignaturesKey(signatures);
|
|
721
|
-
|
|
827
|
+
const taprootSighashKey = options?.taprootSighash ?? 'SIGHASH_DEFAULT';
|
|
828
|
+
return `${segwitKey}-${signaturesKey}-taprootSighash:${taprootSighashKey}`;
|
|
722
829
|
});
|
|
723
830
|
this.outputWeight = (0, lodash_memoize_1.default)(this.outputWeight);
|
|
724
831
|
}
|
|
725
832
|
/**
|
|
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.
|
|
833
|
+
* Returns the compiled Script Satisfaction for a miniscript-based Output.
|
|
834
|
+
* The satisfaction is the unlocking script, derived by the Satisfier
|
|
835
|
+
* algorithm (https://bitcoin.sipa.be/miniscript/).
|
|
751
836
|
*
|
|
752
|
-
*
|
|
753
|
-
*
|
|
754
|
-
*
|
|
837
|
+
* This method uses a two-pass flow:
|
|
838
|
+
* 1) Planning: constraints (nLockTime/nSequence) are computed using fake
|
|
839
|
+
* signatures. This is done since the final solution may not need all the
|
|
840
|
+
* signatures in signersPubKeys. And we may avoid the user do extra
|
|
841
|
+
* signing (tedious op with HWW).
|
|
842
|
+
* 2) Signing: the provided signatures are used to build the final
|
|
843
|
+
* satisfaction, while enforcing the planned constraints so the same
|
|
844
|
+
* solution is selected. Not all the signatures of signersPubKeys may
|
|
845
|
+
* be required.
|
|
755
846
|
*
|
|
756
|
-
*
|
|
757
|
-
* miniscript descriptors.
|
|
847
|
+
* The return value includes the satisfaction script and the constraints.
|
|
758
848
|
*/
|
|
759
849
|
getScriptSatisfaction(
|
|
760
850
|
/**
|
|
@@ -762,29 +852,9 @@ function DescriptorsFactory(ecc) {
|
|
|
762
852
|
* build the Satisfaction of this miniscript-based `Output`.
|
|
763
853
|
*
|
|
764
854
|
* `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
|
-
*
|
|
855
|
+
* `interface PartialSig { pubkey: Uint8Array; signature: Uint8Array; }`
|
|
780
856
|
*/
|
|
781
857
|
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
858
|
const miniscript = __classPrivateFieldGet(this, _Output_miniscript, "f");
|
|
789
859
|
const expandedMiniscript = __classPrivateFieldGet(this, _Output_expandedMiniscript, "f");
|
|
790
860
|
const expansionMap = __classPrivateFieldGet(this, _Output_expansionMap, "f");
|
|
@@ -792,12 +862,9 @@ function DescriptorsFactory(ecc) {
|
|
|
792
862
|
expandedMiniscript === undefined ||
|
|
793
863
|
expansionMap === undefined)
|
|
794
864
|
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)({
|
|
865
|
+
//This crates the plans using fake signatures
|
|
866
|
+
const constraints = __classPrivateFieldGet(this, _Output_instances, "m", _Output_getConstraints).call(this);
|
|
867
|
+
const satisfaction = (0, miniscript_1.satisfyMiniscript)({
|
|
801
868
|
expandedMiniscript,
|
|
802
869
|
expansionMap,
|
|
803
870
|
signatures,
|
|
@@ -806,25 +873,96 @@ function DescriptorsFactory(ecc) {
|
|
|
806
873
|
//verify that the solutions found using the final signatures have not
|
|
807
874
|
//changed
|
|
808
875
|
timeConstraints: {
|
|
809
|
-
nLockTime:
|
|
810
|
-
nSequence:
|
|
876
|
+
nLockTime: constraints?.nLockTime,
|
|
877
|
+
nSequence: constraints?.nSequence
|
|
811
878
|
}
|
|
812
|
-
})
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
879
|
+
});
|
|
880
|
+
__classPrivateFieldGet(this, _Output_instances, "m", _Output_assertMiniscriptSatisfactionResourceLimits).call(this, satisfaction.scriptSatisfaction);
|
|
881
|
+
return satisfaction;
|
|
882
|
+
}
|
|
883
|
+
/**
|
|
884
|
+
* Returns the taproot script‑path satisfaction for a tap miniscript
|
|
885
|
+
* descriptor. This mirrors {@link getScriptSatisfaction} and uses the same
|
|
886
|
+
* two‑pass plan/sign flow.
|
|
887
|
+
*
|
|
888
|
+
* In addition to nLockTime/nSequence, it returns the selected tapLeafHash
|
|
889
|
+
* (the leaf chosen during planning) and the leaf’s tapscript.
|
|
890
|
+
*/
|
|
891
|
+
getTapScriptSatisfaction(
|
|
892
|
+
/**
|
|
893
|
+
* An array with all the signatures needed to
|
|
894
|
+
* build the Satisfaction of this miniscript-based `Output`.
|
|
895
|
+
*
|
|
896
|
+
* `signatures` must be passed using this format (pairs of `pubKey/signature`):
|
|
897
|
+
* `interface PartialSig { pubkey: Uint8Array; signature: Uint8Array; }`
|
|
898
|
+
*/
|
|
899
|
+
signatures) {
|
|
900
|
+
if (__classPrivateFieldGet(this, _Output_taprootSpendPath, "f") !== 'script')
|
|
901
|
+
throw new Error(`Error: taprootSpendPath is key; script-path satisfaction is not allowed`);
|
|
902
|
+
const tapTreeInfo = __classPrivateFieldGet(this, _Output_tapTreeInfo, "f");
|
|
903
|
+
if (!tapTreeInfo)
|
|
904
|
+
throw new Error(`Error: taproot tree info not available`);
|
|
905
|
+
const constraints = __classPrivateFieldGet(this, _Output_instances, "m", _Output_getConstraints).call(this);
|
|
906
|
+
return (0, tapMiniscript_1.satisfyTapTree)({
|
|
907
|
+
tapTreeInfo,
|
|
908
|
+
preimages: __classPrivateFieldGet(this, _Output_preimages, "f"),
|
|
909
|
+
signatures,
|
|
910
|
+
...(constraints?.tapLeafHash
|
|
911
|
+
? { tapLeaf: constraints.tapLeafHash }
|
|
912
|
+
: {}),
|
|
913
|
+
...(constraints
|
|
914
|
+
? {
|
|
915
|
+
timeConstraints: {
|
|
916
|
+
nLockTime: constraints.nLockTime,
|
|
917
|
+
nSequence: constraints.nSequence
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
: {})
|
|
921
|
+
});
|
|
922
|
+
}
|
|
923
|
+
/**
|
|
924
|
+
* Creates and returns an instance of bitcoinjs-lib
|
|
925
|
+
* [`Payment`](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/ts_src/payments/index.ts)'s interface with the `scriptPubKey` of this `Output`.
|
|
926
|
+
*/
|
|
927
|
+
getPayment() {
|
|
928
|
+
return __classPrivateFieldGet(this, _Output_payment, "f");
|
|
929
|
+
}
|
|
930
|
+
/**
|
|
931
|
+
* Returns the Bitcoin Address of this `Output`.
|
|
932
|
+
*/
|
|
933
|
+
getAddress() {
|
|
934
|
+
if (!__classPrivateFieldGet(this, _Output_payment, "f").address)
|
|
935
|
+
throw new Error(`Error: could extract an address from the payment`);
|
|
936
|
+
return __classPrivateFieldGet(this, _Output_payment, "f").address;
|
|
937
|
+
}
|
|
938
|
+
/**
|
|
939
|
+
* Returns this `Output`'s scriptPubKey.
|
|
940
|
+
*/
|
|
941
|
+
getScriptPubKey() {
|
|
942
|
+
if (!__classPrivateFieldGet(this, _Output_payment, "f").output)
|
|
943
|
+
throw new Error(`Error: could extract output.script from the payment`);
|
|
944
|
+
return __classPrivateFieldGet(this, _Output_payment, "f").output;
|
|
816
945
|
}
|
|
817
946
|
/**
|
|
818
947
|
* Gets the nSequence required to fulfill this `Output`.
|
|
819
948
|
*/
|
|
820
949
|
getSequence() {
|
|
821
|
-
return __classPrivateFieldGet(this, _Output_instances, "m",
|
|
950
|
+
return __classPrivateFieldGet(this, _Output_instances, "m", _Output_getConstraints).call(this)?.nSequence;
|
|
822
951
|
}
|
|
823
952
|
/**
|
|
824
953
|
* Gets the nLockTime required to fulfill this `Output`.
|
|
825
954
|
*/
|
|
826
955
|
getLockTime() {
|
|
827
|
-
return __classPrivateFieldGet(this, _Output_instances, "m",
|
|
956
|
+
return __classPrivateFieldGet(this, _Output_instances, "m", _Output_getConstraints).call(this)?.nLockTime;
|
|
957
|
+
}
|
|
958
|
+
/**
|
|
959
|
+
* Returns the tapleaf hash selected during planning for taproot script-path
|
|
960
|
+
* spends. If signersPubKeys are provided, selection is optimized for those
|
|
961
|
+
* pubkeys. If a specific tapLeaf selector is used in spending calls, this
|
|
962
|
+
* reflects that selection.
|
|
963
|
+
*/
|
|
964
|
+
getTapLeafHash() {
|
|
965
|
+
return __classPrivateFieldGet(this, _Output_instances, "m", _Output_getConstraints).call(this)?.tapLeafHash;
|
|
828
966
|
}
|
|
829
967
|
/**
|
|
830
968
|
* Gets the witnessScript required to fulfill this `Output`. Only applies to
|
|
@@ -955,10 +1093,19 @@ function DescriptorsFactory(ecc) {
|
|
|
955
1093
|
*, Also any `addr(SINGLE_KEY_ADDRESS)` * is assumed to be a single key Taproot
|
|
956
1094
|
* address (like those defined in BIP86).
|
|
957
1095
|
* For inputs using arbitrary scripts (not standard addresses),
|
|
958
|
-
* use a descriptor in the format `sh(MINISCRIPT)
|
|
959
|
-
*
|
|
960
|
-
* expressions.
|
|
1096
|
+
* use a descriptor in the format `sh(MINISCRIPT)`, `wsh(MINISCRIPT)` or
|
|
1097
|
+
* `tr(KEY,TREE)` for taproot script-path expressions.
|
|
961
1098
|
*/
|
|
1099
|
+
// NOTE(taproot-weight): Output instances are concrete. If descriptor has
|
|
1100
|
+
// wildcards, constructor requires `index`. No ranged-without-index
|
|
1101
|
+
// estimation is attempted here.
|
|
1102
|
+
// TODO(taproot-weight): Remaining items:
|
|
1103
|
+
// - Annex: not modeled; if annex is used, add witness item sizing.
|
|
1104
|
+
// - Taproot sighash defaults: options.taprootSighash currently drives fake
|
|
1105
|
+
// signature sizing; ensure coinselector passes the intended mode.
|
|
1106
|
+
// - After PSBT taproot script-path fields are fully populated, add regtest
|
|
1107
|
+
// integration fixtures comparing real tx vsize with inputWeight/outputWeight
|
|
1108
|
+
// estimates for taproot key-path and script-path spends.
|
|
962
1109
|
inputWeight(
|
|
963
1110
|
/**
|
|
964
1111
|
* Indicates if the transaction is a Segwit transaction.
|
|
@@ -973,12 +1120,23 @@ function DescriptorsFactory(ecc) {
|
|
|
973
1120
|
* Array of `PartialSig`. Each `PartialSig` includes
|
|
974
1121
|
* a public key and its corresponding signature. This parameter
|
|
975
1122
|
* enables the accurate calculation of signature sizes for ECDSA.
|
|
976
|
-
* Pass 'DANGEROUSLY_USE_FAKE_SIGNATURES' to assume
|
|
977
|
-
* for ECDSA.
|
|
978
|
-
*
|
|
1123
|
+
* Pass 'DANGEROUSLY_USE_FAKE_SIGNATURES' to assume
|
|
1124
|
+
* ECDSA_FAKE_SIGNATURE_SIZE bytes for ECDSA.
|
|
1125
|
+
* For taproot, the fake signature size is controlled by
|
|
1126
|
+
* options.taprootSighash (64 for 'SIGHASH_DEFAULT', 65
|
|
1127
|
+
* for 'non-SIGHASH_DEFAULT'). default value is SIGHASH_DEFAULT
|
|
979
1128
|
* Mainly used for testing.
|
|
980
1129
|
*/
|
|
981
|
-
signatures
|
|
1130
|
+
signatures,
|
|
1131
|
+
/*
|
|
1132
|
+
* Options that affect taproot fake signature sizing.
|
|
1133
|
+
* taprootSighash: 'SIGHASH_DEFAULT' | 'non-SIGHASH_DEFAULT' (default: 'SIGHASH_DEFAULT').
|
|
1134
|
+
* This is only used when signatures === 'DANGEROUSLY_USE_FAKE_SIGNATURES'.
|
|
1135
|
+
*/
|
|
1136
|
+
options = {
|
|
1137
|
+
taprootSighash: 'SIGHASH_DEFAULT'
|
|
1138
|
+
}) {
|
|
1139
|
+
const taprootSighash = options.taprootSighash ?? 'SIGHASH_DEFAULT';
|
|
982
1140
|
if (this.isSegwit() && !isSegwitTx)
|
|
983
1141
|
throw new Error(`a tx is segwit if at least one input is segwit`);
|
|
984
1142
|
//expand any miniscript-based descriptor. If not miniscript-based, then it's
|
|
@@ -991,13 +1149,59 @@ addr(PKH_ADDRESS), addr(WPKH_ADDRESS), addr(SH_WPKH_ADDRESS), addr(SINGLE_KEY_AD
|
|
|
991
1149
|
expansion=${expansion}, isPKH=${isPKH}, isWPKH=${isWPKH}, isSH=${isSH}, isTR=${isTR}.`;
|
|
992
1150
|
if (!expansion && !isPKH && !isWPKH && !isSH && !isTR)
|
|
993
1151
|
throw new Error(errorMsg);
|
|
994
|
-
const
|
|
995
|
-
|
|
996
|
-
|
|
1152
|
+
const resolveEcdsaSignatureSize = () => {
|
|
1153
|
+
if (signatures === 'DANGEROUSLY_USE_FAKE_SIGNATURES')
|
|
1154
|
+
return ((0, varuint_bitcoin_1.encodingLength)(ECDSA_FAKE_SIGNATURE_SIZE) +
|
|
1155
|
+
ECDSA_FAKE_SIGNATURE_SIZE);
|
|
1156
|
+
if (signatures.length !== 1)
|
|
1157
|
+
throw new Error('More than one signture was not expected');
|
|
1158
|
+
const singleSignature = signatures[0];
|
|
1159
|
+
if (!singleSignature)
|
|
1160
|
+
throw new Error('Signatures not present');
|
|
1161
|
+
const length = singleSignature.signature.length;
|
|
1162
|
+
return (0, varuint_bitcoin_1.encodingLength)(length) + length;
|
|
1163
|
+
};
|
|
1164
|
+
const resolveMiniscriptSignatures = () => {
|
|
1165
|
+
if (signatures !== 'DANGEROUSLY_USE_FAKE_SIGNATURES')
|
|
1166
|
+
return signatures;
|
|
1167
|
+
return __classPrivateFieldGet(this, _Output_instances, "m", _Output_resolveMiniscriptSignersPubKeys).call(this).map(pubkey => ({
|
|
1168
|
+
pubkey,
|
|
1169
|
+
// https://transactionfee.info/charts/bitcoin-script-ecdsa-length/
|
|
1170
|
+
signature: new Uint8Array(ECDSA_FAKE_SIGNATURE_SIZE)
|
|
1171
|
+
}));
|
|
1172
|
+
};
|
|
1173
|
+
const taprootFakeSignatureSize = taprootSighash === 'SIGHASH_DEFAULT'
|
|
1174
|
+
? TAPROOT_FAKE_SIGNATURE_SIZE
|
|
1175
|
+
: TAPROOT_FAKE_SIGNATURE_SIZE + 1;
|
|
1176
|
+
const resolveTaprootSignatures = () => {
|
|
1177
|
+
if (signatures !== 'DANGEROUSLY_USE_FAKE_SIGNATURES')
|
|
1178
|
+
return signatures;
|
|
1179
|
+
return __classPrivateFieldGet(this, _Output_instances, "m", _Output_resolveTapTreeSignersPubKeys).call(this).map(pubkey => ({
|
|
1180
|
+
pubkey,
|
|
1181
|
+
signature: new Uint8Array(taprootFakeSignatureSize)
|
|
1182
|
+
}));
|
|
1183
|
+
};
|
|
1184
|
+
const resolveTaprootSignatureSize = () => {
|
|
1185
|
+
let length;
|
|
1186
|
+
if (signatures === 'DANGEROUSLY_USE_FAKE_SIGNATURES') {
|
|
1187
|
+
length = taprootFakeSignatureSize;
|
|
1188
|
+
}
|
|
1189
|
+
else {
|
|
1190
|
+
if (signatures.length !== 1)
|
|
1191
|
+
throw new Error('More than one signture was not expected');
|
|
1192
|
+
const singleSignature = signatures[0];
|
|
1193
|
+
if (!singleSignature)
|
|
1194
|
+
throw new Error('Signatures not present');
|
|
1195
|
+
length = singleSignature.signature.length;
|
|
1196
|
+
}
|
|
1197
|
+
return (0, varuint_bitcoin_1.encodingLength)(length) + length;
|
|
1198
|
+
};
|
|
1199
|
+
const taprootSpendPath = __classPrivateFieldGet(this, _Output_taprootSpendPath, "f");
|
|
1200
|
+
const tapLeaf = __classPrivateFieldGet(this, _Output_tapLeaf, "f");
|
|
997
1201
|
if (expansion ? expansion.startsWith('pkh(') : isPKH) {
|
|
998
1202
|
return (
|
|
999
1203
|
// Non-segwit: (txid:32) + (vout:4) + (sequence:4) + (script_len:1) + (sig:73) + (pubkey:34)
|
|
1000
|
-
(32 + 4 + 4 + 1 +
|
|
1204
|
+
(32 + 4 + 4 + 1 + resolveEcdsaSignatureSize() + 34) * 4 +
|
|
1001
1205
|
//Segwit:
|
|
1002
1206
|
(isSegwitTx ? 1 : 0));
|
|
1003
1207
|
}
|
|
@@ -1008,7 +1212,7 @@ expansion=${expansion}, isPKH=${isPKH}, isWPKH=${isWPKH}, isSH=${isSH}, isTR=${i
|
|
|
1008
1212
|
// Non-segwit: (txid:32) + (vout:4) + (sequence:4) + (script_len:1)
|
|
1009
1213
|
41 * 4 +
|
|
1010
1214
|
// Segwit: (push_count:1) + (sig:73) + (pubkey:34)
|
|
1011
|
-
(1 +
|
|
1215
|
+
(1 + resolveEcdsaSignatureSize() + 34));
|
|
1012
1216
|
}
|
|
1013
1217
|
else if (expansion ? expansion.startsWith('sh(wpkh(') : isSH) {
|
|
1014
1218
|
if (!isSegwitTx)
|
|
@@ -1019,7 +1223,7 @@ expansion=${expansion}, isPKH=${isPKH}, isWPKH=${isWPKH}, isSH=${isSH}, isTR=${i
|
|
|
1019
1223
|
// -> p2wpkh: (script_len:1) + (script:22)
|
|
1020
1224
|
64 * 4 +
|
|
1021
1225
|
// Segwit: (push_count:1) + (sig:73) + (pubkey:34)
|
|
1022
|
-
(1 +
|
|
1226
|
+
(1 + resolveEcdsaSignatureSize() + 34));
|
|
1023
1227
|
}
|
|
1024
1228
|
else if (expansion?.startsWith('sh(wsh(')) {
|
|
1025
1229
|
if (!isSegwitTx)
|
|
@@ -1030,7 +1234,8 @@ expansion=${expansion}, isPKH=${isPKH}, isWPKH=${isWPKH}, isSH=${isSH}, isTR=${i
|
|
|
1030
1234
|
const payment = bitcoinjs_lib_1.payments.p2sh({
|
|
1031
1235
|
redeem: bitcoinjs_lib_1.payments.p2wsh({
|
|
1032
1236
|
redeem: {
|
|
1033
|
-
input: this.getScriptSatisfaction(
|
|
1237
|
+
input: this.getScriptSatisfaction(resolveMiniscriptSignatures())
|
|
1238
|
+
.scriptSatisfaction,
|
|
1034
1239
|
output: witnessScript
|
|
1035
1240
|
}
|
|
1036
1241
|
})
|
|
@@ -1049,7 +1254,8 @@ expansion=${expansion}, isPKH=${isPKH}, isWPKH=${isWPKH}, isSH=${isSH}, isTR=${i
|
|
|
1049
1254
|
throw new Error('sh() must provide redeemScript');
|
|
1050
1255
|
const payment = bitcoinjs_lib_1.payments.p2sh({
|
|
1051
1256
|
redeem: {
|
|
1052
|
-
input: this.getScriptSatisfaction(
|
|
1257
|
+
input: this.getScriptSatisfaction(resolveMiniscriptSignatures())
|
|
1258
|
+
.scriptSatisfaction,
|
|
1053
1259
|
output: redeemScript
|
|
1054
1260
|
}
|
|
1055
1261
|
});
|
|
@@ -1069,7 +1275,8 @@ expansion=${expansion}, isPKH=${isPKH}, isWPKH=${isWPKH}, isSH=${isSH}, isTR=${i
|
|
|
1069
1275
|
throw new Error('wsh must provide witnessScript');
|
|
1070
1276
|
const payment = bitcoinjs_lib_1.payments.p2wsh({
|
|
1071
1277
|
redeem: {
|
|
1072
|
-
input: this.getScriptSatisfaction(
|
|
1278
|
+
input: this.getScriptSatisfaction(resolveMiniscriptSignatures())
|
|
1279
|
+
.scriptSatisfaction,
|
|
1073
1280
|
output: witnessScript
|
|
1074
1281
|
}
|
|
1075
1282
|
});
|
|
@@ -1080,8 +1287,24 @@ expansion=${expansion}, isPKH=${isPKH}, isWPKH=${isWPKH}, isSH=${isSH}, isTR=${i
|
|
|
1080
1287
|
4 * (40 + varSliceSize(payment.input)) +
|
|
1081
1288
|
//Segwit
|
|
1082
1289
|
vectorSize(payment.witness));
|
|
1083
|
-
//when
|
|
1084
|
-
//
|
|
1290
|
+
// when tr(KEY,TREE): choose key-path or script-path based on
|
|
1291
|
+
// constructor taprootSpendPath policy.
|
|
1292
|
+
}
|
|
1293
|
+
else if (expansion?.startsWith('tr(@0,')) {
|
|
1294
|
+
if (!isSegwitTx)
|
|
1295
|
+
throw new Error('Should be SegwitTx');
|
|
1296
|
+
if (taprootSpendPath === 'key')
|
|
1297
|
+
return 41 * 4 + (0, varuint_bitcoin_1.encodingLength)(1) + resolveTaprootSignatureSize();
|
|
1298
|
+
const resolvedTapTreeInfo = __classPrivateFieldGet(this, _Output_tapTreeInfo, "f");
|
|
1299
|
+
if (!resolvedTapTreeInfo)
|
|
1300
|
+
throw new Error(`Error: taproot tree info not available`);
|
|
1301
|
+
const taprootSatisfaction = (0, tapMiniscript_1.satisfyTapTree)({
|
|
1302
|
+
tapTreeInfo: resolvedTapTreeInfo,
|
|
1303
|
+
preimages: __classPrivateFieldGet(this, _Output_preimages, "f"),
|
|
1304
|
+
signatures: resolveTaprootSignatures(),
|
|
1305
|
+
...(tapLeaf !== undefined ? { tapLeaf } : {})
|
|
1306
|
+
});
|
|
1307
|
+
return 41 * 4 + taprootSatisfaction.totalWitnessSize;
|
|
1085
1308
|
}
|
|
1086
1309
|
else if (isTR && (!expansion || expansion === 'tr(@0)')) {
|
|
1087
1310
|
if (!isSegwitTx)
|
|
@@ -1089,8 +1312,8 @@ expansion=${expansion}, isPKH=${isPKH}, isWPKH=${isWPKH}, isSH=${isSH}, isTR=${i
|
|
|
1089
1312
|
return (
|
|
1090
1313
|
// Non-segwit: (txid:32) + (vout:4) + (sequence:4) + (script_len:1)
|
|
1091
1314
|
41 * 4 +
|
|
1092
|
-
// Segwit: (push_count:1) + (sig_length(1) + schnorr_sig(64
|
|
1093
|
-
(1 +
|
|
1315
|
+
// Segwit: (push_count:1) + (sig_length(1) + schnorr_sig(64/65))
|
|
1316
|
+
((0, varuint_bitcoin_1.encodingLength)(1) + resolveTaprootSignatureSize()));
|
|
1094
1317
|
}
|
|
1095
1318
|
else {
|
|
1096
1319
|
throw new Error(errorMsg);
|
|
@@ -1129,13 +1352,6 @@ expansion=${expansion}, isPKH=${isPKH}, isWPKH=${isWPKH}, isSH=${isSH}, isTR=${i
|
|
|
1129
1352
|
throw new Error(errorMsg);
|
|
1130
1353
|
}
|
|
1131
1354
|
}
|
|
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
1355
|
/**
|
|
1140
1356
|
* Sets this output as an input of the provided `psbt` and updates the
|
|
1141
1357
|
* `psbt` locktime if required by the descriptor.
|
|
@@ -1163,6 +1379,10 @@ expansion=${expansion}, isPKH=${isPKH}, isWPKH=${isWPKH}, isSH=${isSH}, isTR=${i
|
|
|
1163
1379
|
*
|
|
1164
1380
|
* @returns A finalizer function to be used after signing the `psbt`.
|
|
1165
1381
|
* This function ensures that this input is properly finalized.
|
|
1382
|
+
* The finalizer completes the PSBT input by adding the unlocking script
|
|
1383
|
+
* (`scriptWitness` or `scriptSig`) that satisfies this `Output`'s spending
|
|
1384
|
+
* conditions. Because these scripts include signatures, you should finish
|
|
1385
|
+
* all signing operations before calling the finalizer.
|
|
1166
1386
|
* The finalizer has this signature:
|
|
1167
1387
|
*
|
|
1168
1388
|
* `( { psbt, validate = true } : { psbt: Psbt; validate: boolean | undefined } ) => void`
|
|
@@ -1170,6 +1390,10 @@ expansion=${expansion}, isPKH=${isPKH}, isWPKH=${isWPKH}, isSH=${isSH}, isTR=${i
|
|
|
1170
1390
|
*/
|
|
1171
1391
|
updatePsbtAsInput({ psbt, txHex, txId, value, vout, //vector output index
|
|
1172
1392
|
rbf = true }) {
|
|
1393
|
+
if (value !== undefined && typeof value !== 'bigint')
|
|
1394
|
+
throw new Error(`Error: value must be a bigint`);
|
|
1395
|
+
if (value !== undefined && value < 0n)
|
|
1396
|
+
throw new Error(`Error: value must be >= 0n`);
|
|
1173
1397
|
if (txHex === undefined) {
|
|
1174
1398
|
console.warn(`Warning: missing txHex may allow fee attacks`);
|
|
1175
1399
|
}
|
|
@@ -1183,15 +1407,46 @@ expansion=${expansion}, isPKH=${isPKH}, isWPKH=${isWPKH}, isSH=${isSH}, isTR=${i
|
|
|
1183
1407
|
//This should only happen when using addr() expressions
|
|
1184
1408
|
throw new Error(`Error: could not determine whether this is a taproot descriptor`);
|
|
1185
1409
|
}
|
|
1186
|
-
const
|
|
1410
|
+
const paymentInternalPubkey = this.getPayment().internalPubkey;
|
|
1411
|
+
const tapInternalKey = isTaproot
|
|
1412
|
+
? paymentInternalPubkey
|
|
1413
|
+
? paymentInternalPubkey
|
|
1414
|
+
: undefined
|
|
1415
|
+
: undefined;
|
|
1416
|
+
let tapLeafScript;
|
|
1417
|
+
let tapBip32Derivation;
|
|
1418
|
+
if (isTaproot && __classPrivateFieldGet(this, _Output_taprootSpendPath, "f") === 'script') {
|
|
1419
|
+
const tapTreeInfo = __classPrivateFieldGet(this, _Output_tapTreeInfo, "f");
|
|
1420
|
+
if (!tapTreeInfo)
|
|
1421
|
+
throw new Error(`Error: taprootSpendPath=script requires taproot tree info`);
|
|
1422
|
+
if (!tapInternalKey)
|
|
1423
|
+
throw new Error(`Error: taprootSpendPath=script requires taproot internal key`);
|
|
1424
|
+
const taprootLeafMetadata = (0, tapMiniscript_1.buildTaprootLeafPsbtMetadata)({
|
|
1425
|
+
tapTreeInfo,
|
|
1426
|
+
internalPubkey: tapInternalKey
|
|
1427
|
+
});
|
|
1428
|
+
tapLeafScript = taprootLeafMetadata.map(({ leaf, controlBlock }) => ({
|
|
1429
|
+
script: leaf.tapScript,
|
|
1430
|
+
leafVersion: leaf.version,
|
|
1431
|
+
controlBlock
|
|
1432
|
+
}));
|
|
1433
|
+
const internalKeyInfo = __classPrivateFieldGet(this, _Output_expansionMap, "f")?.['@0'];
|
|
1434
|
+
if (!internalKeyInfo)
|
|
1435
|
+
throw new Error(`Error: taproot internal key info not available in expansionMap`);
|
|
1436
|
+
tapBip32Derivation = (0, tapMiniscript_1.buildTaprootBip32Derivations)({
|
|
1437
|
+
tapTreeInfo,
|
|
1438
|
+
internalKeyInfo
|
|
1439
|
+
});
|
|
1440
|
+
}
|
|
1441
|
+
const index = (0, psbt_1.addPsbtInput)({
|
|
1187
1442
|
psbt,
|
|
1188
1443
|
vout,
|
|
1189
1444
|
...(txHex !== undefined ? { txHex } : {}),
|
|
1190
1445
|
...(txId !== undefined ? { txId } : {}),
|
|
1191
1446
|
...(value !== undefined ? { value } : {}),
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1447
|
+
tapInternalKey,
|
|
1448
|
+
tapLeafScript,
|
|
1449
|
+
tapBip32Derivation,
|
|
1195
1450
|
sequence: this.getSequence(),
|
|
1196
1451
|
locktime: this.getLockTime(),
|
|
1197
1452
|
keysInfo: __classPrivateFieldGet(this, _Output_expansionMap, "f") ? Object.values(__classPrivateFieldGet(this, _Output_expansionMap, "f")) : [],
|
|
@@ -1201,7 +1456,64 @@ expansion=${expansion}, isPKH=${isPKH}, isWPKH=${isWPKH}, isSH=${isSH}, isTR=${i
|
|
|
1201
1456
|
redeemScript: this.getRedeemScript(),
|
|
1202
1457
|
rbf
|
|
1203
1458
|
});
|
|
1204
|
-
|
|
1459
|
+
//The finalizer adds the unlocking script (scriptSig/scriptWitness) once
|
|
1460
|
+
//signatures are ready.
|
|
1461
|
+
const finalizer = ({ psbt, validate = true }) => {
|
|
1462
|
+
if (validate &&
|
|
1463
|
+
!psbt.validateSignaturesOfInput(index, signatureValidator)) {
|
|
1464
|
+
throw new Error(`Error: invalid signatures on input ${index}`);
|
|
1465
|
+
}
|
|
1466
|
+
//An index must be passed since finding the index in the psbt cannot be
|
|
1467
|
+
//done:
|
|
1468
|
+
//Imagine the case where you received money twice to
|
|
1469
|
+
//the same miniscript-based address. You would have the same scriptPubKey,
|
|
1470
|
+
//same sequences, ... The descriptor does not store the hash of the previous
|
|
1471
|
+
//transaction since it is a general Output instance. Indices must be kept
|
|
1472
|
+
//out of the scope of this class and then passed.
|
|
1473
|
+
__classPrivateFieldGet(this, _Output_instances, "m", _Output_assertPsbtInput).call(this, { index, psbt });
|
|
1474
|
+
if (__classPrivateFieldGet(this, _Output_isTaproot, "f") &&
|
|
1475
|
+
__classPrivateFieldGet(this, _Output_taprootSpendPath, "f") === 'script' &&
|
|
1476
|
+
!__classPrivateFieldGet(this, _Output_tapTreeInfo, "f"))
|
|
1477
|
+
throw new Error(`Error: taprootSpendPath=script requires taproot tree info`);
|
|
1478
|
+
if (__classPrivateFieldGet(this, _Output_tapTreeInfo, "f") && __classPrivateFieldGet(this, _Output_taprootSpendPath, "f") === 'script') {
|
|
1479
|
+
const input = psbt.data.inputs[index];
|
|
1480
|
+
const tapLeafScript = input?.tapLeafScript;
|
|
1481
|
+
if (!tapLeafScript || tapLeafScript.length === 0)
|
|
1482
|
+
throw new Error(`Error: cannot finalize taproot script-path without tapLeafScript`);
|
|
1483
|
+
const tapScriptSig = input?.tapScriptSig;
|
|
1484
|
+
if (!tapScriptSig || tapScriptSig.length === 0)
|
|
1485
|
+
throw new Error(`Error: cannot finalize taproot script-path without tapScriptSig`);
|
|
1486
|
+
const taprootSatisfaction = this.getTapScriptSatisfaction(tapScriptSig);
|
|
1487
|
+
const matchingLeaf = tapLeafScript.find(leafScript => (0, uint8array_tools_1.compare)((0, bitcoinjs_lib_internals_1.tapleafHash)({
|
|
1488
|
+
output: leafScript.script,
|
|
1489
|
+
version: leafScript.leafVersion
|
|
1490
|
+
}), taprootSatisfaction.tapLeafHash) === 0);
|
|
1491
|
+
if (!matchingLeaf)
|
|
1492
|
+
throw new Error(`Error: tapLeafScript does not match planned tapLeafHash`);
|
|
1493
|
+
if ((0, uint8array_tools_1.compare)(matchingLeaf.script, taprootSatisfaction.leaf.tapScript) !==
|
|
1494
|
+
0 ||
|
|
1495
|
+
matchingLeaf.leafVersion !== taprootSatisfaction.leaf.version)
|
|
1496
|
+
throw new Error(`Error: tapLeafScript does not match planned leaf script`);
|
|
1497
|
+
const witness = [
|
|
1498
|
+
...taprootSatisfaction.stackItems,
|
|
1499
|
+
matchingLeaf.script,
|
|
1500
|
+
matchingLeaf.controlBlock
|
|
1501
|
+
];
|
|
1502
|
+
const finalScriptWitness = (0, bitcoinjs_lib_internals_1.witnessStackToScriptWitness)(witness);
|
|
1503
|
+
psbt.finalizeTaprootInput(index, taprootSatisfaction.tapLeafHash, () => ({ finalScriptWitness }));
|
|
1504
|
+
}
|
|
1505
|
+
else if (!__classPrivateFieldGet(this, _Output_miniscript, "f")) {
|
|
1506
|
+
//Use standard finalizers
|
|
1507
|
+
psbt.finalizeInput(index);
|
|
1508
|
+
}
|
|
1509
|
+
else {
|
|
1510
|
+
const signatures = psbt.data.inputs[index]?.partialSig;
|
|
1511
|
+
if (!signatures)
|
|
1512
|
+
throw new Error(`Error: cannot finalize without signatures`);
|
|
1513
|
+
const { scriptSatisfaction } = this.getScriptSatisfaction(signatures);
|
|
1514
|
+
psbt.finalizeInput(index, (0, psbt_1.finalScriptsFuncFactory)(scriptSatisfaction, __classPrivateFieldGet(this, _Output_network, "f")));
|
|
1515
|
+
}
|
|
1516
|
+
};
|
|
1205
1517
|
return finalizer;
|
|
1206
1518
|
}
|
|
1207
1519
|
/**
|
|
@@ -1212,63 +1524,12 @@ expansion=${expansion}, isPKH=${isPKH}, isWPKH=${isWPKH}, isSH=${isSH}, isTR=${i
|
|
|
1212
1524
|
* @param params.value - The value for the output in satoshis.
|
|
1213
1525
|
*/
|
|
1214
1526
|
updatePsbtAsOutput({ psbt, value }) {
|
|
1527
|
+
if (typeof value !== 'bigint')
|
|
1528
|
+
throw new Error(`Error: value must be a bigint`);
|
|
1529
|
+
if (value < 0n)
|
|
1530
|
+
throw new Error(`Error: value must be >= 0n`);
|
|
1215
1531
|
psbt.addOutput({ script: this.getScriptPubKey(), value });
|
|
1216
1532
|
}
|
|
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
1533
|
/**
|
|
1273
1534
|
* Decomposes the descriptor used to form this `Output` into its elemental
|
|
1274
1535
|
* parts. See {@link ExpansionMap ExpansionMap} for a detailed explanation.
|
|
@@ -1284,18 +1545,80 @@ expansion=${expansion}, isPKH=${isPKH}, isWPKH=${isWPKH}, isSH=${isSH}, isTR=${i
|
|
|
1284
1545
|
...(__classPrivateFieldGet(this, _Output_expandedMiniscript, "f") !== undefined
|
|
1285
1546
|
? { expandedMiniscript: __classPrivateFieldGet(this, _Output_expandedMiniscript, "f") }
|
|
1286
1547
|
: {}),
|
|
1548
|
+
...(__classPrivateFieldGet(this, _Output_tapTreeExpression, "f") !== undefined
|
|
1549
|
+
? { tapTreeExpression: __classPrivateFieldGet(this, _Output_tapTreeExpression, "f") }
|
|
1550
|
+
: {}),
|
|
1551
|
+
...(__classPrivateFieldGet(this, _Output_tapTree, "f") !== undefined ? { tapTree: __classPrivateFieldGet(this, _Output_tapTree, "f") } : {}),
|
|
1552
|
+
...(__classPrivateFieldGet(this, _Output_tapTreeInfo, "f") !== undefined
|
|
1553
|
+
? { tapTreeInfo: __classPrivateFieldGet(this, _Output_tapTreeInfo, "f") }
|
|
1554
|
+
: {}),
|
|
1287
1555
|
...(__classPrivateFieldGet(this, _Output_expansionMap, "f") !== undefined
|
|
1288
1556
|
? { expansionMap: __classPrivateFieldGet(this, _Output_expansionMap, "f") }
|
|
1289
1557
|
: {})
|
|
1290
1558
|
};
|
|
1291
1559
|
}
|
|
1292
1560
|
}
|
|
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(),
|
|
1561
|
+
_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() {
|
|
1562
|
+
//If the user did not provide a pubkey subset (signersPubKeys), assume all
|
|
1563
|
+
//miniscript pubkeys can sign.
|
|
1564
|
+
if (__classPrivateFieldGet(this, _Output_signersPubKeys, "f"))
|
|
1565
|
+
return __classPrivateFieldGet(this, _Output_signersPubKeys, "f");
|
|
1566
|
+
const expansionMap = __classPrivateFieldGet(this, _Output_expansionMap, "f");
|
|
1567
|
+
if (!expansionMap)
|
|
1568
|
+
throw new Error(`Error: expansionMap not available for miniscript`);
|
|
1569
|
+
return Object.values(expansionMap).map(keyInfo => {
|
|
1570
|
+
const pubkey = keyInfo.pubkey;
|
|
1571
|
+
if (!pubkey)
|
|
1572
|
+
throw new Error(`Error: miniscript key missing pubkey`);
|
|
1573
|
+
return pubkey;
|
|
1574
|
+
});
|
|
1575
|
+
}, _Output_assertMiniscriptSatisfactionResourceLimits = function _Output_assertMiniscriptSatisfactionResourceLimits(scriptSatisfaction) {
|
|
1576
|
+
if (!__classPrivateFieldGet(this, _Output_miniscript, "f"))
|
|
1577
|
+
return;
|
|
1578
|
+
const satisfactionStackItems = bitcoinjs_lib_1.script.toStack(scriptSatisfaction);
|
|
1579
|
+
// For wsh(...) and sh(wsh(...)), enforce witness stack limits.
|
|
1580
|
+
if (__classPrivateFieldGet(this, _Output_isSegwit, "f") && !__classPrivateFieldGet(this, _Output_isTaproot, "f")) {
|
|
1581
|
+
(0, resourceLimits_1.assertWitnessV0SatisfactionResourceLimits)({
|
|
1582
|
+
stackItems: satisfactionStackItems
|
|
1583
|
+
});
|
|
1584
|
+
return;
|
|
1585
|
+
}
|
|
1586
|
+
if (!__classPrivateFieldGet(this, _Output_isSegwit, "f")) {
|
|
1587
|
+
// In legacy P2SH, after scriptSig execution, the stack is:
|
|
1588
|
+
// [ ...satisfactionStackItems, redeemScript ]
|
|
1589
|
+
// Consensus limits apply here: each element must be <= 520 bytes and total
|
|
1590
|
+
// stack items must be <= 1000.
|
|
1591
|
+
// redeemScript size is already validated during script construction
|
|
1592
|
+
// to fail early (look for MAX_SCRIPT_ELEMENT_SIZE checks in this file).
|
|
1593
|
+
// Below we re-validate again the redeemScript + the rest of stack items.
|
|
1594
|
+
const redeemScript = __classPrivateFieldGet(this, _Output_redeemScript, "f");
|
|
1595
|
+
if (!redeemScript)
|
|
1596
|
+
throw new Error(`Error: redeemScript not available for P2SH spend`);
|
|
1597
|
+
(0, resourceLimits_1.assertConsensusStackResourceLimits)({
|
|
1598
|
+
stackItems: [...satisfactionStackItems, redeemScript]
|
|
1599
|
+
});
|
|
1600
|
+
(0, resourceLimits_1.assertP2shScriptSigStandardSize)({
|
|
1601
|
+
scriptSatisfaction,
|
|
1602
|
+
redeemScript,
|
|
1603
|
+
network: __classPrivateFieldGet(this, _Output_network, "f")
|
|
1604
|
+
});
|
|
1605
|
+
}
|
|
1606
|
+
}, _Output_resolveTapTreeSignersPubKeys = function _Output_resolveTapTreeSignersPubKeys() {
|
|
1607
|
+
//If the user did not provide a pubkey subset (signersPubKeys), assume all
|
|
1608
|
+
//taproot leaf pubkeys can sign.
|
|
1609
|
+
const tapTreeInfo = __classPrivateFieldGet(this, _Output_tapTreeInfo, "f");
|
|
1610
|
+
if (!tapTreeInfo)
|
|
1611
|
+
throw new Error(`Error: taproot tree info not available`);
|
|
1612
|
+
const candidatePubkeys = __classPrivateFieldGet(this, _Output_signersPubKeys, "f")
|
|
1613
|
+
? __classPrivateFieldGet(this, _Output_signersPubKeys, "f").map(tapMiniscript_1.normalizeTaprootPubkey)
|
|
1614
|
+
: (0, tapMiniscript_1.collectTapTreePubkeys)(tapTreeInfo);
|
|
1615
|
+
return Array.from(new Set(candidatePubkeys.map(pubkey => (0, uint8array_tools_1.toHex)(pubkey)))).map(hex => (0, uint8array_tools_1.fromHex)(hex));
|
|
1616
|
+
}, _Output_getConstraints = function _Output_getConstraints() {
|
|
1294
1617
|
const miniscript = __classPrivateFieldGet(this, _Output_miniscript, "f");
|
|
1295
1618
|
const preimages = __classPrivateFieldGet(this, _Output_preimages, "f");
|
|
1296
1619
|
const expandedMiniscript = __classPrivateFieldGet(this, _Output_expandedMiniscript, "f");
|
|
1297
1620
|
const expansionMap = __classPrivateFieldGet(this, _Output_expansionMap, "f");
|
|
1298
|
-
const
|
|
1621
|
+
const tapTreeInfo = __classPrivateFieldGet(this, _Output_tapTreeInfo, "f");
|
|
1299
1622
|
//Create a method. solvePreimages to solve them.
|
|
1300
1623
|
if (miniscript) {
|
|
1301
1624
|
if (expandedMiniscript === undefined || expansionMap === undefined)
|
|
@@ -1303,21 +1626,34 @@ expansion=${expansion}, isPKH=${isPKH}, isWPKH=${isWPKH}, isSH=${isSH}, isTR=${i
|
|
|
1303
1626
|
//We create some fakeSignatures since we may not have them yet.
|
|
1304
1627
|
//We only want to retrieve the nLockTime and nSequence of the satisfaction and
|
|
1305
1628
|
//signatures don't matter
|
|
1306
|
-
const fakeSignatures =
|
|
1629
|
+
const fakeSignatures = __classPrivateFieldGet(this, _Output_instances, "m", _Output_resolveMiniscriptSignersPubKeys).call(this).map(pubkey => ({
|
|
1307
1630
|
pubkey,
|
|
1308
|
-
|
|
1309
|
-
signature: Buffer.alloc(72, 0)
|
|
1631
|
+
signature: new Uint8Array(ECDSA_FAKE_SIGNATURE_SIZE)
|
|
1310
1632
|
}));
|
|
1311
|
-
const
|
|
1633
|
+
const satisfaction = (0, miniscript_1.satisfyMiniscript)({
|
|
1312
1634
|
expandedMiniscript,
|
|
1313
1635
|
expansionMap,
|
|
1314
1636
|
signatures: fakeSignatures,
|
|
1315
1637
|
preimages
|
|
1316
1638
|
});
|
|
1317
|
-
|
|
1639
|
+
__classPrivateFieldGet(this, _Output_instances, "m", _Output_assertMiniscriptSatisfactionResourceLimits).call(this, satisfaction.scriptSatisfaction);
|
|
1640
|
+
const { nLockTime, nSequence } = satisfaction;
|
|
1641
|
+
return { nLockTime, nSequence, tapLeafHash: undefined };
|
|
1318
1642
|
}
|
|
1319
|
-
else
|
|
1320
|
-
|
|
1643
|
+
else if (tapTreeInfo && __classPrivateFieldGet(this, _Output_taprootSpendPath, "f") === 'script') {
|
|
1644
|
+
const fakeSignatures = __classPrivateFieldGet(this, _Output_instances, "m", _Output_resolveTapTreeSignersPubKeys).call(this).map(pubkey => ({
|
|
1645
|
+
pubkey,
|
|
1646
|
+
signature: new Uint8Array(TAPROOT_FAKE_SIGNATURE_SIZE)
|
|
1647
|
+
}));
|
|
1648
|
+
const { nLockTime, nSequence, tapLeafHash } = (0, tapMiniscript_1.satisfyTapTree)({
|
|
1649
|
+
tapTreeInfo,
|
|
1650
|
+
preimages: __classPrivateFieldGet(this, _Output_preimages, "f"),
|
|
1651
|
+
signatures: fakeSignatures,
|
|
1652
|
+
...(__classPrivateFieldGet(this, _Output_tapLeaf, "f") !== undefined ? { tapLeaf: __classPrivateFieldGet(this, _Output_tapLeaf, "f") } : {})
|
|
1653
|
+
});
|
|
1654
|
+
return { nLockTime, nSequence, tapLeafHash };
|
|
1655
|
+
}
|
|
1656
|
+
return undefined;
|
|
1321
1657
|
}, _Output_assertPsbtInput = function _Output_assertPsbtInput({ psbt, index }) {
|
|
1322
1658
|
const input = psbt.data.inputs[index];
|
|
1323
1659
|
const txInput = psbt.txInputs[index];
|
|
@@ -1346,29 +1682,18 @@ expansion=${expansion}, isPKH=${isPKH}, isWPKH=${isWPKH}, isSH=${isSH}, isTR=${i
|
|
|
1346
1682
|
? 0xffffffff
|
|
1347
1683
|
: 0xfffffffe;
|
|
1348
1684
|
const sequenceRBF = sequence !== undefined ? sequence : 0xfffffffd;
|
|
1349
|
-
const
|
|
1350
|
-
?
|
|
1351
|
-
:
|
|
1352
|
-
if (
|
|
1685
|
+
const eqBytes = (bytes1, bytes2) => bytes1 === undefined || bytes2 === undefined
|
|
1686
|
+
? bytes1 === bytes2
|
|
1687
|
+
: (0, uint8array_tools_1.compare)(bytes1, bytes2) === 0;
|
|
1688
|
+
if ((0, uint8array_tools_1.compare)(scriptPubKey, this.getScriptPubKey()) !== 0 ||
|
|
1353
1689
|
(sequenceRBF !== inputSequence && sequenceNoRBF !== inputSequence) ||
|
|
1354
1690
|
locktime !== psbt.locktime ||
|
|
1355
|
-
!
|
|
1356
|
-
!
|
|
1691
|
+
!eqBytes(this.getWitnessScript(), input.witnessScript) ||
|
|
1692
|
+
!eqBytes(this.getRedeemScript(), input.redeemScript)) {
|
|
1357
1693
|
throw new Error(`Error: cannot finalize psbt index ${index} since it does not correspond to this descriptor`);
|
|
1358
1694
|
}
|
|
1359
1695
|
};
|
|
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
1696
|
return {
|
|
1370
|
-
// deprecated TAG must also be below so it is exported to descriptors.d.ts
|
|
1371
|
-
/** @deprecated @hidden */ Descriptor,
|
|
1372
1697
|
Output,
|
|
1373
1698
|
parseKeyExpression,
|
|
1374
1699
|
expand,
|