@bitcoinerlab/descriptors-core 3.1.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.
Files changed (76) hide show
  1. package/README.md +710 -0
  2. package/dist/adapters/applyPR2137.d.ts +2 -0
  3. package/dist/adapters/applyPR2137.js +150 -0
  4. package/dist/adapters/bitcoinjs.d.ts +8 -0
  5. package/dist/adapters/bitcoinjs.js +36 -0
  6. package/dist/adapters/scure/address.d.ts +2 -0
  7. package/dist/adapters/scure/address.js +50 -0
  8. package/dist/adapters/scure/bip32.d.ts +2 -0
  9. package/dist/adapters/scure/bip32.js +16 -0
  10. package/dist/adapters/scure/common.d.ts +14 -0
  11. package/dist/adapters/scure/common.js +36 -0
  12. package/dist/adapters/scure/ecpair.d.ts +2 -0
  13. package/dist/adapters/scure/ecpair.js +58 -0
  14. package/dist/adapters/scure/payments.d.ts +2 -0
  15. package/dist/adapters/scure/payments.js +216 -0
  16. package/dist/adapters/scure/psbt.d.ts +43 -0
  17. package/dist/adapters/scure/psbt.js +382 -0
  18. package/dist/adapters/scure/script.d.ts +20 -0
  19. package/dist/adapters/scure/script.js +163 -0
  20. package/dist/adapters/scure/transaction.d.ts +2 -0
  21. package/dist/adapters/scure/transaction.js +32 -0
  22. package/dist/adapters/scure.d.ts +6 -0
  23. package/dist/adapters/scure.js +37 -0
  24. package/dist/adapters/scureKeys.d.ts +4 -0
  25. package/dist/adapters/scureKeys.js +135 -0
  26. package/dist/bip174.d.ts +87 -0
  27. package/dist/bip174.js +12 -0
  28. package/dist/bitcoinLib.d.ts +385 -0
  29. package/dist/bitcoinLib.js +19 -0
  30. package/dist/bitcoinjs-lib-internals.d.ts +6 -0
  31. package/dist/bitcoinjs-lib-internals.js +60 -0
  32. package/dist/bitcoinjs.d.ts +12 -0
  33. package/dist/bitcoinjs.js +18 -0
  34. package/dist/checksum.d.ts +6 -0
  35. package/dist/checksum.js +58 -0
  36. package/dist/crypto.d.ts +3 -0
  37. package/dist/crypto.js +79 -0
  38. package/dist/descriptors.d.ts +481 -0
  39. package/dist/descriptors.js +1888 -0
  40. package/dist/index.d.ts +23 -0
  41. package/dist/index.js +87 -0
  42. package/dist/keyExpressions.d.ts +124 -0
  43. package/dist/keyExpressions.js +310 -0
  44. package/dist/keyInterfaces.d.ts +5 -0
  45. package/dist/keyInterfaces.js +50 -0
  46. package/dist/ledger.d.ts +183 -0
  47. package/dist/ledger.js +618 -0
  48. package/dist/miniscript.d.ts +125 -0
  49. package/dist/miniscript.js +310 -0
  50. package/dist/multipath.d.ts +13 -0
  51. package/dist/multipath.js +76 -0
  52. package/dist/networkUtils.d.ts +3 -0
  53. package/dist/networkUtils.js +16 -0
  54. package/dist/networks.d.ts +16 -0
  55. package/dist/networks.js +31 -0
  56. package/dist/parseUtils.d.ts +7 -0
  57. package/dist/parseUtils.js +46 -0
  58. package/dist/psbt.d.ts +40 -0
  59. package/dist/psbt.js +228 -0
  60. package/dist/re.d.ts +31 -0
  61. package/dist/re.js +79 -0
  62. package/dist/resourceLimits.d.ts +28 -0
  63. package/dist/resourceLimits.js +84 -0
  64. package/dist/scriptExpressions.d.ts +95 -0
  65. package/dist/scriptExpressions.js +98 -0
  66. package/dist/scure.d.ts +4 -0
  67. package/dist/scure.js +10 -0
  68. package/dist/signers.d.ts +161 -0
  69. package/dist/signers.js +324 -0
  70. package/dist/tapMiniscript.d.ts +231 -0
  71. package/dist/tapMiniscript.js +524 -0
  72. package/dist/tapTree.d.ts +91 -0
  73. package/dist/tapTree.js +166 -0
  74. package/dist/types.d.ts +296 -0
  75. package/dist/types.js +4 -0
  76. package/package.json +148 -0
