utxo-descriptors 0.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.
package/dist/parse.cjs ADDED
@@ -0,0 +1,725 @@
1
+ 'use strict';
2
+
3
+ var __defProp = Object.defineProperty;
4
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
5
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
6
+
7
+ // src/checksum.ts
8
+ var INPUT_CHARSET = "0123456789()[],'/*abcdefgh@:$%{}IJKLMNOPQRSTUVWXYZ&+-.;<=>?!^_|~ijklmnopqrstuvwxyzABCDEFGH`#\"\\ ";
9
+ var CHECKSUM_CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l";
10
+ var GENERATOR = [0xf5dee51989n, 0xa9fdca3312n, 0x1bab10e32dn, 0x3706b1677an, 0x644d626ffdn];
11
+ var CHECKSUM_LENGTH = 8;
12
+ var GROUP_SIZE = 3;
13
+ var GROUP_BITS = 5;
14
+ var GROUP_MASK = 0x7ffffffffn;
15
+ var TOP_SHIFT = 35n;
16
+ var SYMBOL_MASK = 31;
17
+ function polymod(symbols) {
18
+ let checksum = 1n;
19
+ for (const symbol of symbols) {
20
+ const top = checksum >> TOP_SHIFT;
21
+ checksum = (checksum & GROUP_MASK) << 5n ^ BigInt(symbol);
22
+ for (let bit = 0; bit < GENERATOR.length; bit += 1) {
23
+ const generatorValue = GENERATOR[bit];
24
+ if (generatorValue !== void 0 && (top >> BigInt(bit) & 1n) === 1n) {
25
+ checksum ^= generatorValue;
26
+ }
27
+ }
28
+ }
29
+ return checksum;
30
+ }
31
+ function expand(text) {
32
+ const symbols = [];
33
+ let groupAccumulator = 0;
34
+ let groupCount = 0;
35
+ for (const char of text) {
36
+ const value = INPUT_CHARSET.indexOf(char);
37
+ if (value === -1) {
38
+ return void 0;
39
+ }
40
+ symbols.push(value & SYMBOL_MASK);
41
+ groupAccumulator = groupAccumulator * GROUP_SIZE + (value >> GROUP_BITS);
42
+ groupCount += 1;
43
+ if (groupCount === GROUP_SIZE) {
44
+ symbols.push(groupAccumulator);
45
+ groupAccumulator = 0;
46
+ groupCount = 0;
47
+ }
48
+ }
49
+ if (groupCount > 0) {
50
+ symbols.push(groupAccumulator);
51
+ }
52
+ return symbols;
53
+ }
54
+ function checksumVerify(script, checksum) {
55
+ if (checksum.length !== CHECKSUM_LENGTH) {
56
+ return false;
57
+ }
58
+ const scriptSymbols = expand(script);
59
+ if (scriptSymbols === void 0) {
60
+ return false;
61
+ }
62
+ const checksumSymbols = [];
63
+ for (const char of checksum) {
64
+ const value = CHECKSUM_CHARSET.indexOf(char);
65
+ if (value === -1) {
66
+ return false;
67
+ }
68
+ checksumSymbols.push(value);
69
+ }
70
+ return polymod([...scriptSymbols, ...checksumSymbols]) === 1n;
71
+ }
72
+ function hasValidCharset(text) {
73
+ return expand(text) !== void 0;
74
+ }
75
+
76
+ // src/base58.ts
77
+ var ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
78
+ var BASE = 58n;
79
+ var BYTE_MAX_PLUS_ONE = 256n;
80
+ var CHAR_TO_VALUE = new Map(
81
+ ALPHABET.split("").map((char, index) => [char, BigInt(index)])
82
+ );
83
+ function base58Decode(text) {
84
+ if (text.length === 0) {
85
+ return void 0;
86
+ }
87
+ let value = 0n;
88
+ for (const char of text) {
89
+ const digit = CHAR_TO_VALUE.get(char);
90
+ if (digit === void 0) {
91
+ return void 0;
92
+ }
93
+ value = value * BASE + digit;
94
+ }
95
+ const bytes = [];
96
+ while (value > 0n) {
97
+ bytes.unshift(Number(value % BYTE_MAX_PLUS_ONE));
98
+ value /= BYTE_MAX_PLUS_ONE;
99
+ }
100
+ for (const char of text) {
101
+ if (char !== "1") {
102
+ break;
103
+ }
104
+ bytes.unshift(0);
105
+ }
106
+ return Uint8Array.from(bytes);
107
+ }
108
+
109
+ // src/key-expression.ts
110
+ var MAX_INDEX = 2147483647;
111
+ var FINGERPRINT_PATTERN = /^[0-9a-fA-F]{8}$/;
112
+ var STEP_PATTERN = /^([0-9]+)(h|')?$/;
113
+ var EXTENDED_KEY_BYTES = 82;
114
+ var WIF_UNCOMPRESSED_BYTES = 37;
115
+ var WIF_COMPRESSED_BYTES = 38;
116
+ var XPUB_VERSIONS = {
117
+ xpub: 76067358,
118
+ xprv: 76066276,
119
+ tpub: 70617039,
120
+ tprv: 70615956
121
+ };
122
+ function fail(message) {
123
+ return { ok: false, message };
124
+ }
125
+ function parseStep(segment) {
126
+ const match = STEP_PATTERN.exec(segment);
127
+ if (match === null) {
128
+ return void 0;
129
+ }
130
+ const [, digits, marker] = match;
131
+ const index = Number(digits);
132
+ if (!Number.isSafeInteger(index) || index > MAX_INDEX) {
133
+ return void 0;
134
+ }
135
+ return { index, hardened: marker !== void 0 };
136
+ }
137
+ function parseOrigin(bracketed) {
138
+ const segments = bracketed.split("/");
139
+ const [fingerprint, ...pathSegments] = segments;
140
+ if (fingerprint === void 0 || !FINGERPRINT_PATTERN.test(fingerprint)) {
141
+ return void 0;
142
+ }
143
+ const path = [];
144
+ for (const segment of pathSegments) {
145
+ const step = parseStep(segment);
146
+ if (step === void 0) {
147
+ return void 0;
148
+ }
149
+ path.push(step);
150
+ }
151
+ return { fingerprint: fingerprint.toLowerCase(), path };
152
+ }
153
+ function splitOrigin(text) {
154
+ if (!text.startsWith("[")) {
155
+ return { remainder: text };
156
+ }
157
+ const closeIndex = text.indexOf("]");
158
+ if (closeIndex === -1) {
159
+ return fail("unterminated key origin: missing ']'");
160
+ }
161
+ const origin = parseOrigin(text.slice(1, closeIndex));
162
+ if (origin === void 0) {
163
+ return fail(
164
+ "invalid key origin: expected an 8-character hex fingerprint and /NUM or /NUMh steps"
165
+ );
166
+ }
167
+ const remainder = text.slice(closeIndex + 1);
168
+ if (remainder.startsWith("[")) {
169
+ return fail("a key expression may only carry one key origin");
170
+ }
171
+ if (remainder.length === 0) {
172
+ return fail("key origin is not followed by a key");
173
+ }
174
+ return { origin, remainder };
175
+ }
176
+ function parseTrailingPath(text) {
177
+ if (text.length === 0) {
178
+ return { path: [], isRanged: false };
179
+ }
180
+ const segments = text.slice(1).split("/");
181
+ const path = [];
182
+ for (const [index, segment] of segments.entries()) {
183
+ const isLast = index === segments.length - 1;
184
+ if (isLast && /^\*(h|')?$/.test(segment)) {
185
+ return { path, isRanged: true };
186
+ }
187
+ if (segment.startsWith("<") && segment.endsWith(">")) {
188
+ const values = segment.slice(1, -1).split(";").map((part) => parseStep(part));
189
+ if (values.length < 2 || values.some((value) => value === void 0)) {
190
+ return void 0;
191
+ }
192
+ const resolved = values;
193
+ const seen = /* @__PURE__ */ new Set();
194
+ for (const value of resolved) {
195
+ const key = `${String(value.index)}:${String(value.hardened)}`;
196
+ if (seen.has(key)) {
197
+ return void 0;
198
+ }
199
+ seen.add(key);
200
+ }
201
+ path.push({ kind: "multipath", values: resolved });
202
+ continue;
203
+ }
204
+ const step = parseStep(segment);
205
+ if (step === void 0) {
206
+ return void 0;
207
+ }
208
+ path.push({ kind: "step", ...step });
209
+ }
210
+ return { path, isRanged: false };
211
+ }
212
+ function classifyKeyMaterial(keyBody) {
213
+ if (/^(02|03)[0-9a-fA-F]{64}$/.test(keyBody)) {
214
+ return "hex-compressed";
215
+ }
216
+ if (/^04[0-9a-fA-F]{128}$/.test(keyBody)) {
217
+ return "hex-uncompressed";
218
+ }
219
+ if (/^[0-9a-fA-F]{64}$/.test(keyBody)) {
220
+ return "hex-xonly";
221
+ }
222
+ const extendedPrefix = keyBody.slice(0, 4);
223
+ if (extendedPrefix in XPUB_VERSIONS) {
224
+ const decoded2 = base58Decode(keyBody);
225
+ const expectedVersion = XPUB_VERSIONS[extendedPrefix];
226
+ if (decoded2?.length === EXTENDED_KEY_BYTES && expectedVersion !== void 0) {
227
+ const version = decoded2[0] << 24 | decoded2[1] << 16 | decoded2[2] << 8 | decoded2[3];
228
+ if (version >>> 0 === expectedVersion >>> 0) {
229
+ return extendedPrefix === "xprv" || extendedPrefix === "tprv" ? "xprv" : "xpub";
230
+ }
231
+ }
232
+ return void 0;
233
+ }
234
+ const decoded = base58Decode(keyBody);
235
+ if (decoded?.length === WIF_COMPRESSED_BYTES) {
236
+ return "wif-compressed";
237
+ }
238
+ if (decoded?.length === WIF_UNCOMPRESSED_BYTES) {
239
+ return "wif-uncompressed";
240
+ }
241
+ return void 0;
242
+ }
243
+ function parseKeyExpression(raw, options) {
244
+ const split = splitOrigin(raw);
245
+ if ("ok" in split) {
246
+ return split;
247
+ }
248
+ const { origin, remainder } = split;
249
+ const slashIndex = remainder.indexOf("/");
250
+ const keyBody = slashIndex === -1 ? remainder : remainder.slice(0, slashIndex);
251
+ const trailingText = slashIndex === -1 ? "" : remainder.slice(slashIndex);
252
+ const kind = classifyKeyMaterial(keyBody);
253
+ if (kind === void 0) {
254
+ return fail(`unrecognized key material: ${keyBody}`);
255
+ }
256
+ if (kind === "hex-xonly" && !options.allowXOnly) {
257
+ return fail("bare x-only (64 hex character) keys are only valid inside tr()");
258
+ }
259
+ if (kind !== "xpub" && kind !== "xprv" && trailingText.length > 0) {
260
+ return fail("a derivation path is only allowed after an extended (xpub/xprv) key");
261
+ }
262
+ const trailing = parseTrailingPath(trailingText);
263
+ if (trailing === void 0) {
264
+ return fail(`invalid derivation path: ${trailingText}`);
265
+ }
266
+ const key = {
267
+ raw,
268
+ kind,
269
+ ...origin !== void 0 ? { origin } : {},
270
+ path: trailing.path,
271
+ isRanged: trailing.isRanged
272
+ };
273
+ return { ok: true, key };
274
+ }
275
+
276
+ // src/tokenizer.ts
277
+ var STRUCTURAL = {
278
+ "(": "lparen",
279
+ ")": "rparen",
280
+ ",": "comma",
281
+ "{": "lbrace",
282
+ "}": "rbrace"
283
+ };
284
+ function tokenize(text) {
285
+ const tokens = [];
286
+ let index = 0;
287
+ while (index < text.length) {
288
+ const char = text[index];
289
+ const structuralKind = STRUCTURAL[char];
290
+ if (structuralKind !== void 0) {
291
+ tokens.push({ kind: structuralKind, text: char, position: index });
292
+ index += 1;
293
+ continue;
294
+ }
295
+ const start = index;
296
+ let atom = "";
297
+ while (index < text.length && STRUCTURAL[text[index]] === void 0) {
298
+ const atomChar = text[index];
299
+ if (!hasValidCharset(atomChar)) {
300
+ return { ok: false, position: index };
301
+ }
302
+ atom += atomChar;
303
+ index += 1;
304
+ }
305
+ tokens.push({ kind: "atom", text: atom, position: start });
306
+ }
307
+ return { ok: true, tokens };
308
+ }
309
+
310
+ // src/traverse.ts
311
+ function forEachKey(node, visit) {
312
+ switch (node.kind) {
313
+ case "pk":
314
+ case "pkh":
315
+ case "wpkh":
316
+ case "combo":
317
+ visit(node.key);
318
+ break;
319
+ case "sh":
320
+ case "wsh":
321
+ forEachKey(node.inner, visit);
322
+ break;
323
+ case "multi":
324
+ node.keys.forEach(visit);
325
+ break;
326
+ case "raw":
327
+ case "addr":
328
+ break;
329
+ case "tr":
330
+ visit(node.internalKey);
331
+ if (node.tree !== void 0) {
332
+ forEachKeyInTree(node.tree, visit);
333
+ }
334
+ break;
335
+ }
336
+ }
337
+ function forEachKeyInTree(tree, visit) {
338
+ if (tree.kind === "leaf") {
339
+ visit(tree.script.key);
340
+ return;
341
+ }
342
+ forEachKeyInTree(tree.left, visit);
343
+ forEachKeyInTree(tree.right, visit);
344
+ }
345
+ function anyKeyRanged(node) {
346
+ let ranged = false;
347
+ forEachKey(node, (key) => {
348
+ if (key.isRanged) {
349
+ ranged = true;
350
+ }
351
+ });
352
+ return ranged;
353
+ }
354
+
355
+ // src/parse.ts
356
+ var CHECKSUM_LENGTH2 = 8;
357
+ var HEX_PAIR = 2;
358
+ function keyLimit(context) {
359
+ if (context.place === "top") {
360
+ return 3;
361
+ }
362
+ if (context.place === "sh") {
363
+ return 15;
364
+ }
365
+ return 20;
366
+ }
367
+ function requireCompressed(context) {
368
+ return context.place === "wsh";
369
+ }
370
+ function isCompressedCapable(key) {
371
+ return key.kind === "hex-compressed" || key.kind === "wif-compressed" || key.kind === "xpub" || key.kind === "xprv";
372
+ }
373
+ var Cursor = class {
374
+ constructor(tokens) {
375
+ __publicField(this, "tokens", tokens);
376
+ __publicField(this, "position", 0);
377
+ }
378
+ peek() {
379
+ return this.tokens[this.position];
380
+ }
381
+ advance() {
382
+ const token = this.tokens[this.position];
383
+ this.position += 1;
384
+ return token;
385
+ }
386
+ atEnd() {
387
+ return this.position >= this.tokens.length;
388
+ }
389
+ };
390
+ function fail2(reason, message, position) {
391
+ return { ok: false, reason, message, ...position !== void 0 ? { position } : {} };
392
+ }
393
+ function expectKind(cursor, kind, what) {
394
+ const token = cursor.advance();
395
+ if (token === void 0) {
396
+ return fail2("unexpected-token", `expected ${what} but reached the end of the descriptor`);
397
+ }
398
+ if (token.kind !== kind) {
399
+ return fail2("unexpected-token", `expected ${what}`, token.position);
400
+ }
401
+ return { ok: true, token };
402
+ }
403
+ function isFailure(value) {
404
+ return !value.ok;
405
+ }
406
+ function parseKey(cursor, allowXOnly) {
407
+ const token = cursor.advance();
408
+ if (token === void 0) {
409
+ return fail2(
410
+ "unexpected-token",
411
+ "expected a key expression but reached the end of the descriptor"
412
+ );
413
+ }
414
+ if (token.kind !== "atom") {
415
+ return fail2("unexpected-token", "expected a key expression", token.position);
416
+ }
417
+ const result = parseKeyExpression(token.text, { allowXOnly });
418
+ if (!result.ok) {
419
+ return fail2("invalid-key-expression", result.message, token.position);
420
+ }
421
+ return { ok: true, key: result.key };
422
+ }
423
+ function parseNumber(cursor) {
424
+ const token = cursor.advance();
425
+ if (token === void 0) {
426
+ return fail2("unexpected-token", "expected a number but reached the end of the descriptor");
427
+ }
428
+ if (token.kind !== "atom" || !/^[0-9]+$/.test(token.text) || !Number.isSafeInteger(Number(token.text))) {
429
+ return fail2("unexpected-token", "expected a number", token.position);
430
+ }
431
+ return { ok: true, value: Number(token.text) };
432
+ }
433
+ function parseSingleKeyNode(cursor, kind, context, position) {
434
+ const keyResult = parseKey(cursor, false);
435
+ if (isFailure(keyResult)) {
436
+ return keyResult;
437
+ }
438
+ if ((kind === "wpkh" || requireCompressed(context)) && !isCompressedCapable(keyResult.key)) {
439
+ return fail2("invalid-key-expression", `${kind}() requires a compressed key`, position);
440
+ }
441
+ const closeResult = expectKind(cursor, "rparen", `')' to close ${kind}(...)`);
442
+ if (isFailure(closeResult)) {
443
+ return closeResult;
444
+ }
445
+ return { ok: true, node: { kind, key: keyResult.key } };
446
+ }
447
+ function parseMultiNode(cursor, sorted, context, position) {
448
+ const thresholdResult = parseNumber(cursor);
449
+ if (isFailure(thresholdResult)) {
450
+ return thresholdResult;
451
+ }
452
+ const keys = [];
453
+ for (; ; ) {
454
+ const comma = expectKind(cursor, "comma", "',' before a key in multi(...)");
455
+ if (isFailure(comma)) {
456
+ return comma;
457
+ }
458
+ const keyResult = parseKey(cursor, false);
459
+ if (isFailure(keyResult)) {
460
+ return keyResult;
461
+ }
462
+ if (requireCompressed(context) && !isCompressedCapable(keyResult.key)) {
463
+ return fail2("invalid-key-expression", "keys inside wsh() must be compressed", position);
464
+ }
465
+ keys.push(keyResult.key);
466
+ if (cursor.peek()?.kind !== "comma") {
467
+ break;
468
+ }
469
+ }
470
+ const closeResult = expectKind(cursor, "rparen", "')' to close multi(...)");
471
+ if (isFailure(closeResult)) {
472
+ return closeResult;
473
+ }
474
+ const limit = keyLimit(context);
475
+ if (keys.length > limit) {
476
+ return fail2(
477
+ "key-count-exceeded",
478
+ `at most ${String(limit)} keys are allowed in this context`,
479
+ position
480
+ );
481
+ }
482
+ if (thresholdResult.value < 1 || thresholdResult.value > keys.length) {
483
+ return fail2("invalid-context", "threshold must be between 1 and the number of keys", position);
484
+ }
485
+ return { ok: true, node: { kind: "multi", threshold: thresholdResult.value, keys, sorted } };
486
+ }
487
+ function parseTapLeaf(cursor) {
488
+ const nameToken = cursor.advance();
489
+ if (nameToken === void 0) {
490
+ return fail2(
491
+ "unexpected-token",
492
+ "expected a tapscript leaf but reached the end of the descriptor"
493
+ );
494
+ }
495
+ if (nameToken.kind !== "atom" || nameToken.text !== "pk") {
496
+ return fail2(
497
+ "unsupported",
498
+ "tapscript leaves are limited to pk(KEY) in this version; other miniscript fragments are not yet supported",
499
+ nameToken.position
500
+ );
501
+ }
502
+ const lparen = expectKind(cursor, "lparen", "'(' after pk");
503
+ if (isFailure(lparen)) {
504
+ return lparen;
505
+ }
506
+ const keyResult = parseKey(cursor, true);
507
+ if (isFailure(keyResult)) {
508
+ return keyResult;
509
+ }
510
+ const closeResult = expectKind(cursor, "rparen", "')' to close pk(...)");
511
+ if (isFailure(closeResult)) {
512
+ return closeResult;
513
+ }
514
+ const leaf = { kind: "pk", key: keyResult.key };
515
+ return { ok: true, tree: { kind: "leaf", script: leaf } };
516
+ }
517
+ function parseTapTree(cursor) {
518
+ if (cursor.peek()?.kind === "lbrace") {
519
+ cursor.advance();
520
+ const leftResult = parseTapTree(cursor);
521
+ if (isFailure(leftResult)) {
522
+ return leftResult;
523
+ }
524
+ const comma = expectKind(cursor, "comma", "',' inside a tapscript tree branch");
525
+ if (isFailure(comma)) {
526
+ return comma;
527
+ }
528
+ const rightResult = parseTapTree(cursor);
529
+ if (isFailure(rightResult)) {
530
+ return rightResult;
531
+ }
532
+ const closeResult = expectKind(cursor, "rbrace", "'}' to close a tapscript tree branch");
533
+ if (isFailure(closeResult)) {
534
+ return closeResult;
535
+ }
536
+ return { ok: true, tree: { kind: "branch", left: leftResult.tree, right: rightResult.tree } };
537
+ }
538
+ return parseTapLeaf(cursor);
539
+ }
540
+ function parseTr(cursor) {
541
+ const keyResult = parseKey(cursor, true);
542
+ if (isFailure(keyResult)) {
543
+ return keyResult;
544
+ }
545
+ if (cursor.peek()?.kind === "rparen") {
546
+ cursor.advance();
547
+ return { ok: true, node: { kind: "tr", internalKey: keyResult.key } };
548
+ }
549
+ const comma = expectKind(cursor, "comma", "',' before the tapscript tree in tr(...)");
550
+ if (isFailure(comma)) {
551
+ return comma;
552
+ }
553
+ const treeResult = parseTapTree(cursor);
554
+ if (isFailure(treeResult)) {
555
+ return treeResult;
556
+ }
557
+ const closeResult = expectKind(cursor, "rparen", "')' to close tr(...)");
558
+ if (isFailure(closeResult)) {
559
+ return closeResult;
560
+ }
561
+ return { ok: true, node: { kind: "tr", internalKey: keyResult.key, tree: treeResult.tree } };
562
+ }
563
+ function parseRaw(cursor, position) {
564
+ const token = cursor.advance();
565
+ if (token === void 0) {
566
+ return fail2("unexpected-token", "expected hex data in raw(...)", position);
567
+ }
568
+ if (token.kind !== "atom" || !/^[0-9a-fA-F]+$/.test(token.text) || token.text.length % HEX_PAIR !== 0) {
569
+ return fail2("invalid-context", "raw() requires an even-length hex string", token.position);
570
+ }
571
+ const closeResult = expectKind(cursor, "rparen", "')' to close raw(...)");
572
+ if (isFailure(closeResult)) {
573
+ return closeResult;
574
+ }
575
+ return { ok: true, node: { kind: "raw", hex: token.text.toLowerCase() } };
576
+ }
577
+ function parseAddr(cursor, position) {
578
+ const token = cursor.advance();
579
+ if (token?.kind !== "atom") {
580
+ return fail2("unexpected-token", "expected an address in addr(...)", position);
581
+ }
582
+ const closeResult = expectKind(cursor, "rparen", "')' to close addr(...)");
583
+ if (isFailure(closeResult)) {
584
+ return closeResult;
585
+ }
586
+ return { ok: true, node: { kind: "addr", address: token.text } };
587
+ }
588
+ function parseScript(cursor, context) {
589
+ const nameToken = cursor.advance();
590
+ if (nameToken === void 0) {
591
+ return fail2(
592
+ "unexpected-token",
593
+ "expected a script expression but reached the end of the descriptor"
594
+ );
595
+ }
596
+ if (nameToken.kind !== "atom") {
597
+ return fail2("unexpected-token", "expected a function name", nameToken.position);
598
+ }
599
+ const name = nameToken.text;
600
+ const position = nameToken.position;
601
+ const lparen = expectKind(cursor, "lparen", `'(' after ${name}`);
602
+ if (isFailure(lparen)) {
603
+ return lparen;
604
+ }
605
+ switch (name) {
606
+ case "pk":
607
+ case "pkh":
608
+ return parseSingleKeyNode(cursor, name, context, position);
609
+ case "wpkh": {
610
+ if (context.place === "wsh") {
611
+ return fail2("invalid-context", "wpkh() is not allowed inside wsh()", position);
612
+ }
613
+ return parseSingleKeyNode(cursor, "wpkh", context, position);
614
+ }
615
+ case "sh": {
616
+ if (context.place !== "top") {
617
+ return fail2("invalid-context", "sh() is only allowed at the top level", position);
618
+ }
619
+ const innerResult = parseScript(cursor, { place: "sh" });
620
+ if (isFailure(innerResult)) {
621
+ return innerResult;
622
+ }
623
+ const closeResult = expectKind(cursor, "rparen", "')' to close sh(...)");
624
+ if (isFailure(closeResult)) {
625
+ return closeResult;
626
+ }
627
+ return { ok: true, node: { kind: "sh", inner: innerResult.node } };
628
+ }
629
+ case "wsh": {
630
+ if (context.place === "wsh") {
631
+ return fail2("invalid-context", "wsh() cannot be nested inside wsh()", position);
632
+ }
633
+ const innerResult = parseScript(cursor, { place: "wsh" });
634
+ if (isFailure(innerResult)) {
635
+ return innerResult;
636
+ }
637
+ const closeResult = expectKind(cursor, "rparen", "')' to close wsh(...)");
638
+ if (isFailure(closeResult)) {
639
+ return closeResult;
640
+ }
641
+ return { ok: true, node: { kind: "wsh", inner: innerResult.node } };
642
+ }
643
+ case "multi":
644
+ case "sortedmulti":
645
+ return parseMultiNode(cursor, name === "sortedmulti", context, position);
646
+ case "combo": {
647
+ if (context.place !== "top") {
648
+ return fail2("invalid-context", "combo() is only allowed at the top level", position);
649
+ }
650
+ const keyResult = parseKey(cursor, false);
651
+ if (isFailure(keyResult)) {
652
+ return keyResult;
653
+ }
654
+ const closeResult = expectKind(cursor, "rparen", "')' to close combo(...)");
655
+ if (isFailure(closeResult)) {
656
+ return closeResult;
657
+ }
658
+ return { ok: true, node: { kind: "combo", key: keyResult.key } };
659
+ }
660
+ case "raw": {
661
+ if (context.place !== "top") {
662
+ return fail2("invalid-context", "raw() is only allowed at the top level", position);
663
+ }
664
+ return parseRaw(cursor, position);
665
+ }
666
+ case "addr": {
667
+ if (context.place !== "top") {
668
+ return fail2("invalid-context", "addr() is only allowed at the top level", position);
669
+ }
670
+ return parseAddr(cursor, position);
671
+ }
672
+ case "tr": {
673
+ if (context.place !== "top") {
674
+ return fail2("invalid-context", "tr() is only allowed at the top level", position);
675
+ }
676
+ return parseTr(cursor);
677
+ }
678
+ case "musig":
679
+ return fail2("unsupported", "musig() (BIP-390) is not implemented", position);
680
+ case "sp":
681
+ return fail2("unsupported", "sp() (BIP-392, silent payments) is not implemented", position);
682
+ default:
683
+ return fail2("unknown-function", `unknown script expression: ${name}`, position);
684
+ }
685
+ }
686
+ function parseDescriptor(text) {
687
+ const hashIndex = text.indexOf("#");
688
+ const scriptText = hashIndex === -1 ? text : text.slice(0, hashIndex);
689
+ const tokenizeResult = tokenize(scriptText);
690
+ if (!tokenizeResult.ok) {
691
+ return fail2(
692
+ "invalid-character",
693
+ "the descriptor contains a character outside the allowed charset",
694
+ tokenizeResult.position
695
+ );
696
+ }
697
+ if (hashIndex !== -1) {
698
+ const checksumText = text.slice(hashIndex + 1);
699
+ if (checksumText.length !== CHECKSUM_LENGTH2 || !checksumVerify(scriptText, checksumText)) {
700
+ return fail2("invalid-checksum", "the descriptor checksum does not match its script");
701
+ }
702
+ }
703
+ const cursor = new Cursor(tokenizeResult.tokens);
704
+ const scriptResult = parseScript(cursor, { place: "top" });
705
+ if (isFailure(scriptResult)) {
706
+ return scriptResult;
707
+ }
708
+ if (!cursor.atEnd()) {
709
+ return fail2(
710
+ "unexpected-token",
711
+ "unexpected trailing content after the descriptor",
712
+ cursor.peek()?.position
713
+ );
714
+ }
715
+ const descriptor = {
716
+ script: scriptResult.node,
717
+ ...hashIndex !== -1 ? { checksum: text.slice(hashIndex + 1) } : {},
718
+ isRanged: anyKeyRanged(scriptResult.node)
719
+ };
720
+ return { ok: true, descriptor };
721
+ }
722
+
723
+ exports.parseDescriptor = parseDescriptor;
724
+ //# sourceMappingURL=parse.cjs.map
725
+ //# sourceMappingURL=parse.cjs.map