@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,125 @@
1
+ import type { ECPairAPILike, BIP32APILike } from './bitcoinLib';
2
+ import { type Network } from './networks';
3
+ import type { PartialSig } from './bip174';
4
+ import type { Preimage, TimeConstraints, ExpansionMap } from './types';
5
+ import type { BitcoinLib } from './bitcoinLib';
6
+ /**
7
+ * Expand a miniscript to a generalized form using variables instead of key
8
+ * expressions. Variables will be of this form: @0, @1, ...
9
+ * This is done so that it can be compiled with compileMiniscript and
10
+ * satisfied with satisfier.
11
+ * Also compute pubkeys from descriptors to use them later.
12
+ */
13
+ export declare function expandMiniscript({ miniscript, isSegwit, isTaproot, network, ECPair, BIP32 }: {
14
+ miniscript: string;
15
+ isSegwit: boolean;
16
+ isTaproot: boolean;
17
+ network?: Network;
18
+ ECPair: ECPairAPILike;
19
+ BIP32: BIP32APILike;
20
+ }): {
21
+ expandedMiniscript: string;
22
+ expansionMap: ExpansionMap;
23
+ };
24
+ export declare function miniscript2Script({ expandedMiniscript, expansionMap, tapscript, scriptLib }: {
25
+ expandedMiniscript: string;
26
+ expansionMap: ExpansionMap;
27
+ tapscript?: boolean;
28
+ scriptLib: BitcoinLib['script'];
29
+ }): Uint8Array;
30
+ /**
31
+ * Assumptions:
32
+ * The attacker does not have access to any of the private keys of public keys
33
+ * that participate in the Script.
34
+ *
35
+ * The attacker only has access to hash preimages that honest users have access
36
+ * to as well.
37
+ *
38
+ * Pass timeConstraints to search for the first solution with this nLockTime and
39
+ * nSequence. Throw if no solution is possible using these constraints.
40
+ *
41
+ * Time constraints are used to keep the chosen satisfaction stable between the
42
+ * planning pass (fake signatures) and the signing pass (real signatures).
43
+ * We run the satisfier once with fake signatures to discover the implied
44
+ * nLockTime/nSequence without requiring user signatures. If real signatures
45
+ * had the same length, the satisfier would typically pick the same
46
+ * minimal-weight solution again. But ECDSA signature sizes can vary (71–73
47
+ * bytes), which may change which solution is considered "smallest".
48
+ *
49
+ * Passing the previously derived timeConstraints in the second pass forces the
50
+ * same solution to be selected, ensuring locktime/sequence do not change
51
+ * between planning and finalization.
52
+ *
53
+ * Don't pass timeConstraints (this is the default) if you want to get the
54
+ * smallest size solution altogether.
55
+ *
56
+ * If a solution is not found this function throws.
57
+ */
58
+ export declare function satisfyMiniscript({ expandedMiniscript, expansionMap, signatures, preimages, timeConstraints, tapscript, scriptLib }: {
59
+ expandedMiniscript: string;
60
+ expansionMap: ExpansionMap;
61
+ signatures?: PartialSig[];
62
+ preimages?: Preimage[];
63
+ timeConstraints?: TimeConstraints;
64
+ tapscript?: boolean;
65
+ scriptLib: BitcoinLib['script'];
66
+ }): {
67
+ scriptSatisfaction: Uint8Array;
68
+ nLockTime: number | undefined;
69
+ nSequence: number | undefined;
70
+ };
71
+ /**
72
+ *
73
+ * Use this function instead of bitcoinjs-lib's equivalent `script.number.encode`
74
+ * when encoding numbers to be compiled with `fromASM` to avoid problems.
75
+ *
76
+ * Motivation:
77
+ *
78
+ * Numbers in Bitcoin assembly code are represented in hex and in Little Endian.
79
+ * Decimal: 32766 - Big endian: 0x7FFE - Little Endian: 0xFE7F.
80
+ *
81
+ * This function takes an integer and encodes it so that bitcoinjs-lib `fromASM`
82
+ * can compile it. This is basically what bitcoinjs-lib's `script.number.encode`
83
+ * does.
84
+ *
85
+ * Note that `fromASM` already converts integers from 1 to 16 to
86
+ * OP_1 ... OP_16 {@link https://github.com/bitcoinjs/bitcoinjs-lib/blob/59b21162a2c4645c64271ca004c7a3755a3d72fb/src/script.js#L33 here}.
87
+ * This is done in Bitcoin to save some bits.
88
+ *
89
+ * Neither this function nor `script.number.encode` convert numbers to
90
+ * their op code equivalent since this is done later in `fromASM`.
91
+ *
92
+ * Both functions simply convert numbers to Little Endian.
93
+ *
94
+ * However, the `0` number is an edge case that we specially handle with this
95
+ * function.
96
+ *
97
+ * bitcoinjs-lib's `scriptLib.number.encode(0)` produces an empty array.
98
+ * This is what the Bitcoin interpreter does and it is what `script.number.encode` was
99
+ * implemented to do.
100
+ *
101
+ * The problem is `scriptLib.number.encode(0).toString('hex')` produces an
102
+ * empty string and thus it should not be used to serialize number zero before `fromASM`.
103
+ *
104
+ * A zero should produce the OP_0 ASM symbolic code (corresponding to a `0` when
105
+ * compiled).
106
+ *
107
+ * So, this function will produce a string in hex format in Little Endian
108
+ * encoding for integers not equal to `0` and it will return `OP_0` for `0`.
109
+ *
110
+ * Read more about the this {@link https://github.com/bitcoinjs/bitcoinjs-lib/issues/1799#issuecomment-1122591738 here}.
111
+ *
112
+ * Use it in combination with `fromASM` like this:
113
+ *
114
+ * ```javascript
115
+ * //To produce "0 1 OP_ADD":
116
+ * fromASM(
117
+ * `${numberEncodeAsm(0)} ${numberEncodeAsm(1)} OP_ADD`
118
+ * .trim().replace(/\s+/g, ' ')
119
+ * )
120
+ * ```
121
+ *
122
+ * @param {number} number An integer.
123
+ * @returns {string} Returns `"OP_0"` for `number === 0` and a hex string representing other numbers in Little Endian encoding.
124
+ */
125
+ export declare function numberEncodeAsm(number: number, scriptLib: BitcoinLib['script']): string;
@@ -0,0 +1,310 @@
1
+ "use strict";
2
+ // Copyright (c) 2023 Jose-Luis Landabaso - https://bitcoinerlab.com
3
+ // Distributed under the MIT software license
4
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
5
+ if (k2 === undefined) k2 = k;
6
+ var desc = Object.getOwnPropertyDescriptor(m, k);
7
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
8
+ desc = { enumerable: true, get: function() { return m[k]; } };
9
+ }
10
+ Object.defineProperty(o, k2, desc);
11
+ }) : (function(o, m, k, k2) {
12
+ if (k2 === undefined) k2 = k;
13
+ o[k2] = m[k];
14
+ }));
15
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
16
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
17
+ }) : function(o, v) {
18
+ o["default"] = v;
19
+ });
20
+ var __importStar = (this && this.__importStar) || (function () {
21
+ var ownKeys = function(o) {
22
+ ownKeys = Object.getOwnPropertyNames || function (o) {
23
+ var ar = [];
24
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
25
+ return ar;
26
+ };
27
+ return ownKeys(o);
28
+ };
29
+ return function (mod) {
30
+ if (mod && mod.__esModule) return mod;
31
+ var result = {};
32
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
33
+ __setModuleDefault(result, mod);
34
+ return result;
35
+ };
36
+ })();
37
+ Object.defineProperty(exports, "__esModule", { value: true });
38
+ exports.expandMiniscript = expandMiniscript;
39
+ exports.miniscript2Script = miniscript2Script;
40
+ exports.satisfyMiniscript = satisfyMiniscript;
41
+ exports.numberEncodeAsm = numberEncodeAsm;
42
+ const keyExpressions_1 = require("./keyExpressions");
43
+ const networks_1 = require("./networks");
44
+ const RE = __importStar(require("./re"));
45
+ const miniscript_1 = require("@bitcoinerlab/miniscript");
46
+ const uint8array_tools_1 = require("uint8array-tools");
47
+ const crypto_1 = require("./crypto");
48
+ /**
49
+ * Expand a miniscript to a generalized form using variables instead of key
50
+ * expressions. Variables will be of this form: @0, @1, ...
51
+ * This is done so that it can be compiled with compileMiniscript and
52
+ * satisfied with satisfier.
53
+ * Also compute pubkeys from descriptors to use them later.
54
+ */
55
+ function expandMiniscript({ miniscript, isSegwit, isTaproot, network = networks_1.networks.bitcoin, ECPair, BIP32 }) {
56
+ const reKeyExp = isTaproot
57
+ ? RE.reTaprootKeyExp
58
+ : isSegwit
59
+ ? RE.reSegwitKeyExp
60
+ : RE.reNonSegwitKeyExp;
61
+ const expansionMap = {};
62
+ let keyIndex = 0;
63
+ const keyExpressionRegex = new RegExp(String.raw `^${reKeyExp}$`);
64
+ const replaceKeyExpression = (keyExpression) => {
65
+ const trimmed = keyExpression.trim();
66
+ if (!trimmed)
67
+ throw new Error(`Error: expected a keyExpression but got ${keyExpression}`);
68
+ if (!keyExpressionRegex.test(trimmed))
69
+ throw new Error(`Error: expected a keyExpression but got ${trimmed}`);
70
+ const key = `@${keyIndex}`;
71
+ keyIndex += 1;
72
+ expansionMap[key] = (0, keyExpressions_1.parseKeyExpression)({
73
+ keyExpression: trimmed,
74
+ isSegwit,
75
+ isTaproot,
76
+ network,
77
+ ECPair,
78
+ BIP32
79
+ });
80
+ return key;
81
+ };
82
+ // These are the Miniscript fragments where key arguments are expected.
83
+ // We only replace key expressions inside these fragments to avoid confusing
84
+ // hash preimages (e.g. sha256(03...)) with keys.
85
+ //
86
+ // Note: sorted script expressions (`sortedmulti`, `sortedmulti_a`) are
87
+ // intentionally excluded here. They are descriptor-level expressions (not
88
+ // Miniscript fragments) and are handled in descriptor/tap-tree code paths
89
+ // before Miniscript compilation.
90
+ const keyBearingFragmentRegex = /\b(pk|pkh|multi_a|multi)\(([^()]*)\)/g;
91
+ const expandedMiniscript = miniscript.replace(keyBearingFragmentRegex, (_, name, inner) => {
92
+ if (name === 'pk' || name === 'pkh')
93
+ return `${name}(${replaceKeyExpression(inner)})`;
94
+ //now do *multi* which has arguments:
95
+ const parts = inner.split(',').map(part => part.trim());
96
+ if (parts.length < 2)
97
+ throw new Error(`Error: invalid miniscript ${miniscript} (missing keys)`);
98
+ const k = parts[0] ?? '';
99
+ if (!k)
100
+ throw new Error(`Error: invalid miniscript ${miniscript} (missing threshold)`);
101
+ const replacedKeys = parts
102
+ .slice(1)
103
+ .map(keyExpression => replaceKeyExpression(keyExpression));
104
+ return `${name}(${[k, ...replacedKeys].join(',')})`;
105
+ });
106
+ //Do some assertions. Miniscript must not have duplicate keys, also all
107
+ //keyExpressions must produce a valid pubkey (unless it's ranged and we want
108
+ //to expand a generalized form, then we don't check)
109
+ const pubkeysHex = Object.values(expansionMap)
110
+ .filter(keyInfo => keyInfo.keyExpression.indexOf('*') === -1)
111
+ .map(keyInfo => {
112
+ if (!keyInfo.pubkey)
113
+ throw new Error(`Error: keyExpression ${keyInfo.keyExpression} does not have a pubkey`);
114
+ return (0, uint8array_tools_1.toHex)(keyInfo.pubkey);
115
+ });
116
+ if (new Set(pubkeysHex).size !== pubkeysHex.length) {
117
+ throw new Error(`Error: miniscript ${miniscript} is not sane: contains duplicate public keys.`);
118
+ }
119
+ return { expandedMiniscript, expansionMap };
120
+ }
121
+ /**
122
+ * Particularize an expanded ASM expression using the variables in
123
+ * expansionMap.
124
+ * This is the kind of the opposite to what expandMiniscript does.
125
+ * Signatures and preimages are already subsituted by the satisfier calling
126
+ * this function.
127
+ */
128
+ function substituteAsm({ expandedAsm, expansionMap, scriptLib }) {
129
+ //Replace back variables into the pubkeys previously computed.
130
+ let asm = Object.keys(expansionMap).reduce((accAsm, key) => {
131
+ const pubkey = expansionMap[key]?.pubkey;
132
+ if (!pubkey) {
133
+ throw new Error(`Error: invalid expansionMap for ${key}`);
134
+ }
135
+ return accAsm
136
+ .replaceAll(`<${key}>`, `<${(0, uint8array_tools_1.toHex)(pubkey)}>`)
137
+ .replaceAll(`<HASH160(${key})>`, `<${(0, uint8array_tools_1.toHex)((0, crypto_1.hash160)(pubkey))}>`);
138
+ }, expandedAsm);
139
+ //Now clean it and prepare it so that fromASM can be called:
140
+ asm = asm
141
+ .trim()
142
+ //Replace one or more consecutive whitespace characters (spaces, tabs,
143
+ //or line breaks) with a single space.
144
+ .replace(/\s+/g, ' ')
145
+ //Now encode numbers to little endian hex. Note that numbers are not
146
+ //enclosed in <>, since <> represents hex code already encoded.
147
+ //The regex below will match one or more digits within a string,
148
+ //except if the sequence is surrounded by "<" and ">"
149
+ .replace(/(<\d+>)|\b\d+\b/g, match => match.startsWith('<') ? match : numberEncodeAsm(Number(match), scriptLib))
150
+ //we don't have numbers anymore, now it's safe to remove < and > since we
151
+ //know that every remaining is either an op_code or a hex encoded number
152
+ .replace(/[<>]/g, '');
153
+ return asm;
154
+ }
155
+ function miniscript2Script({ expandedMiniscript, expansionMap, tapscript = false, scriptLib }) {
156
+ const compiled = (0, miniscript_1.compileMiniscript)(expandedMiniscript, { tapscript });
157
+ if (compiled.issane !== true) {
158
+ throw new Error(`Error: Miniscript ${expandedMiniscript} is not sane`);
159
+ }
160
+ return scriptLib.fromASM(substituteAsm({
161
+ expandedAsm: compiled.asm,
162
+ expansionMap,
163
+ scriptLib
164
+ }));
165
+ }
166
+ /**
167
+ * Assumptions:
168
+ * The attacker does not have access to any of the private keys of public keys
169
+ * that participate in the Script.
170
+ *
171
+ * The attacker only has access to hash preimages that honest users have access
172
+ * to as well.
173
+ *
174
+ * Pass timeConstraints to search for the first solution with this nLockTime and
175
+ * nSequence. Throw if no solution is possible using these constraints.
176
+ *
177
+ * Time constraints are used to keep the chosen satisfaction stable between the
178
+ * planning pass (fake signatures) and the signing pass (real signatures).
179
+ * We run the satisfier once with fake signatures to discover the implied
180
+ * nLockTime/nSequence without requiring user signatures. If real signatures
181
+ * had the same length, the satisfier would typically pick the same
182
+ * minimal-weight solution again. But ECDSA signature sizes can vary (71–73
183
+ * bytes), which may change which solution is considered "smallest".
184
+ *
185
+ * Passing the previously derived timeConstraints in the second pass forces the
186
+ * same solution to be selected, ensuring locktime/sequence do not change
187
+ * between planning and finalization.
188
+ *
189
+ * Don't pass timeConstraints (this is the default) if you want to get the
190
+ * smallest size solution altogether.
191
+ *
192
+ * If a solution is not found this function throws.
193
+ */
194
+ function satisfyMiniscript({ expandedMiniscript, expansionMap, signatures = [], preimages = [], timeConstraints, tapscript = false, scriptLib }) {
195
+ //convert 'sha256(6c...33)' to: { ['<sha256_preimage(6c...33)>']: '10...5f'}
196
+ const preimageMap = {};
197
+ preimages.forEach(preimage => {
198
+ preimageMap['<' + preimage.digest.replace('(', '_preimage(') + '>'] =
199
+ '<' + preimage.preimage + '>';
200
+ });
201
+ //convert the pubkeys in signatures into [{['<sig(@0)>']: '30450221'}, ...]
202
+ //get the keyExpressions: @0, @1 from the keys in expansionMap
203
+ const expandedSignatureMap = {};
204
+ signatures.forEach(signature => {
205
+ const pubkeyHex = (0, uint8array_tools_1.toHex)(signature.pubkey);
206
+ const keyExpression = Object.keys(expansionMap).find(k => expansionMap[k]?.pubkey && (0, uint8array_tools_1.toHex)(expansionMap[k].pubkey) === pubkeyHex);
207
+ expandedSignatureMap['<sig(' + keyExpression + ')>'] =
208
+ '<' + (0, uint8array_tools_1.toHex)(signature.signature) + '>';
209
+ });
210
+ const expandedKnownsMap = { ...preimageMap, ...expandedSignatureMap };
211
+ const knowns = Object.keys(expandedKnownsMap);
212
+ //satisfier verifies again internally whether expandedKnownsMap with given knowns is sane
213
+ const { nonMalleableSats } = (0, miniscript_1.satisfier)(expandedMiniscript, {
214
+ knowns,
215
+ tapscript
216
+ });
217
+ if (!Array.isArray(nonMalleableSats) || !nonMalleableSats[0])
218
+ throw new Error(`Error: unresolvable miniscript ${expandedMiniscript}`);
219
+ let sat;
220
+ if (!timeConstraints) {
221
+ sat = nonMalleableSats[0];
222
+ }
223
+ else {
224
+ sat = nonMalleableSats.find(nonMalleableSat => nonMalleableSat.nSequence === timeConstraints.nSequence &&
225
+ nonMalleableSat.nLockTime === timeConstraints.nLockTime);
226
+ if (sat === undefined) {
227
+ throw new Error(`Error: unresolvable miniscript ${expandedMiniscript}. Could not find solutions for sequence ${timeConstraints.nSequence} & locktime=${timeConstraints.nLockTime}. Signatures are applied to a hash that depends on sequence and locktime. Did you provide all the signatures wrt the signers keys declared and include all preimages?`);
228
+ }
229
+ }
230
+ //substitute signatures and preimages:
231
+ let expandedAsm = sat.asm;
232
+ //replace in expandedAsm all the <sig(@0)> and <sha256_preimage(6c...33)>
233
+ //to <304...01> and <107...5f> ...
234
+ for (const search in expandedKnownsMap) {
235
+ const replace = expandedKnownsMap[search];
236
+ if (!replace || replace === '<>')
237
+ throw new Error(`Error: invalid expandedKnownsMap`);
238
+ expandedAsm = expandedAsm.replaceAll(search, replace);
239
+ }
240
+ const scriptSatisfaction = scriptLib.fromASM(substituteAsm({ expandedAsm, expansionMap, scriptLib }));
241
+ return {
242
+ scriptSatisfaction,
243
+ nLockTime: sat.nLockTime,
244
+ nSequence: sat.nSequence
245
+ };
246
+ }
247
+ /**
248
+ *
249
+ * Use this function instead of bitcoinjs-lib's equivalent `script.number.encode`
250
+ * when encoding numbers to be compiled with `fromASM` to avoid problems.
251
+ *
252
+ * Motivation:
253
+ *
254
+ * Numbers in Bitcoin assembly code are represented in hex and in Little Endian.
255
+ * Decimal: 32766 - Big endian: 0x7FFE - Little Endian: 0xFE7F.
256
+ *
257
+ * This function takes an integer and encodes it so that bitcoinjs-lib `fromASM`
258
+ * can compile it. This is basically what bitcoinjs-lib's `script.number.encode`
259
+ * does.
260
+ *
261
+ * Note that `fromASM` already converts integers from 1 to 16 to
262
+ * OP_1 ... OP_16 {@link https://github.com/bitcoinjs/bitcoinjs-lib/blob/59b21162a2c4645c64271ca004c7a3755a3d72fb/src/script.js#L33 here}.
263
+ * This is done in Bitcoin to save some bits.
264
+ *
265
+ * Neither this function nor `script.number.encode` convert numbers to
266
+ * their op code equivalent since this is done later in `fromASM`.
267
+ *
268
+ * Both functions simply convert numbers to Little Endian.
269
+ *
270
+ * However, the `0` number is an edge case that we specially handle with this
271
+ * function.
272
+ *
273
+ * bitcoinjs-lib's `scriptLib.number.encode(0)` produces an empty array.
274
+ * This is what the Bitcoin interpreter does and it is what `script.number.encode` was
275
+ * implemented to do.
276
+ *
277
+ * The problem is `scriptLib.number.encode(0).toString('hex')` produces an
278
+ * empty string and thus it should not be used to serialize number zero before `fromASM`.
279
+ *
280
+ * A zero should produce the OP_0 ASM symbolic code (corresponding to a `0` when
281
+ * compiled).
282
+ *
283
+ * So, this function will produce a string in hex format in Little Endian
284
+ * encoding for integers not equal to `0` and it will return `OP_0` for `0`.
285
+ *
286
+ * Read more about the this {@link https://github.com/bitcoinjs/bitcoinjs-lib/issues/1799#issuecomment-1122591738 here}.
287
+ *
288
+ * Use it in combination with `fromASM` like this:
289
+ *
290
+ * ```javascript
291
+ * //To produce "0 1 OP_ADD":
292
+ * fromASM(
293
+ * `${numberEncodeAsm(0)} ${numberEncodeAsm(1)} OP_ADD`
294
+ * .trim().replace(/\s+/g, ' ')
295
+ * )
296
+ * ```
297
+ *
298
+ * @param {number} number An integer.
299
+ * @returns {string} Returns `"OP_0"` for `number === 0` and a hex string representing other numbers in Little Endian encoding.
300
+ */
301
+ function numberEncodeAsm(number, scriptLib) {
302
+ if (Number.isSafeInteger(number) === false) {
303
+ throw new Error(`Error: invalid number ${number}`);
304
+ }
305
+ if (number === 0) {
306
+ return 'OP_0';
307
+ }
308
+ else
309
+ return (0, uint8array_tools_1.toHex)(scriptLib.number.encode(number));
310
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Resolves all multipath tuple segments (for example `/<0;1>/*`) in lockstep
3
+ * using the provided `change` value.
4
+ *
5
+ * - `/**` is first canonicalized to `/<0;1>/*`.
6
+ * - All tuples in the descriptor must have the same cardinality.
7
+ * - Tuple values must be strictly increasing decimal numbers.
8
+ * - `change` must match one of the values in each tuple.
9
+ */
10
+ export declare function resolveMultipathDescriptor({ descriptor, change }: {
11
+ descriptor: string;
12
+ change?: number;
13
+ }): string;
@@ -0,0 +1,76 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveMultipathDescriptor = resolveMultipathDescriptor;
4
+ /**
5
+ * Replaces the receive/change shorthand `/**` with its canonical multipath
6
+ * representation `/<0;1>/*`.
7
+ */
8
+ function expandReceiveChangeShorthand(descriptor) {
9
+ return descriptor.replace(/\/\*\*/g, '/<0;1>/*');
10
+ }
11
+ /**
12
+ * Parses a multipath tuple body from `<...>`.
13
+ *
14
+ * This implementation intentionally accepts only decimal numbers (no hardened
15
+ * suffixes) and enforces strict left-to-right increase as a safety feature to
16
+ * catch likely human errors such as `<1;0>`.
17
+ */
18
+ function parseMultipathTuple(tupleBody) {
19
+ const parts = tupleBody.split(';');
20
+ if (parts.length < 2)
21
+ throw new Error(`Error: multipath tuple must contain at least 2 values, got <${tupleBody}>`);
22
+ const values = parts.map(part => {
23
+ if (!/^(0|[1-9]\d*)$/.test(part))
24
+ throw new Error(`Error: multipath tuple values must be decimal numbers, got <${tupleBody}>`);
25
+ const value = Number(part);
26
+ if (!Number.isSafeInteger(value))
27
+ throw new Error(`Error: multipath tuple value overflow, got <${tupleBody}>`);
28
+ return value;
29
+ });
30
+ for (let i = 1; i < values.length; i++) {
31
+ const prev = values[i - 1];
32
+ const current = values[i];
33
+ if (prev === undefined || current === undefined)
34
+ throw new Error(`Error: invalid multipath tuple <${tupleBody}>`);
35
+ if (current <= prev)
36
+ throw new Error(`Error: multipath tuple values must be strictly increasing from left to right, got <${tupleBody}>`);
37
+ }
38
+ return values;
39
+ }
40
+ /**
41
+ * Resolves all multipath tuple segments (for example `/<0;1>/*`) in lockstep
42
+ * using the provided `change` value.
43
+ *
44
+ * - `/**` is first canonicalized to `/<0;1>/*`.
45
+ * - All tuples in the descriptor must have the same cardinality.
46
+ * - Tuple values must be strictly increasing decimal numbers.
47
+ * - `change` must match one of the values in each tuple.
48
+ */
49
+ function resolveMultipathDescriptor({ descriptor, change }) {
50
+ const canonicalDescriptor = expandReceiveChangeShorthand(descriptor);
51
+ const tupleMatches = Array.from(canonicalDescriptor.matchAll(/\/<([^<>]+)>/g), match => ({
52
+ token: match[0],
53
+ tupleBody: match[1],
54
+ values: parseMultipathTuple(match[1])
55
+ }));
56
+ if (tupleMatches.length === 0)
57
+ return canonicalDescriptor;
58
+ if (change === undefined)
59
+ throw new Error(`Error: change was not provided for multipath descriptor`);
60
+ if (!Number.isInteger(change) || change < 0)
61
+ throw new Error(`Error: invalid change ${change}`);
62
+ const tupleSize = tupleMatches[0]?.values.length;
63
+ if (!tupleSize)
64
+ throw new Error(`Error: invalid multipath tuple`);
65
+ for (const tupleMatch of tupleMatches) {
66
+ if (tupleMatch.values.length !== tupleSize)
67
+ throw new Error(`Error: all multipath tuples must have the same number of options`);
68
+ }
69
+ let resolvedDescriptor = canonicalDescriptor;
70
+ for (const tupleMatch of tupleMatches) {
71
+ if (!tupleMatch.values.includes(change))
72
+ throw new Error(`Error: change ${change} not found in multipath tuple <${tupleMatch.tupleBody}>`);
73
+ resolvedDescriptor = resolvedDescriptor.replaceAll(tupleMatch.token, `/${change}`);
74
+ }
75
+ return resolvedDescriptor;
76
+ }
@@ -0,0 +1,3 @@
1
+ import { type Network } from './networks';
2
+ export declare function isBitcoinMainnet(network: Network): boolean;
3
+ export declare function coinTypeFromNetwork(network: Network): 0 | 1;
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isBitcoinMainnet = isBitcoinMainnet;
4
+ exports.coinTypeFromNetwork = coinTypeFromNetwork;
5
+ const networks_1 = require("./networks");
6
+ function isBitcoinMainnet(network) {
7
+ return (network.bech32 === networks_1.networks.bitcoin.bech32 &&
8
+ network.bip32.public === networks_1.networks.bitcoin.bip32.public &&
9
+ network.bip32.private === networks_1.networks.bitcoin.bip32.private &&
10
+ network.pubKeyHash === networks_1.networks.bitcoin.pubKeyHash &&
11
+ network.scriptHash === networks_1.networks.bitcoin.scriptHash &&
12
+ network.wif === networks_1.networks.bitcoin.wif);
13
+ }
14
+ function coinTypeFromNetwork(network) {
15
+ return isBitcoinMainnet(network) ? 0 : 1;
16
+ }
@@ -0,0 +1,16 @@
1
+ export interface Network {
2
+ messagePrefix: string;
3
+ bech32: string;
4
+ bip32: {
5
+ public: number;
6
+ private: number;
7
+ };
8
+ pubKeyHash: number;
9
+ scriptHash: number;
10
+ wif: number;
11
+ }
12
+ export declare const networks: {
13
+ bitcoin: Network;
14
+ testnet: Network;
15
+ regtest: Network;
16
+ };
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ // Copyright (c) 2026 Jose-Luis Landabaso - https://bitcoinerlab.com
3
+ // Distributed under the MIT software license
4
+ Object.defineProperty(exports, "__esModule", { value: true });
5
+ exports.networks = void 0;
6
+ exports.networks = {
7
+ bitcoin: {
8
+ messagePrefix: '\x18Bitcoin Signed Message:\n',
9
+ bech32: 'bc',
10
+ bip32: { public: 0x0488b21e, private: 0x0488ade4 },
11
+ pubKeyHash: 0x00,
12
+ scriptHash: 0x05,
13
+ wif: 0x80
14
+ },
15
+ testnet: {
16
+ messagePrefix: '\x18Bitcoin Signed Message:\n',
17
+ bech32: 'tb',
18
+ bip32: { public: 0x043587cf, private: 0x04358394 },
19
+ pubKeyHash: 0x6f,
20
+ scriptHash: 0xc4,
21
+ wif: 0xef
22
+ },
23
+ regtest: {
24
+ messagePrefix: '\x18Bitcoin Signed Message:\n',
25
+ bech32: 'bcrt',
26
+ bip32: { public: 0x043587cf, private: 0x04358394 },
27
+ pubKeyHash: 0x6f,
28
+ scriptHash: 0xc4,
29
+ wif: 0xef
30
+ }
31
+ };
@@ -0,0 +1,7 @@
1
+ export declare function splitTopLevelComma({ expression, onError }: {
2
+ expression: string;
3
+ onError: (expression: string) => Error;
4
+ }): {
5
+ left: string;
6
+ right: string;
7
+ } | null;
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.splitTopLevelComma = splitTopLevelComma;
4
+ function splitTopLevelComma({ expression, onError }) {
5
+ let braceDepth = 0;
6
+ let parenDepth = 0;
7
+ let commaIndex = -1;
8
+ for (let i = 0; i < expression.length; i++) {
9
+ const char = expression[i];
10
+ if (!char)
11
+ continue;
12
+ if (char === '{') {
13
+ braceDepth++;
14
+ }
15
+ else if (char === '}') {
16
+ if (braceDepth === 0)
17
+ throw onError(expression);
18
+ braceDepth--;
19
+ }
20
+ else if (char === '(') {
21
+ //Track miniscript argument lists so we don't split on commas inside them.
22
+ parenDepth++;
23
+ }
24
+ else if (char === ')') {
25
+ if (parenDepth === 0)
26
+ throw onError(expression);
27
+ parenDepth--;
28
+ }
29
+ else if (char === ',') {
30
+ if (braceDepth === 0 && parenDepth === 0) {
31
+ if (commaIndex !== -1)
32
+ throw onError(expression);
33
+ commaIndex = i;
34
+ }
35
+ }
36
+ }
37
+ if (braceDepth !== 0 || parenDepth !== 0)
38
+ throw onError(expression);
39
+ if (commaIndex === -1)
40
+ return null;
41
+ const left = expression.slice(0, commaIndex).trim();
42
+ const right = expression.slice(commaIndex + 1).trim();
43
+ if (!left || !right)
44
+ throw onError(expression);
45
+ return { left, right };
46
+ }