@@ -0,0 +1,524 @@
1
+ "use strict";
2
+ // Distributed under the MIT software license
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.buildTapTreeInfo = buildTapTreeInfo;
5
+ exports.tapTreeInfoToScriptTree = tapTreeInfoToScriptTree;
6
+ exports.buildTaprootLeafPsbtMetadata = buildTaprootLeafPsbtMetadata;
7
+ exports.buildTaprootBip32Derivations = buildTaprootBip32Derivations;
8
+ exports.normalizeTaprootPubkey = normalizeTaprootPubkey;
9
+ exports.compileSortedMultiAExpandedExpression = compileSortedMultiAExpandedExpression;
10
+ exports.collectTaprootLeafSatisfactions = collectTaprootLeafSatisfactions;
11
+ exports.selectBestTaprootLeafSatisfaction = selectBestTaprootLeafSatisfaction;
12
+ exports.collectTapTreePubkeys = collectTapTreePubkeys;
13
+ exports.satisfyTapTree = satisfyTapTree;
14
+ const bitcoinjs_lib_internals_1 = require("./bitcoinjs-lib-internals");
15
+ const varuint_bitcoin_1 = require("varuint-bitcoin");
16
+ const uint8array_tools_1 = require("uint8array-tools");
17
+ const networks_1 = require("./networks");
18
+ const miniscript_1 = require("./miniscript");
19
+ const tapTree_1 = require("./tapTree");
20
+ const resourceLimits_1 = require("./resourceLimits");
21
+ const TAPROOT_LEAF_VERSION_TAPSCRIPT = 0xc0;
22
+ function expandTaprootMiniscript({ miniscript, network = networks_1.networks.bitcoin, BIP32, ECPair }) {
23
+ return (0, miniscript_1.expandMiniscript)({
24
+ miniscript,
25
+ isSegwit: true,
26
+ isTaproot: true,
27
+ network,
28
+ BIP32,
29
+ ECPair
30
+ });
31
+ }
32
+ /**
33
+ * Compiles a taproot miniscript tree into per-leaf metadata.
34
+ * Each leaf contains expanded expression metadata, key expansion map,
35
+ * compiled tapscript and leaf version. This keeps the taproot script-path data
36
+ * ready for satisfactions and witness building.
37
+ *
38
+ * `leafExpansionOverride` allows descriptor-level script expressions to provide:
39
+ * - user-facing expanded expression metadata (for selector/template use), and
40
+ * - custom expansion map and tapscript bytes for compilation.
41
+ *
42
+ * Example: sortedmulti_a can expose `expandedExpression=sortedmulti_a(...)`
43
+ * while providing a tapscript already compiled.
44
+ */
45
+ /** @internal */
46
+ function buildTapTreeInfo({ tapTree, network = networks_1.networks.bitcoin, BIP32, ECPair, leafExpansionOverride, scriptLib }) {
47
+ // Defensive: parseTapTreeExpression() already enforces this for descriptor
48
+ // strings, but buildTapTreeInfo is exported and can be called directly.
49
+ (0, tapTree_1.assertTapTreeDepth)(tapTree);
50
+ if ('expression' in tapTree) {
51
+ // Tap-tree leaves store generic descriptor expressions. Most leaves are
52
+ // miniscript fragments, but descriptor-level script expressions (such as
53
+ // sortedmulti_a) can also appear and be normalized through
54
+ // `leafExpansionOverride`.
55
+ const expression = tapTree.expression;
56
+ const override = leafExpansionOverride(expression);
57
+ let expandedExpression;
58
+ let expandedMiniscript;
59
+ let expansionMap;
60
+ let tapScript;
61
+ if (override) {
62
+ // Descriptor-level expression overrides (e.g. sortedmulti_a) preserve a
63
+ // user-facing expanded expression while allowing custom compilation.
64
+ expandedExpression = override.expandedExpression;
65
+ expansionMap = override.expansionMap;
66
+ tapScript = override.tapScript;
67
+ }
68
+ else {
69
+ const expanded = expandTaprootMiniscript({
70
+ miniscript: expression,
71
+ network,
72
+ BIP32,
73
+ ECPair
74
+ });
75
+ expandedExpression = expanded.expandedMiniscript;
76
+ expandedMiniscript = expanded.expandedMiniscript;
77
+ expansionMap = expanded.expansionMap;
78
+ tapScript = (0, miniscript_1.miniscript2Script)({
79
+ expandedMiniscript: expanded.expandedMiniscript,
80
+ expansionMap,
81
+ tapscript: true,
82
+ scriptLib
83
+ });
84
+ }
85
+ return {
86
+ expression,
87
+ expandedExpression,
88
+ ...(expandedMiniscript !== undefined ? { expandedMiniscript } : {}),
89
+ expansionMap,
90
+ tapScript,
91
+ version: TAPROOT_LEAF_VERSION_TAPSCRIPT
92
+ };
93
+ }
94
+ return {
95
+ left: buildTapTreeInfo({
96
+ tapTree: tapTree.left,
97
+ network,
98
+ BIP32,
99
+ ECPair,
100
+ leafExpansionOverride,
101
+ scriptLib
102
+ }),
103
+ right: buildTapTreeInfo({
104
+ tapTree: tapTree.right,
105
+ network,
106
+ BIP32,
107
+ ECPair,
108
+ leafExpansionOverride,
109
+ scriptLib
110
+ })
111
+ };
112
+ }
113
+ /** @internal */
114
+ function tapTreeInfoToScriptTree(tapTreeInfo) {
115
+ if ('expression' in tapTreeInfo) {
116
+ return {
117
+ output: tapTreeInfo.tapScript,
118
+ version: tapTreeInfo.version
119
+ };
120
+ }
121
+ return [
122
+ tapTreeInfoToScriptTree(tapTreeInfo.left),
123
+ tapTreeInfoToScriptTree(tapTreeInfo.right)
124
+ ];
125
+ }
126
+ /**
127
+ * Builds taproot PSBT leaf metadata for every leaf in a `tapTreeInfo`.
128
+ *
129
+ * For each leaf, this function computes:
130
+ * - `tapLeafHash`: BIP341 leaf hash of tapscript + leaf version
131
+ * - `depth`: leaf depth in the tree (root children have depth 1)
132
+ * - `controlBlock`: script-path proof used in PSBT `tapLeafScript`
133
+ *
134
+ * The control block layout is:
135
+ *
136
+ * ```text
137
+ * [1-byte (leafVersion | parity)] [32-byte internal key]
138
+ * [32-byte sibling hash #1] ... [32-byte sibling hash #N]
139
+ * ```
140
+ *
141
+ * where:
142
+ * - `parity` is derived from tweaking the internal key with the tree root
143
+ * - sibling hashes are the merkle path from that leaf to the root
144
+ *
145
+ * Example tree:
146
+ *
147
+ * ```text
148
+ * root
149
+ * / \
150
+ * L1 L2
151
+ * / \
152
+ * L3 L4
153
+ * ```
154
+ *
155
+ * Depths:
156
+ * - L1 depth = 1
157
+ * - L3 depth = 2
158
+ * - L4 depth = 2
159
+ *
160
+ * Conceptual output:
161
+ *
162
+ * ```text
163
+ * [
164
+ * L1 -> { depth: 1, tapLeafHash: h1, controlBlock: [v|p, ik, hash(L2)] }
165
+ * L3 -> { depth: 2, tapLeafHash: h3, controlBlock: [v|p, ik, hash(L4), hash(L1)] }
166
+ * L4 -> { depth: 2, tapLeafHash: h4, controlBlock: [v|p, ik, hash(L3), hash(L1)] }
167
+ * ]
168
+ * ```
169
+ *
170
+ * Legend:
171
+ * - `ik`: the 32-byte internal key placed in the control block.
172
+ * - `hash(X)`: the merkle sibling hash at each level when proving leaf `X`.
173
+ *
174
+ * Note: in this diagram, `L2` is a branch node (right subtree), not a leaf,
175
+ * so `hash(L2) = TapBranch(hash(L3), hash(L4))`.
176
+ *
177
+ * Notes:
178
+ * - Leaves are returned in deterministic left-first order.
179
+ * - One metadata entry is returned per leaf.
180
+ * - `controlBlock.length === 33 + 32 * depth`.
181
+ * - Throws if internal key is invalid or control block cannot be derived.
182
+ *
183
+ * Typical usage:
184
+ * - Convert this metadata into PSBT `tapLeafScript[]` entries
185
+ * for all leaves.
186
+ */
187
+ /** @internal */
188
+ function buildTaprootLeafPsbtMetadata({ tapTreeInfo, internalPubkey, payments }) {
189
+ const normalizedInternalPubkey = normalizeTaprootPubkey(internalPubkey);
190
+ const scriptTree = tapTreeInfoToScriptTree(tapTreeInfo);
191
+ return (0, tapTree_1.collectTapTreeLeaves)(tapTreeInfo).map(({ leaf, depth }) => {
192
+ if (depth > tapTree_1.MAX_TAPTREE_DEPTH)
193
+ throw new Error(`Error: taproot tree depth is too large, ${depth} is larger than ${tapTree_1.MAX_TAPTREE_DEPTH}`);
194
+ const tapLeafHash = (0, bitcoinjs_lib_internals_1.tapleafHash)({
195
+ output: leaf.tapScript,
196
+ version: leaf.version
197
+ });
198
+ const payment = payments.p2tr({
199
+ internalPubkey: normalizedInternalPubkey,
200
+ scriptTree,
201
+ redeem: {
202
+ output: leaf.tapScript,
203
+ redeemVersion: leaf.version
204
+ }
205
+ });
206
+ const controlBlock = payment.witness?.[1];
207
+ if (!controlBlock)
208
+ throw new Error(`Error: could not build controlBlock for taproot leaf ${leaf.expression}`);
209
+ return { leaf, depth, tapLeafHash, controlBlock };
210
+ });
211
+ }
212
+ /**
213
+ * Builds PSBT `tapBip32Derivation` entries for taproot script-path spends.
214
+ *
215
+ * Leaf keys include the list of tapleaf hashes where they appear.
216
+ * If `internalKeyInfo` has derivation data, it is included with empty
217
+ * `leafHashes`.
218
+ *
219
+ * Example tree:
220
+ *
221
+ * ```text
222
+ * root
223
+ * / \
224
+ * L1 L2
225
+ *
226
+ * L1 uses key A
227
+ * L2 uses key A and key B
228
+ *
229
+ * h1 = tapleafHash(L1)
230
+ * h2 = tapleafHash(L2)
231
+ * ```
232
+ *
233
+ * Then output is conceptually:
234
+ *
235
+ * ```text
236
+ * [
237
+ * key A -> leafHashes [h1, h2]
238
+ * key B -> leafHashes [h2]
239
+ * internal key -> leafHashes []
240
+ * ]
241
+ * ```
242
+ *
243
+ * Notes:
244
+ * - Keys missing `masterFingerprint` or `path` are skipped.
245
+ * - Duplicate pubkeys are merged.
246
+ * - If the same pubkey appears with conflicting derivation metadata,
247
+ * this function throws.
248
+ * - Output and `leafHashes` are sorted deterministically.
249
+ */
250
+ /** @internal */
251
+ function buildTaprootBip32Derivations({ tapTreeInfo, internalKeyInfo }) {
252
+ const entries = new Map();
253
+ const updateAndInsert = ({ pubkey, masterFingerprint, path, leafHash }) => {
254
+ const normalizedPubkey = normalizeTaprootPubkey(pubkey);
255
+ const pubkeyHex = (0, uint8array_tools_1.toHex)(normalizedPubkey);
256
+ const current = entries.get(pubkeyHex);
257
+ if (!current) {
258
+ const next = {
259
+ masterFingerprint,
260
+ pubkey: normalizedPubkey,
261
+ path,
262
+ leafHashes: new Map()
263
+ };
264
+ if (leafHash)
265
+ next.leafHashes.set((0, uint8array_tools_1.toHex)(leafHash), leafHash);
266
+ entries.set(pubkeyHex, next);
267
+ return;
268
+ }
269
+ if ((0, uint8array_tools_1.compare)(current.masterFingerprint, masterFingerprint) !== 0 ||
270
+ current.path !== path) {
271
+ throw new Error(`Error: inconsistent taproot key derivation metadata for pubkey ${pubkeyHex}`);
272
+ }
273
+ if (leafHash)
274
+ current.leafHashes.set((0, uint8array_tools_1.toHex)(leafHash), leafHash);
275
+ };
276
+ const leaves = (0, tapTree_1.collectTapTreeLeaves)(tapTreeInfo);
277
+ for (const { leaf } of leaves) {
278
+ const leafHash = (0, bitcoinjs_lib_internals_1.tapleafHash)({
279
+ output: leaf.tapScript,
280
+ version: leaf.version
281
+ });
282
+ for (const keyInfo of Object.values(leaf.expansionMap)) {
283
+ if (!keyInfo.pubkey || !keyInfo.masterFingerprint || !keyInfo.path)
284
+ continue;
285
+ updateAndInsert({
286
+ pubkey: keyInfo.pubkey,
287
+ masterFingerprint: keyInfo.masterFingerprint,
288
+ path: keyInfo.path,
289
+ leafHash
290
+ });
291
+ }
292
+ }
293
+ if (internalKeyInfo?.pubkey &&
294
+ internalKeyInfo.masterFingerprint &&
295
+ internalKeyInfo.path) {
296
+ updateAndInsert({
297
+ pubkey: internalKeyInfo.pubkey,
298
+ masterFingerprint: internalKeyInfo.masterFingerprint,
299
+ path: internalKeyInfo.path
300
+ });
301
+ }
302
+ return [...entries.entries()]
303
+ .sort(([a], [b]) => a.localeCompare(b))
304
+ .map(([, entry]) => ({
305
+ masterFingerprint: entry.masterFingerprint,
306
+ pubkey: entry.pubkey,
307
+ path: entry.path,
308
+ leafHashes: [...entry.leafHashes.entries()]
309
+ .sort(([a], [b]) => a.localeCompare(b))
310
+ .map(([, leafHash]) => leafHash)
311
+ }));
312
+ }
313
+ function varSliceSize(someScript) {
314
+ const length = someScript.length;
315
+ return (0, varuint_bitcoin_1.encodingLength)(length) + length;
316
+ }
317
+ function vectorSize(someVector) {
318
+ const length = someVector.length;
319
+ return ((0, varuint_bitcoin_1.encodingLength)(length) +
320
+ someVector.reduce((sum, witness) => sum + varSliceSize(witness), 0));
321
+ }
322
+ function witnessStackSize(witness) {
323
+ return vectorSize(witness);
324
+ }
325
+ function estimateTaprootWitnessSize({ stackItems, tapScript, depth }) {
326
+ if (depth > tapTree_1.MAX_TAPTREE_DEPTH)
327
+ throw new Error(`Error: taproot tree depth is too large, ${depth} is larger than ${tapTree_1.MAX_TAPTREE_DEPTH}`);
328
+ const controlBlock = new Uint8Array(33 + 32 * depth);
329
+ return witnessStackSize([...stackItems, tapScript, controlBlock]);
330
+ }
331
+ /** @internal */
332
+ function normalizeTaprootPubkey(pubkey) {
333
+ if (pubkey.length === 32)
334
+ return pubkey;
335
+ if (pubkey.length === 33)
336
+ return pubkey.slice(1, 33);
337
+ throw new Error(`Error: invalid taproot pubkey length`);
338
+ }
339
+ /**
340
+ * Compiles an expanded `sortedmulti_a(...)` leaf expression to its internal
341
+ * `multi_a(...)` form by sorting placeholders using the resolved pubkeys from
342
+ * `expansionMap`.
343
+ */
344
+ /** @internal */
345
+ function compileSortedMultiAExpandedExpression({ expandedExpression, expansionMap }) {
346
+ const trimmed = expandedExpression.trim();
347
+ const match = trimmed.match(/^sortedmulti_a\((.*)\)$/);
348
+ if (!match)
349
+ throw new Error(`Error: invalid sortedmulti_a() expression: ${trimmed}`);
350
+ const inner = match[1];
351
+ if (!inner)
352
+ throw new Error(`Error: invalid sortedmulti_a() expression: ${trimmed}`);
353
+ const parts = inner.split(',').map(part => part.trim());
354
+ if (parts.length < 2)
355
+ throw new Error(`Error: invalid sortedmulti_a() expression: ${trimmed}`);
356
+ const threshold = parts[0];
357
+ if (!threshold)
358
+ throw new Error(`Error: invalid sortedmulti_a() threshold: ${trimmed}`);
359
+ const placeholders = parts.slice(1);
360
+ const sortedPlaceholders = placeholders
361
+ .map(placeholder => {
362
+ const keyInfo = expansionMap[placeholder];
363
+ if (!keyInfo?.pubkey)
364
+ throw new Error(`Error: sortedmulti_a() placeholder ${placeholder} not found in expansionMap`);
365
+ return { placeholder, pubkey: keyInfo.pubkey };
366
+ })
367
+ .sort((a, b) => (0, uint8array_tools_1.compare)(a.pubkey, b.pubkey))
368
+ .map(({ placeholder }) => placeholder);
369
+ return `multi_a(${[threshold, ...sortedPlaceholders].join(',')})`;
370
+ }
371
+ /**
372
+ * Computes satisfactions for taproot script-path leaves.
373
+ *
374
+ * If `tapLeaf` is undefined, all satisfiable leaves are returned. If `tapLeaf`
375
+ * is provided, only that leaf is considered.
376
+ *
377
+ * Callers are expected to pass real signatures, or fake signatures generated
378
+ * during planning. See satisfyMiniscript() for how timeConstraints keep the
379
+ * chosen leaf consistent between planning and signing.
380
+ */
381
+ /** @internal */
382
+ function collectTaprootLeafSatisfactions({ tapTreeInfo, preimages, signatures, timeConstraints, tapLeaf, scriptLib }) {
383
+ const candidates = (0, tapTree_1.selectTapLeafCandidates)({
384
+ tapTreeInfo,
385
+ ...(tapLeaf !== undefined ? { tapLeaf } : {})
386
+ });
387
+ const getLeafPubkeys = (leaf) => {
388
+ return Object.values(leaf.expansionMap).map(keyInfo => {
389
+ if (!keyInfo.pubkey)
390
+ throw new Error(`Error: taproot leaf key missing pubkey`);
391
+ return normalizeTaprootPubkey(keyInfo.pubkey);
392
+ });
393
+ };
394
+ const resolveLeafSignatures = (leaf) => {
395
+ const leafPubkeys = getLeafPubkeys(leaf);
396
+ const leafPubkeySet = new Set(leafPubkeys.map(pubkey => (0, uint8array_tools_1.toHex)(pubkey)));
397
+ return signatures
398
+ .map((sig) => ({
399
+ pubkey: normalizeTaprootPubkey(sig.pubkey),
400
+ signature: sig.signature
401
+ }))
402
+ .filter((sig) => leafPubkeySet.has((0, uint8array_tools_1.toHex)(sig.pubkey)));
403
+ };
404
+ const satisfactions = [];
405
+ for (const candidate of candidates) {
406
+ const { leaf } = candidate;
407
+ const leafSignatures = resolveLeafSignatures(leaf);
408
+ try {
409
+ let satisfierMiniscript = leaf.expandedMiniscript;
410
+ // Most taproot leaves are Miniscript fragments, so `expandedMiniscript`
411
+ // is available (and currently matches `expandedExpression`).
412
+ // Descriptor-level leaf expressions (currently `sortedmulti_a`)
413
+ // expose only `expandedExpression`, so we derive an internal
414
+ // Miniscript-equivalent form here before calling `satisfyMiniscript(...)`
415
+ if (!satisfierMiniscript) {
416
+ if (!/^sortedmulti_a\(/.test(leaf.expandedExpression.trim()))
417
+ throw new Error(`Error: taproot leaf does not provide a satisfier miniscript`);
418
+ satisfierMiniscript = compileSortedMultiAExpandedExpression({
419
+ expandedExpression: leaf.expandedExpression,
420
+ expansionMap: leaf.expansionMap
421
+ });
422
+ }
423
+ const { scriptSatisfaction, nLockTime, nSequence } = (0, miniscript_1.satisfyMiniscript)({
424
+ expandedMiniscript: satisfierMiniscript,
425
+ expansionMap: leaf.expansionMap,
426
+ signatures: leafSignatures,
427
+ preimages,
428
+ ...(timeConstraints !== undefined ? { timeConstraints } : {}),
429
+ tapscript: true,
430
+ scriptLib
431
+ });
432
+ const satisfactionStackItems = scriptLib.toStack(scriptSatisfaction);
433
+ (0, resourceLimits_1.assertTaprootScriptPathSatisfactionResourceLimits)({
434
+ stackItems: satisfactionStackItems
435
+ });
436
+ const totalWitnessSize = estimateTaprootWitnessSize({
437
+ stackItems: satisfactionStackItems,
438
+ tapScript: leaf.tapScript,
439
+ depth: candidate.depth
440
+ });
441
+ satisfactions.push({
442
+ leaf,
443
+ depth: candidate.depth,
444
+ tapLeafHash: candidate.tapLeafHash,
445
+ scriptSatisfaction,
446
+ stackItems: satisfactionStackItems,
447
+ nLockTime,
448
+ nSequence,
449
+ totalWitnessSize
450
+ });
451
+ }
452
+ catch (error) {
453
+ if (tapLeaf !== undefined)
454
+ throw error;
455
+ }
456
+ }
457
+ if (satisfactions.length === 0)
458
+ throw new Error(`Error: no satisfiable taproot leaves found`);
459
+ return satisfactions;
460
+ }
461
+ /**
462
+ * Selects the taproot leaf satisfaction with the smallest total witness size.
463
+ * Assumes the input list is in left-first tree order for deterministic ties.
464
+ */
465
+ /** @internal */
466
+ function selectBestTaprootLeafSatisfaction(satisfactions) {
467
+ return satisfactions.reduce((best, current) => {
468
+ if (!best)
469
+ return current;
470
+ if (current.totalWitnessSize < best.totalWitnessSize)
471
+ return current;
472
+ return best;
473
+ });
474
+ }
475
+ /**
476
+ * Collects a unique set of taproot leaf pubkeys (x-only) across the tree.
477
+ * This is useful for building fake signatures when no signer subset is given.
478
+ */
479
+ /** @internal */
480
+ function collectTapTreePubkeys(tapTreeInfo) {
481
+ const pubkeySet = new Set();
482
+ const pubkeys = [];
483
+ const leaves = (0, tapTree_1.collectTapTreeLeaves)(tapTreeInfo);
484
+ for (const entry of leaves) {
485
+ for (const keyInfo of Object.values(entry.leaf.expansionMap)) {
486
+ if (!keyInfo.pubkey)
487
+ throw new Error(`Error: taproot leaf key missing pubkey`);
488
+ const normalized = normalizeTaprootPubkey(keyInfo.pubkey);
489
+ const hex = (0, uint8array_tools_1.toHex)(normalized);
490
+ if (pubkeySet.has(hex))
491
+ continue;
492
+ pubkeySet.add(hex);
493
+ pubkeys.push(normalized);
494
+ }
495
+ }
496
+ return pubkeys;
497
+ }
498
+ /**
499
+ * Returns the best satisfaction for a taproot tree, by witness size.
500
+ *
501
+ * If `tapLeaf` is provided, only that leaf is considered. If `tapLeaf` is a
502
+ * bytes, it is treated as a tapLeafHash and must match exactly one leaf. If
503
+ * `tapLeaf` is a string, it is treated as a raw leaf expression and must
504
+ * match exactly one leaf (whitespace-insensitive).
505
+ *
506
+ * This function is typically called twice:
507
+ * 1) Planning pass: call it with fake signatures (built by the caller) to
508
+ * choose the best leaf without requiring user signatures.
509
+ * 2) Signing pass: call it again with real signatures and the timeConstraints
510
+ * returned from the first pass (see satisfyMiniscript() for why this keeps
511
+ * the chosen leaf consistent between planning and signing).
512
+ */
513
+ /** @internal */
514
+ function satisfyTapTree({ tapTreeInfo, signatures, preimages, tapLeaf, timeConstraints, scriptLib }) {
515
+ const satisfactions = collectTaprootLeafSatisfactions({
516
+ tapTreeInfo,
517
+ preimages,
518
+ signatures,
519
+ ...(tapLeaf !== undefined ? { tapLeaf } : {}),
520
+ ...(timeConstraints !== undefined ? { timeConstraints } : {}),
521
+ scriptLib
522
+ });
523
+ return selectBestTaprootLeafSatisfaction(satisfactions);
524
+ }
@@ -0,0 +1,91 @@
1
+ import type { ExpansionMap } from './types';
2
+ /** @internal */
3
+ export type TreeNode<TLeaf> = TLeaf | {
4
+ left: TreeNode<TLeaf>;
5
+ right: TreeNode<TLeaf>;
6
+ };
7
+ /** @internal */
8
+ export type TapLeaf = {
9
+ /** Raw leaf expression as written in tr(KEY,TREE). */
10
+ expression: string;
11
+ };
12
+ /** @internal */
13
+ export type TapTreeNode = TreeNode<TapLeaf>;
14
+ /** @internal */
15
+ export type TapLeafInfo = {
16
+ /** Raw leaf expression as written in tr(KEY,TREE). */
17
+ expression: string;
18
+ /** Expanded descriptor-level expression for this leaf. */
19
+ expandedExpression: string;
20
+ /**
21
+ * Expanded miniscript form when the leaf expression is a miniscript fragment.
22
+ *
23
+ * For descriptor-level script expressions (e.g. sortedmulti_a), this can be
24
+ * undefined even though `tapScript` is available.
25
+ */
26
+ expandedMiniscript?: string;
27
+ expansionMap: ExpansionMap;
28
+ tapScript: Uint8Array;
29
+ version: number;
30
+ };
31
+ /** @internal */
32
+ export type TapTreeInfoNode = TreeNode<TapLeafInfo>;
33
+ export type TapLeafSelection = {
34
+ leaf: TapLeafInfo;
35
+ depth: number;
36
+ tapLeafHash: Uint8Array;
37
+ };
38
+ export declare const MAX_TAPTREE_DEPTH = 128;
39
+ export declare function assertTapTreeDepth(tapTree: TapTreeNode): void;
40
+ /**
41
+ * Collects taproot leaf metadata with depth from a tree.
42
+ * Traversal is left-first, following the order of `{left,right}` in the
43
+ * expression so tie-breaks are deterministic.
44
+ *
45
+ * Example tree:
46
+ * ```
47
+ * {pk(A),{pk(B),pk(C)}}
48
+ * ```
49
+ * Visual shape:
50
+ * ```
51
+ * root
52
+ * / \
53
+ * pk(A) branch
54
+ * / \
55
+ * pk(B) pk(C)
56
+ * ```
57
+ * Collected leaves with depth:
58
+ * ```
59
+ * [
60
+ * { leaf: pk(A), depth: 1 },
61
+ * { leaf: pk(B), depth: 2 },
62
+ * { leaf: pk(C), depth: 2 }
63
+ * ]
64
+ * ```
65
+ */
66
+ export declare function collectTapTreeLeaves(tapTreeInfo: TapTreeInfoNode): Array<{
67
+ leaf: TapLeafInfo;
68
+ depth: number;
69
+ }>;
70
+ /**
71
+ * Resolves taproot leaf candidates based on an optional selector.
72
+ *
73
+ * If `tapLeaf` is undefined, all leaves are returned for auto-selection.
74
+ * If `tapLeaf` is bytes, it is treated as a tapleaf hash and must match
75
+ * exactly one leaf.
76
+ * If `tapLeaf` is a string, it is treated as a raw taproot leaf expression
77
+ * (not expanded). Matching is whitespace-insensitive. If the expression appears
78
+ * more than once, this function throws an error.
79
+ *
80
+ * Example:
81
+ * ```
82
+ * const candidates = selectTapLeafCandidates({ tapTreeInfo, tapLeaf });
83
+ * // tapLeaf can be undefined, bytes (tapleaf hash) or a leaf expression:
84
+ * // f.ex.: 'pk(03bb...)'
85
+ * ```
86
+ */
87
+ export declare function selectTapLeafCandidates({ tapTreeInfo, tapLeaf }: {
88
+ tapTreeInfo: TapTreeInfoNode;
89
+ tapLeaf?: Uint8Array | string;
90
+ }): TapLeafSelection[];
91
+ export declare function parseTapTreeExpression(expression: string): TapTreeNode;