@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.
@@ -0,0 +1,167 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MAX_TAPTREE_DEPTH = void 0;
4
+ exports.assertTapTreeDepth = assertTapTreeDepth;
5
+ exports.collectTapTreeLeaves = collectTapTreeLeaves;
6
+ exports.selectTapLeafCandidates = selectTapLeafCandidates;
7
+ exports.parseTapTreeExpression = parseTapTreeExpression;
8
+ // NOTE: This uses an internal bitcoinjs-lib module. Consider adding a local wrapper.
9
+ const bitcoinjs_lib_internals_1 = require("./bitcoinjs-lib-internals");
10
+ const uint8array_tools_1 = require("uint8array-tools");
11
+ const parseUtils_1 = require("./parseUtils");
12
+ // See BIP341 control block limits and Sipa's Miniscript "Resource limitations":
13
+ // https://bitcoin.sipa.be/miniscript/
14
+ // Taproot script path depth is encoded in the control block as 32-byte hashes,
15
+ // with consensus max depth 128.
16
+ exports.MAX_TAPTREE_DEPTH = 128;
17
+ function tapTreeMaxDepth(tapTree, depth = 0) {
18
+ if ('expression' in tapTree)
19
+ return depth;
20
+ return Math.max(tapTreeMaxDepth(tapTree.left, depth + 1), tapTreeMaxDepth(tapTree.right, depth + 1));
21
+ }
22
+ function assertTapTreeDepth(tapTree) {
23
+ const maxDepth = tapTreeMaxDepth(tapTree);
24
+ if (maxDepth > exports.MAX_TAPTREE_DEPTH)
25
+ throw new Error(`Error: taproot tree depth is too large, ${maxDepth} is larger than ${exports.MAX_TAPTREE_DEPTH}`);
26
+ }
27
+ /**
28
+ * Collects taproot leaf metadata with depth from a tree.
29
+ * Traversal is left-first, following the order of `{left,right}` in the
30
+ * expression so tie-breaks are deterministic.
31
+ *
32
+ * Example tree:
33
+ * ```
34
+ * {pk(A),{pk(B),pk(C)}}
35
+ * ```
36
+ * Visual shape:
37
+ * ```
38
+ * root
39
+ * / \
40
+ * pk(A) branch
41
+ * / \
42
+ * pk(B) pk(C)
43
+ * ```
44
+ * Collected leaves with depth:
45
+ * ```
46
+ * [
47
+ * { leaf: pk(A), depth: 1 },
48
+ * { leaf: pk(B), depth: 2 },
49
+ * { leaf: pk(C), depth: 2 }
50
+ * ]
51
+ * ```
52
+ */
53
+ function collectTapTreeLeaves(tapTreeInfo) {
54
+ const leaves = [];
55
+ const walk = (node, depth) => {
56
+ if ('expression' in node) {
57
+ leaves.push({ leaf: node, depth });
58
+ return;
59
+ }
60
+ walk(node.left, depth + 1);
61
+ walk(node.right, depth + 1);
62
+ };
63
+ walk(tapTreeInfo, 0);
64
+ return leaves;
65
+ }
66
+ function computeTapLeafHash(leaf) {
67
+ return (0, bitcoinjs_lib_internals_1.tapleafHash)({ output: leaf.tapScript, version: leaf.version });
68
+ }
69
+ function normalizeExpressionForMatch(expression) {
70
+ return expression.replace(/\s+/g, '');
71
+ }
72
+ /**
73
+ * Resolves taproot leaf candidates based on an optional selector.
74
+ *
75
+ * If `tapLeaf` is undefined, all leaves are returned for auto-selection.
76
+ * If `tapLeaf` is bytes, it is treated as a tapleaf hash and must match
77
+ * exactly one leaf.
78
+ * If `tapLeaf` is a string, it is treated as a raw taproot leaf expression
79
+ * (not expanded). Matching is whitespace-insensitive. If the expression appears
80
+ * more than once, this function throws an error.
81
+ *
82
+ * Example:
83
+ * ```
84
+ * const candidates = selectTapLeafCandidates({ tapTreeInfo, tapLeaf });
85
+ * // tapLeaf can be undefined, bytes (tapleaf hash) or a leaf expression:
86
+ * // f.ex.: 'pk(03bb...)'
87
+ * ```
88
+ */
89
+ function selectTapLeafCandidates({ tapTreeInfo, tapLeaf }) {
90
+ const leaves = collectTapTreeLeaves(tapTreeInfo).map(({ leaf, depth }) => ({
91
+ leaf,
92
+ depth,
93
+ tapLeafHash: computeTapLeafHash(leaf)
94
+ }));
95
+ if (tapLeaf === undefined)
96
+ return leaves;
97
+ if (tapLeaf instanceof Uint8Array) {
98
+ const match = leaves.find(entry => (0, uint8array_tools_1.compare)(entry.tapLeafHash, tapLeaf) === 0);
99
+ if (!match)
100
+ throw new Error(`Error: tapleaf hash not found in tapTreeInfo`);
101
+ return [match];
102
+ }
103
+ const normalizedSelector = normalizeExpressionForMatch(tapLeaf);
104
+ const matches = leaves.filter(entry => normalizeExpressionForMatch(entry.leaf.expression) === normalizedSelector);
105
+ if (matches.length === 0)
106
+ throw new Error(`Error: taproot leaf expression not found in tapTreeInfo: ${tapLeaf}`);
107
+ if (matches.length > 1)
108
+ throw new Error(`Error: taproot leaf expression is ambiguous in tapTreeInfo: ${tapLeaf}`);
109
+ return matches;
110
+ }
111
+ function tapTreeError(expression) {
112
+ return new Error(`Error: invalid taproot tree expression: ${expression}`);
113
+ }
114
+ /**
115
+ * Splits the inner tree expression of a branch into left/right parts.
116
+ * The input must be the contents inside `{}` (no outer braces).
117
+ * Example: `pk(@0),{pk(@1),pk(@2)}` => left: `pk(@0)`, right: `{pk(@1),pk(@2)}`.
118
+ */
119
+ function splitTapTreeExpression(expression) {
120
+ const result = (0, parseUtils_1.splitTopLevelComma)({ expression, onError: tapTreeError });
121
+ if (!result)
122
+ throw tapTreeError(expression);
123
+ return result;
124
+ }
125
+ /**
126
+ * Parses a single taproot tree node expression.
127
+ *
128
+ * Note: the field name is intentionally generic (`expression`) because taproot
129
+ * leaves can contain either miniscript fragments (e.g. `pk(...)`) or
130
+ * descriptor-level script expressions (e.g. `sortedmulti_a(...)`).
131
+ * Examples:
132
+ * - `pk(@0)` => { expression: 'pk(@0)' }
133
+ * - `{pk(@0),pk(@1)}` => { left: { expression: 'pk(@0)' }, right: { expression: 'pk(@1)' } }
134
+ * - `{pk(@0),{pk(@1),pk(@2)}}` =>
135
+ * {
136
+ * left: { expression: 'pk(@0)' },
137
+ * right: { left: { expression: 'pk(@1)' }, right: { expression: 'pk(@2)' } }
138
+ * }
139
+ */
140
+ function parseTapTreeNode(expression) {
141
+ const trimmedExpression = expression.trim();
142
+ if (!trimmedExpression)
143
+ throw tapTreeError(expression);
144
+ if (trimmedExpression.startsWith('{')) {
145
+ if (!trimmedExpression.endsWith('}'))
146
+ throw tapTreeError(expression);
147
+ const inner = trimmedExpression.slice(1, -1).trim();
148
+ if (!inner)
149
+ throw tapTreeError(expression);
150
+ const { left, right } = splitTapTreeExpression(inner);
151
+ return {
152
+ left: parseTapTreeNode(left),
153
+ right: parseTapTreeNode(right)
154
+ };
155
+ }
156
+ if (trimmedExpression.includes('{') || trimmedExpression.includes('}'))
157
+ throw tapTreeError(expression);
158
+ return { expression: trimmedExpression };
159
+ }
160
+ function parseTapTreeExpression(expression) {
161
+ const trimmed = expression.trim();
162
+ if (!trimmed)
163
+ throw tapTreeError(expression);
164
+ const tapTree = parseTapTreeNode(trimmed);
165
+ assertTapTreeDepth(tapTree);
166
+ return tapTree;
167
+ }
package/dist/types.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import type { ECPairInterface } from 'ecpair';
2
2
  import type { BIP32Interface } from 'bip32';
3
3
  import type { Payment, Network } from 'bitcoinjs-lib';
4
+ import type { TapTreeNode, TapTreeInfoNode } from './tapTree';
4
5
  /**
5
6
  * Preimage
6
7
  * See {@link Output}
@@ -24,14 +25,14 @@ export type TimeConstraints = {
24
25
  nSequence: number | undefined;
25
26
  };
26
27
  /**
27
- * See {@link _Internal_.ParseKeyExpression | ParseKeyExpression}.
28
+ * See {@link _Internal_.KeyExpressionParser | KeyExpressionParser}.
28
29
  */
29
30
  export type KeyInfo = {
30
31
  keyExpression: string;
31
- pubkey?: Buffer;
32
+ pubkey?: Uint8Array;
32
33
  ecpair?: ECPairInterface;
33
34
  bip32?: BIP32Interface;
34
- masterFingerprint?: Buffer;
35
+ masterFingerprint?: Uint8Array;
35
36
  originPath?: string;
36
37
  keyPath?: string;
37
38
  path?: string;
@@ -127,14 +128,59 @@ export type Expansion = {
127
128
  * expression.
128
129
  */
129
130
  expandedMiniscript?: string;
131
+ /**
132
+ * The taproot tree expression, if any. Only defined for `tr(KEY, TREE)`.
133
+ * Example: `{pk(02aa...),{pk(03bb...),pk(02cc...)}}`.
134
+ */
135
+ tapTreeExpression?: string;
136
+ /**
137
+ * The parsed taproot tree, if any. Only defined for `tr(KEY, TREE)`.
138
+ * Example:
139
+ * ```
140
+ * {
141
+ * left: { expression: 'pk(02aa...)' },
142
+ * right: {
143
+ * left: { expression: 'pk(03bb...)' },
144
+ * right: { expression: 'pk(02cc...)' }
145
+ * }
146
+ * }
147
+ * ```
148
+ */
149
+ tapTree?: TapTreeNode;
150
+ /**
151
+ * The compiled taproot tree metadata, if any. Only defined for `tr(KEY, TREE)`.
152
+ * Same as tapTree, but each leaf includes:
153
+ * - `expandedExpression`: descriptor-level expanded leaf expression
154
+ * - optional `expandedMiniscript`: miniscript-expanded leaf (when applicable)
155
+ * - key expansion map
156
+ * - compiled tapscript (`tapScript`)
157
+ * - leaf version.
158
+ *
159
+ * Note: `@i` placeholders are scoped per leaf, since each leaf is expanded
160
+ * and satisfied independently.
161
+ *
162
+ * Example:
163
+ * ```
164
+ * {
165
+ * left: {
166
+ * expression: 'pk(02aa...)',
167
+ * expandedExpression: 'pk(@0)',
168
+ * expandedMiniscript: 'pk(@0)',
169
+ * expansionMap: ExpansionMap;
170
+ * tapScript: Uint8Array;
171
+ * version: number;
172
+ * },
173
+ * right: ....
174
+ */
175
+ tapTreeInfo?: TapTreeInfoNode;
130
176
  /**
131
177
  * The redeem script for the descriptor, if applicable.
132
178
  */
133
- redeemScript?: Buffer;
179
+ redeemScript?: Uint8Array;
134
180
  /**
135
181
  * The witness script for the descriptor, if applicable.
136
182
  */
137
- witnessScript?: Buffer;
183
+ witnessScript?: Uint8Array;
138
184
  /**
139
185
  * Whether the descriptor is a ranged-descriptor.
140
186
  */
@@ -170,7 +216,7 @@ export type Expansion = {
170
216
  *
171
217
  * See {@link KeyInfo} for the complete list of elements retrieved by this function.
172
218
  */
173
- export interface ParseKeyExpression {
219
+ export interface KeyExpressionParser {
174
220
  (params: {
175
221
  keyExpression: string;
176
222
  /**
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@bitcoinerlab/descriptors",
3
3
  "description": "This library parses and creates Bitcoin Miniscript Descriptors and generates Partially Signed Bitcoin Transactions (PSBTs). It provides PSBT finalizers and signers for single-signature, BIP32 and Hardware Wallets.",
4
4
  "homepage": "https://github.com/bitcoinerlab/descriptors",
5
- "version": "2.3.6",
5
+ "version": "3.0.1",
6
6
  "author": "Jose-Luis Landabaso",
7
7
  "license": "MIT",
8
8
  "repository": {
@@ -36,43 +36,43 @@
36
36
  "build": "npm run build:src && npm run build:test",
37
37
  "lint": "./node_modules/@bitcoinerlab/configs/scripts/lint.sh",
38
38
  "ensureTester": "./node_modules/@bitcoinerlab/configs/scripts/ensureTester.sh",
39
- "test:integration:soft": "npm run ensureTester && node test/integration/standardOutputs.js && echo \"\n\n\" && node test/integration/miniscript.js && echo \"\n\n\" && node test/integration/sortedmulti.js",
40
- "test:integration:ledger": "npm run ensureTester && node test/integration/ledger.js",
41
- "test:integration:deprecated": "npm run ensureTester && node test/integration/standardOutputs-deprecated.js && echo \"\n\n\" && node test/integration/miniscript-deprecated.js && echo \"\n\n\" && node test/integration/ledger-deprecated.js",
39
+ "test:integration:soft": "npm run ensureTester && node test/integration/standardOutputs.js && echo \"\n\n\" && node test/integration/miniscript.js && echo \"\n\n\" && node test/integration/sortedmulti.js && echo \"\n\n\" && node test/integration/taproot.js",
40
+ "test:integration:ledger": "npm run ensureTester && node test/integration/ledgerTaproot.js && echo \"\n\n\" && node test/integration/ledger.js",
42
41
  "test:unit": "jest",
43
42
  "test": "npm run lint && npm run build && npm run test:unit && npm run test:integration:soft",
44
43
  "testledger": "npm run lint && npm run build && npm run test:integration:ledger",
45
- "prepublishOnly": "npm run test && echo \"\n\n\" && npm run test:integration:deprecated && npm run test:integration:ledger"
44
+ "prepublishOnly": "npm run test && echo \"\n\n\" && npm run test:integration:ledger"
46
45
  },
47
46
  "files": [
48
47
  "dist"
49
48
  ],
50
49
  "peerDependencies": {
51
- "ledger-bitcoin": "^0.2.2"
50
+ "@ledgerhq/ledger-bitcoin": "^0.3.0"
52
51
  },
53
52
  "peerDependenciesMeta": {
54
- "ledger-bitcoin": {
53
+ "@ledgerhq/ledger-bitcoin": {
55
54
  "optional": true
56
55
  }
57
56
  },
58
57
  "devDependencies": {
59
58
  "@bitcoinerlab/configs": "^2.0.0",
60
- "@ledgerhq/hw-transport-node-hid": "^6.27.12",
59
+ "@bitcoinerlab/miniscript-policies": "^1.0.0",
60
+ "@ledgerhq/hw-transport-node-hid": "^6.30.0",
61
+ "@ledgerhq/ledger-bitcoin": "^0.3.0",
61
62
  "@types/lodash.memoize": "^4.1.9",
62
63
  "bip39": "^3.0.4",
63
64
  "bip65": "^1.0.3",
64
65
  "bip68": "^1.0.4",
65
- "ledger-bitcoin": "^0.2.2",
66
66
  "regtest-client": "^0.2.1",
67
67
  "yargs": "^17.7.2"
68
68
  },
69
69
  "dependencies": {
70
- "@bitcoinerlab/miniscript": "^1.4.3",
70
+ "@bitcoinerlab/miniscript": "^2.0.0",
71
71
  "@bitcoinerlab/secp256k1": "^1.2.0",
72
72
  "bip32": "^4.0.0",
73
- "bitcoinjs-lib": "^6.1.7",
74
- "ecpair": "^2.1.0",
73
+ "bitcoinjs-lib": "^7.0.1",
74
+ "ecpair": "^3.0.1",
75
75
  "lodash.memoize": "^4.1.2",
76
- "varuint-bitcoin": "^1.1.2"
76
+ "varuint-bitcoin": "^2.0.0"
77
77
  }
78
78
  }