@bitcoinerlab/descriptors 0.2.2 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +60 -2
- package/dist/descriptors.d.ts +2 -1
- package/dist/descriptors.js +313 -253
- package/dist/keyExpressions.js +10 -8
- package/dist/miniscript.js +6 -3
- package/dist/psbt.js +10 -5
- package/dist/types.d.ts +21 -1
- package/package.json +6 -5
package/README.md
CHANGED
|
@@ -79,8 +79,8 @@ constructor({
|
|
|
79
79
|
// to denote an arbitrary index.
|
|
80
80
|
index, // The descriptor's index in the case of a range descriptor
|
|
81
81
|
// (must be an integer >= 0).
|
|
82
|
-
checksumRequired = false
|
|
83
|
-
|
|
82
|
+
checksumRequired = false // Optional flag indicating if the descriptor is
|
|
83
|
+
// required to include a checksum. Defaults to false.
|
|
84
84
|
allowMiniscriptInP2SH = false, // Flag indicating if this instance can parse
|
|
85
85
|
// and generate script satisfactions for
|
|
86
86
|
// sh(miniscript) top-level expressions of
|
|
@@ -131,6 +131,64 @@ Here, `index` is the `inputIndex` obtained from the `updatePsbt()` method and `p
|
|
|
131
131
|
|
|
132
132
|
For further information on using the Descriptor class, refer to the [comprehensive guides](https://bitcoinerlab.com/guides) that offer explanations and playgrounds to help learn the module. Additionally, a [Stack Exchange answer](https://bitcoin.stackexchange.com/a/118036/89665) provides a focused explanation on the constructor, specifically the `signersPubKeys` parameter, and the usage of `updatePsbt`, `finalizePsbt`, `getAddress`, and `getScriptPubKey`.
|
|
133
133
|
|
|
134
|
+
#### Tip: Parsing descriptors without instantiating a class
|
|
135
|
+
|
|
136
|
+
`DescriptorsFactory` provides a convenient `expand()` function that allows you to parse a descriptor expression without the need to instantiate the `Descriptor` class. This function can be used as follows:
|
|
137
|
+
|
|
138
|
+
```javascript
|
|
139
|
+
const { expand } = descriptors.DescriptorsFactory(secp256k1);
|
|
140
|
+
const result = expand({
|
|
141
|
+
expression: 'sh(wsh(andor(pk(0252972572d465d016d4c501887b8df303eee3ed602c056b1eb09260dfa0da0ab2),older(8640),pk([d34db33f/49'/0'/0']tpubDCdxmvzJ5QBjTN8oCjjyT2V58AyZvA1fkmCeZRC75QMoaHcVP2m45Bv3hmnR7ttAwkb2UNYyoXdHVt4gwBqRrJqLUU2JrM43HippxiWpHra/1/2/3/4/*))))',
|
|
142
|
+
network: networks.testnet, // One of bitcoinjs-lib `networks`
|
|
143
|
+
// (https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/src/networks.js)
|
|
144
|
+
// or another one with the same interface.
|
|
145
|
+
// Optional (defaults to bitcoin mainnet).
|
|
146
|
+
allowMiniscriptInP2SH: true, // Optional flag to allow miniscript in P2SH.
|
|
147
|
+
// Defaults to false.
|
|
148
|
+
index, // Optional. The descriptor's index in the case of a range descriptor
|
|
149
|
+
// (must be an integer >= 0). If not set for ranged descriptors, then
|
|
150
|
+
// the function will return an expansionMap with ranged keyPaths and
|
|
151
|
+
// won't compute Payment or scripts.
|
|
152
|
+
checksumRequired = false // Optional flag indicating if the descriptor is
|
|
153
|
+
// required to include a checksum. Defaults to false.
|
|
154
|
+
});
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
The `expand()` function returns an object with the following properties:
|
|
158
|
+
|
|
159
|
+
- `payment: Payment | undefined`: The corresponding [bitcoinjs-lib Payment](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/ts_src/payments/index.ts) for the provided expression, if applicable.
|
|
160
|
+
- `expandedExpression: string | undefined`: The expanded descriptor expression.
|
|
161
|
+
- `miniscript: string | undefined`: The extracted miniscript from the expression, if any.
|
|
162
|
+
- `expansionMap: ExpansionMap | undefined`: A map of key expressions in the descriptor to their corresponding expanded keys.
|
|
163
|
+
- `isSegwit: boolean | undefined`: A boolean indicating whether the descriptor represents a SegWit script.
|
|
164
|
+
- `expandedMiniscript: string | undefined`: The expanded miniscript, if any.
|
|
165
|
+
- `redeemScript: Buffer | undefined`: The redeem script for the descriptor, if applicable.
|
|
166
|
+
- `witnessScript: Buffer | undefined`: The witness script for the descriptor, if applicable.
|
|
167
|
+
- `isRanged: boolean` : Whether the expression represents a ranged descriptor.
|
|
168
|
+
- `canonicalExpression` : This is the preferred or authoritative representation of the descriptor expression. It standardizes the descriptor by replacing indexes on wildcards and eliminating checksums.
|
|
169
|
+
|
|
170
|
+
For the example expression provided, the `expandedExpression` and a portion of the `expansionMap` would be as follows:
|
|
171
|
+
|
|
172
|
+
```javascript
|
|
173
|
+
// expression: 'sh(wsh(andor(pk(0252972572d465d016d4c501887b8df303eee3ed602c056b1eb09260dfa0da0ab2),older(8640),pk([d34db33f/49'/0'/0']tpubDCdxmvzJ5QBjTN8oCjjyT2V58AyZvA1fkmCeZRC75QMoaHcVP2m45Bv3hmnR7ttAwkb2UNYyoXdHVt4gwBqRrJqLUU2JrM43HippxiWpHra/1/2/3/4/*))))'
|
|
174
|
+
|
|
175
|
+
expandedExpression: 'sh(wsh(andor(pk(@0),older(8640),pk(@1))))',
|
|
176
|
+
expansionMap: {
|
|
177
|
+
'@0': {
|
|
178
|
+
keyExpression:
|
|
179
|
+
'0252972572d465d016d4c501887b8df303eee3ed602c056b1eb09260dfa0da0ab2'
|
|
180
|
+
},
|
|
181
|
+
'@1': {
|
|
182
|
+
keyExpression:
|
|
183
|
+
"[d34db33f/49'/0'/0']tpubDCdxmvzJ5QBjTN8oCjjyT2V58AyZvA1fkmCeZRC75QMoaHcVP2m45Bv3hmnR7ttAwkb2UNYyoXdHVt4gwBqRrJqLUU2JrM43HippxiWpHra/1/2/3/4/*",
|
|
184
|
+
keyPath: '/1/2/3/4/*',
|
|
185
|
+
originPath: "/49'/0'/0'",
|
|
186
|
+
path: "m/49'/0'/0'/1/2/3/4/*",
|
|
187
|
+
// Other relevant properties returned: `pubkey`, `ecpair` & `bip32` interfaces, `masterFingerprint`, etc.
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
134
192
|
### keyExpressions and scriptExpressions
|
|
135
193
|
|
|
136
194
|
This library also includes a set of function helpers that facilitate the generation of the `expression` parameter in the constructor of the `Descriptor` class. These helpers are located under the `scriptExpressions` module, which can be imported using the following statement:
|
package/dist/descriptors.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { BIP32API } from 'bip32';
|
|
2
2
|
import { ECPairAPI } from 'ecpair';
|
|
3
|
-
import type { TinySecp256k1Interface, ParseKeyExpression, DescriptorInterfaceConstructor } from './types';
|
|
3
|
+
import type { TinySecp256k1Interface, ParseKeyExpression, Expand, DescriptorInterfaceConstructor } from './types';
|
|
4
4
|
/**
|
|
5
5
|
* Builds the functions needed to operate with descriptors using an external elliptic curve (ecc) library.
|
|
6
6
|
* @param {Object} ecc - an object containing elliptic curve operations, such as [tiny-secp256k1](https://github.com/bitcoinjs/tiny-secp256k1) or [@bitcoinerlab/secp256k1](https://github.com/bitcoinerlab/secp256k1).
|
|
@@ -10,5 +10,6 @@ export declare function DescriptorsFactory(ecc: TinySecp256k1Interface): {
|
|
|
10
10
|
Descriptor: DescriptorInterfaceConstructor;
|
|
11
11
|
ECPair: ECPairAPI;
|
|
12
12
|
parseKeyExpression: ParseKeyExpression;
|
|
13
|
+
expand: Expand;
|
|
13
14
|
BIP32: BIP32API;
|
|
14
15
|
};
|
package/dist/descriptors.js
CHANGED
|
@@ -55,7 +55,7 @@ function countNonPushOnlyOPs(script) {
|
|
|
55
55
|
const decompile = bitcoinjs_lib_1.script.decompile(script);
|
|
56
56
|
if (!decompile)
|
|
57
57
|
throw new Error(`Error: cound not decompile ${script}`);
|
|
58
|
-
return decompile.filter(op => op > bitcoinjs_lib_1.script.OPS['OP_16']).length;
|
|
58
|
+
return decompile.filter(op => typeof op === 'number' && op > bitcoinjs_lib_1.script.OPS['OP_16']).length;
|
|
59
59
|
}
|
|
60
60
|
/*
|
|
61
61
|
* Returns a bare descriptor without checksum and particularized for a certain
|
|
@@ -75,20 +75,20 @@ function evaluate({ expression, checksumRequired, index }) {
|
|
|
75
75
|
throw new Error(`Error: invalid descriptor checksum for ${expression}`);
|
|
76
76
|
}
|
|
77
77
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
if (
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
78
|
+
if (index !== undefined) {
|
|
79
|
+
const mWildcard = evaluatedExpression.match(/\*/g);
|
|
80
|
+
if (mWildcard && mWildcard.length > 0) {
|
|
81
|
+
//From https://github.com/bitcoin/bitcoin/blob/master/doc/descriptors.md
|
|
82
|
+
//To prevent a combinatorial explosion of the search space, if more than
|
|
83
|
+
//one of the multi() key arguments is a BIP32 wildcard path ending in /* or
|
|
84
|
+
//*', the multi() expression only matches multisig scripts with the ith
|
|
85
|
+
//child key from each wildcard path in lockstep, rather than scripts with
|
|
86
|
+
//any combination of child keys from each wildcard path.
|
|
87
|
+
//We extend this reasoning for musig for all cases
|
|
88
|
+
evaluatedExpression = evaluatedExpression.replaceAll('*', index.toString());
|
|
89
|
+
}
|
|
90
|
+
else
|
|
91
|
+
throw new Error(`Error: index passed for non-ranged descriptor: ${expression}`);
|
|
92
92
|
}
|
|
93
93
|
return evaluatedExpression;
|
|
94
94
|
}
|
|
@@ -114,6 +114,273 @@ function DescriptorsFactory(ecc) {
|
|
|
114
114
|
BIP32
|
|
115
115
|
});
|
|
116
116
|
};
|
|
117
|
+
/**
|
|
118
|
+
* Takes a descriptor (expression) and expands it to its corresponding Bitcoin script and other relevant details.
|
|
119
|
+
*
|
|
120
|
+
* @param {Object} params The parameters for the function.
|
|
121
|
+
* @param {string} params.expression The descriptor expression to be expanded.
|
|
122
|
+
* @param {string} [params.loggedExpression] The descriptor expression used for logging error messages. If not provided, defaults to the original expression.
|
|
123
|
+
* @param {Object} [params.network=networks.bitcoin] The Bitcoin network to use. If not provided, defaults to Bitcoin mainnet.
|
|
124
|
+
* @param {boolean} [params.allowMiniscriptInP2SH=false] Flag to allow miniscript in P2SH. If not provided, defaults to false.
|
|
125
|
+
*
|
|
126
|
+
* @returns {Object} An object containing various details about the expanded descriptor:
|
|
127
|
+
* - payment: The corresponding [bitcoinjs-lib Payment](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/ts_src/payments/index.ts) for the provided expression, if applicable.
|
|
128
|
+
* - expandedExpression: The expanded descriptor expression.
|
|
129
|
+
* - miniscript: The extracted miniscript from the expression, if any.
|
|
130
|
+
* - expansionMap: A map of key expressions in the descriptor to their corresponding expanded keys.
|
|
131
|
+
* - isSegwit: A boolean indicating whether the descriptor represents a SegWit script.
|
|
132
|
+
* - expandedMiniscript: The expanded miniscript, if any.
|
|
133
|
+
* - redeemScript: The redeem script for the descriptor, if applicable.
|
|
134
|
+
* - witnessScript: The witness script for the descriptor, if applicable.
|
|
135
|
+
* - isRanged: Whether this expression representas a ranged-descriptor
|
|
136
|
+
* - canonicalExpression: This is the preferred or authoritative
|
|
137
|
+
* representation of the descriptor expression. It standardizes the
|
|
138
|
+
* descriptor by replacing indexes on wildcards and eliminating checksums.
|
|
139
|
+
* This helps ensure consistency and facilitates efficient interpretation and handling by systems or software.
|
|
140
|
+
*
|
|
141
|
+
* @throws {Error} Throws an error if the descriptor cannot be parsed or does not conform to the expected format.
|
|
142
|
+
*/
|
|
143
|
+
const expand = ({ expression, index, checksumRequired = false, network = bitcoinjs_lib_1.networks.bitcoin, allowMiniscriptInP2SH = false }) => {
|
|
144
|
+
let expandedExpression;
|
|
145
|
+
let miniscript;
|
|
146
|
+
let expansionMap;
|
|
147
|
+
let isSegwit;
|
|
148
|
+
let expandedMiniscript;
|
|
149
|
+
let payment;
|
|
150
|
+
let witnessScript;
|
|
151
|
+
let redeemScript;
|
|
152
|
+
const isRanged = expression.indexOf('*') !== -1;
|
|
153
|
+
if (index !== undefined)
|
|
154
|
+
if (!Number.isInteger(index) || index < 0)
|
|
155
|
+
throw new Error(`Error: invalid index ${index}`);
|
|
156
|
+
//Verify and remove checksum (if exists) and
|
|
157
|
+
//particularize range descriptor for index (if desc is range descriptor)
|
|
158
|
+
const canonicalExpression = evaluate({
|
|
159
|
+
expression,
|
|
160
|
+
...(index !== undefined ? { index } : {}),
|
|
161
|
+
checksumRequired
|
|
162
|
+
});
|
|
163
|
+
const isCanonicalRanged = canonicalExpression.indexOf('*') !== -1;
|
|
164
|
+
//addr(ADDR)
|
|
165
|
+
if (canonicalExpression.match(RE.reAddrAnchored)) {
|
|
166
|
+
if (isRanged)
|
|
167
|
+
throw new Error(`Error: addr() cannot be ranged`);
|
|
168
|
+
const matchedAddress = canonicalExpression.match(RE.reAddrAnchored)?.[1]; //[1]-> whatever is found addr(->HERE<-)
|
|
169
|
+
if (!matchedAddress)
|
|
170
|
+
throw new Error(`Error: could not get an address in ${expression}`);
|
|
171
|
+
let output;
|
|
172
|
+
try {
|
|
173
|
+
output = bitcoinjs_lib_1.address.toOutputScript(matchedAddress, network);
|
|
174
|
+
}
|
|
175
|
+
catch (e) {
|
|
176
|
+
throw new Error(`Error: invalid address ${matchedAddress}`);
|
|
177
|
+
}
|
|
178
|
+
try {
|
|
179
|
+
payment = p2pkh({ output, network });
|
|
180
|
+
}
|
|
181
|
+
catch (e) { }
|
|
182
|
+
try {
|
|
183
|
+
payment = p2sh({ output, network });
|
|
184
|
+
}
|
|
185
|
+
catch (e) { }
|
|
186
|
+
try {
|
|
187
|
+
payment = p2wpkh({ output, network });
|
|
188
|
+
}
|
|
189
|
+
catch (e) { }
|
|
190
|
+
try {
|
|
191
|
+
payment = p2wsh({ output, network });
|
|
192
|
+
}
|
|
193
|
+
catch (e) { }
|
|
194
|
+
try {
|
|
195
|
+
payment = p2tr({ output, network });
|
|
196
|
+
}
|
|
197
|
+
catch (e) { }
|
|
198
|
+
if (!payment) {
|
|
199
|
+
throw new Error(`Error: invalid address ${matchedAddress}`);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
//pk(KEY)
|
|
203
|
+
else if (canonicalExpression.match(RE.rePkAnchored)) {
|
|
204
|
+
isSegwit = false;
|
|
205
|
+
const keyExpression = canonicalExpression.match(RE.reKeyExp)?.[0];
|
|
206
|
+
if (!keyExpression)
|
|
207
|
+
throw new Error(`Error: keyExpression could not me extracted`);
|
|
208
|
+
if (canonicalExpression !== `pk(${keyExpression})`)
|
|
209
|
+
throw new Error(`Error: invalid expression ${expression}`);
|
|
210
|
+
expandedExpression = 'pk(@0)';
|
|
211
|
+
const pKE = parseKeyExpression({ keyExpression, network, isSegwit });
|
|
212
|
+
expansionMap = { '@0': pKE };
|
|
213
|
+
if (!isCanonicalRanged) {
|
|
214
|
+
const pubkey = pKE.pubkey;
|
|
215
|
+
//Note there exists no address for p2pk, but we can still use the script
|
|
216
|
+
if (!pubkey)
|
|
217
|
+
throw new Error(`Error: could not extract a pubkey from ${expression}`);
|
|
218
|
+
payment = p2pk({ pubkey, network });
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
//pkh(KEY) - legacy
|
|
222
|
+
else if (canonicalExpression.match(RE.rePkhAnchored)) {
|
|
223
|
+
isSegwit = false;
|
|
224
|
+
const keyExpression = canonicalExpression.match(RE.reKeyExp)?.[0];
|
|
225
|
+
if (!keyExpression)
|
|
226
|
+
throw new Error(`Error: keyExpression could not me extracted`);
|
|
227
|
+
if (canonicalExpression !== `pkh(${keyExpression})`)
|
|
228
|
+
throw new Error(`Error: invalid expression ${expression}`);
|
|
229
|
+
expandedExpression = 'pkh(@0)';
|
|
230
|
+
const pKE = parseKeyExpression({ keyExpression, network, isSegwit });
|
|
231
|
+
expansionMap = { '@0': pKE };
|
|
232
|
+
if (!isCanonicalRanged) {
|
|
233
|
+
const pubkey = pKE.pubkey;
|
|
234
|
+
if (!pubkey)
|
|
235
|
+
throw new Error(`Error: could not extract a pubkey from ${expression}`);
|
|
236
|
+
payment = p2pkh({ pubkey, network });
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
//sh(wpkh(KEY)) - nested segwit
|
|
240
|
+
else if (canonicalExpression.match(RE.reShWpkhAnchored)) {
|
|
241
|
+
isSegwit = true;
|
|
242
|
+
const keyExpression = canonicalExpression.match(RE.reKeyExp)?.[0];
|
|
243
|
+
if (!keyExpression)
|
|
244
|
+
throw new Error(`Error: keyExpression could not me extracted`);
|
|
245
|
+
if (canonicalExpression !== `sh(wpkh(${keyExpression}))`)
|
|
246
|
+
throw new Error(`Error: invalid expression ${expression}`);
|
|
247
|
+
expandedExpression = 'sh(wpkh(@0))';
|
|
248
|
+
const pKE = parseKeyExpression({ keyExpression, network, isSegwit });
|
|
249
|
+
expansionMap = { '@0': pKE };
|
|
250
|
+
if (!isCanonicalRanged) {
|
|
251
|
+
const pubkey = pKE.pubkey;
|
|
252
|
+
if (!pubkey)
|
|
253
|
+
throw new Error(`Error: could not extract a pubkey from ${expression}`);
|
|
254
|
+
payment = p2sh({ redeem: p2wpkh({ pubkey, network }), network });
|
|
255
|
+
redeemScript = payment.redeem?.output;
|
|
256
|
+
if (!redeemScript)
|
|
257
|
+
throw new Error(`Error: could not calculate redeemScript for ${expression}`);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
//wpkh(KEY) - native segwit
|
|
261
|
+
else if (canonicalExpression.match(RE.reWpkhAnchored)) {
|
|
262
|
+
isSegwit = true;
|
|
263
|
+
const keyExpression = canonicalExpression.match(RE.reKeyExp)?.[0];
|
|
264
|
+
if (!keyExpression)
|
|
265
|
+
throw new Error(`Error: keyExpression could not me extracted`);
|
|
266
|
+
if (canonicalExpression !== `wpkh(${keyExpression})`)
|
|
267
|
+
throw new Error(`Error: invalid expression ${expression}`);
|
|
268
|
+
expandedExpression = 'wpkh(@0)';
|
|
269
|
+
const pKE = parseKeyExpression({ keyExpression, network, isSegwit });
|
|
270
|
+
expansionMap = { '@0': pKE };
|
|
271
|
+
if (!isCanonicalRanged) {
|
|
272
|
+
const pubkey = pKE.pubkey;
|
|
273
|
+
if (!pubkey)
|
|
274
|
+
throw new Error(`Error: could not extract a pubkey from ${expression}`);
|
|
275
|
+
payment = p2wpkh({ pubkey, network });
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
//sh(wsh(miniscript))
|
|
279
|
+
else if (canonicalExpression.match(RE.reShWshMiniscriptAnchored)) {
|
|
280
|
+
isSegwit = true;
|
|
281
|
+
miniscript = canonicalExpression.match(RE.reShWshMiniscriptAnchored)?.[1]; //[1]-> whatever is found sh(wsh(->HERE<-))
|
|
282
|
+
if (!miniscript)
|
|
283
|
+
throw new Error(`Error: could not get miniscript in ${expression}`);
|
|
284
|
+
({ expandedMiniscript, expansionMap } = expandMiniscript({
|
|
285
|
+
miniscript,
|
|
286
|
+
isSegwit,
|
|
287
|
+
network
|
|
288
|
+
}));
|
|
289
|
+
expandedExpression = `sh(wsh(${expandedMiniscript}))`;
|
|
290
|
+
if (!isCanonicalRanged) {
|
|
291
|
+
const script = (0, miniscript_1.miniscript2Script)({ expandedMiniscript, expansionMap });
|
|
292
|
+
witnessScript = script;
|
|
293
|
+
if (script.byteLength > MAX_STANDARD_P2WSH_SCRIPT_SIZE) {
|
|
294
|
+
throw new Error(`Error: script is too large, ${script.byteLength} bytes is larger than ${MAX_STANDARD_P2WSH_SCRIPT_SIZE} bytes`);
|
|
295
|
+
}
|
|
296
|
+
const nonPushOnlyOps = countNonPushOnlyOPs(script);
|
|
297
|
+
if (nonPushOnlyOps > MAX_OPS_PER_SCRIPT) {
|
|
298
|
+
throw new Error(`Error: too many non-push ops, ${nonPushOnlyOps} non-push ops is larger than ${MAX_OPS_PER_SCRIPT}`);
|
|
299
|
+
}
|
|
300
|
+
payment = p2sh({
|
|
301
|
+
redeem: p2wsh({ redeem: { output: script, network }, network }),
|
|
302
|
+
network
|
|
303
|
+
});
|
|
304
|
+
redeemScript = payment.redeem?.output;
|
|
305
|
+
if (!redeemScript)
|
|
306
|
+
throw new Error(`Error: could not calculate redeemScript for ${expression}`);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
//sh(miniscript)
|
|
310
|
+
else if (canonicalExpression.match(RE.reShMiniscriptAnchored)) {
|
|
311
|
+
//isSegwit false because we know it's a P2SH of a miniscript and not a
|
|
312
|
+
//P2SH that embeds a witness payment.
|
|
313
|
+
isSegwit = false;
|
|
314
|
+
miniscript = canonicalExpression.match(RE.reShMiniscriptAnchored)?.[1]; //[1]-> whatever is found sh(->HERE<-)
|
|
315
|
+
if (!miniscript)
|
|
316
|
+
throw new Error(`Error: could not get miniscript in ${expression}`);
|
|
317
|
+
if (allowMiniscriptInP2SH === false &&
|
|
318
|
+
//These top-level expressions within sh are allowed within sh.
|
|
319
|
+
//They can be parsed with miniscript2Script, but first we must make sure
|
|
320
|
+
//that other expressions are not accepted (unless forced with allowMiniscriptInP2SH).
|
|
321
|
+
miniscript.search(/^(pk\(|pkh\(|wpkh\(|combo\(|multi\(|sortedmulti\(|multi_a\(|sortedmulti_a\()/) !== 0) {
|
|
322
|
+
throw new Error(`Error: Miniscript expressions can only be used in wsh`);
|
|
323
|
+
}
|
|
324
|
+
({ expandedMiniscript, expansionMap } = expandMiniscript({
|
|
325
|
+
miniscript,
|
|
326
|
+
isSegwit,
|
|
327
|
+
network
|
|
328
|
+
}));
|
|
329
|
+
expandedExpression = `sh(${expandedMiniscript})`;
|
|
330
|
+
if (!isCanonicalRanged) {
|
|
331
|
+
const script = (0, miniscript_1.miniscript2Script)({ expandedMiniscript, expansionMap });
|
|
332
|
+
redeemScript = script;
|
|
333
|
+
if (script.byteLength > MAX_SCRIPT_ELEMENT_SIZE) {
|
|
334
|
+
throw new Error(`Error: P2SH script is too large, ${script.byteLength} bytes is larger than ${MAX_SCRIPT_ELEMENT_SIZE} bytes`);
|
|
335
|
+
}
|
|
336
|
+
const nonPushOnlyOps = countNonPushOnlyOPs(script);
|
|
337
|
+
if (nonPushOnlyOps > MAX_OPS_PER_SCRIPT) {
|
|
338
|
+
throw new Error(`Error: too many non-push ops, ${nonPushOnlyOps} non-push ops is larger than ${MAX_OPS_PER_SCRIPT}`);
|
|
339
|
+
}
|
|
340
|
+
payment = p2sh({ redeem: { output: script, network }, network });
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
//wsh(miniscript)
|
|
344
|
+
else if (canonicalExpression.match(RE.reWshMiniscriptAnchored)) {
|
|
345
|
+
isSegwit = true;
|
|
346
|
+
miniscript = canonicalExpression.match(RE.reWshMiniscriptAnchored)?.[1]; //[1]-> whatever is found wsh(->HERE<-)
|
|
347
|
+
if (!miniscript)
|
|
348
|
+
throw new Error(`Error: could not get miniscript in ${expression}`);
|
|
349
|
+
({ expandedMiniscript, expansionMap } = expandMiniscript({
|
|
350
|
+
miniscript,
|
|
351
|
+
isSegwit,
|
|
352
|
+
network
|
|
353
|
+
}));
|
|
354
|
+
expandedExpression = `wsh(${expandedMiniscript})`;
|
|
355
|
+
if (!isCanonicalRanged) {
|
|
356
|
+
const script = (0, miniscript_1.miniscript2Script)({ expandedMiniscript, expansionMap });
|
|
357
|
+
witnessScript = script;
|
|
358
|
+
if (script.byteLength > MAX_STANDARD_P2WSH_SCRIPT_SIZE) {
|
|
359
|
+
throw new Error(`Error: script is too large, ${script.byteLength} bytes is larger than ${MAX_STANDARD_P2WSH_SCRIPT_SIZE} bytes`);
|
|
360
|
+
}
|
|
361
|
+
const nonPushOnlyOps = countNonPushOnlyOPs(script);
|
|
362
|
+
if (nonPushOnlyOps > MAX_OPS_PER_SCRIPT) {
|
|
363
|
+
throw new Error(`Error: too many non-push ops, ${nonPushOnlyOps} non-push ops is larger than ${MAX_OPS_PER_SCRIPT}`);
|
|
364
|
+
}
|
|
365
|
+
payment = p2wsh({ redeem: { output: script, network }, network });
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
else {
|
|
369
|
+
throw new Error(`Error: Could not parse descriptor ${expression}`);
|
|
370
|
+
}
|
|
371
|
+
return {
|
|
372
|
+
...(payment !== undefined ? { payment } : {}),
|
|
373
|
+
...(expandedExpression !== undefined ? { expandedExpression } : {}),
|
|
374
|
+
...(miniscript !== undefined ? { miniscript } : {}),
|
|
375
|
+
...(expansionMap !== undefined ? { expansionMap } : {}),
|
|
376
|
+
...(isSegwit !== undefined ? { isSegwit } : {}),
|
|
377
|
+
...(expandedMiniscript !== undefined ? { expandedMiniscript } : {}),
|
|
378
|
+
...(redeemScript !== undefined ? { redeemScript } : {}),
|
|
379
|
+
...(witnessScript !== undefined ? { witnessScript } : {}),
|
|
380
|
+
isRanged,
|
|
381
|
+
canonicalExpression
|
|
382
|
+
};
|
|
383
|
+
};
|
|
117
384
|
/**
|
|
118
385
|
* Expand a miniscript to a generalized form using variables instead of key
|
|
119
386
|
* expressions. Variables will be of this form: @0, @1, ...
|
|
@@ -144,7 +411,6 @@ function DescriptorsFactory(ecc) {
|
|
|
144
411
|
* @throws {Error} - when descriptor is invalid
|
|
145
412
|
*/
|
|
146
413
|
constructor({ expression, index, checksumRequired = false, allowMiniscriptInP2SH = false, network = bitcoinjs_lib_1.networks.bitcoin, preimages = [], signersPubKeys }) {
|
|
147
|
-
var _a, _b, _c, _d, _e, _f;
|
|
148
414
|
_Descriptor_instances.add(this);
|
|
149
415
|
_Descriptor_payment.set(this, void 0);
|
|
150
416
|
_Descriptor_preimages.set(this, []);
|
|
@@ -163,253 +429,47 @@ function DescriptorsFactory(ecc) {
|
|
|
163
429
|
__classPrivateFieldSet(this, _Descriptor_preimages, preimages, "f");
|
|
164
430
|
if (typeof expression !== 'string')
|
|
165
431
|
throw new Error(`Error: invalid descriptor type`);
|
|
166
|
-
|
|
167
|
-
//particularize range descriptor for index (if desc is range descriptor)
|
|
168
|
-
const evaluatedExpression = evaluate({
|
|
432
|
+
const expandedResult = expand({
|
|
169
433
|
expression,
|
|
170
434
|
...(index !== undefined ? { index } : {}),
|
|
171
|
-
checksumRequired
|
|
435
|
+
checksumRequired,
|
|
436
|
+
network,
|
|
437
|
+
allowMiniscriptInP2SH
|
|
172
438
|
});
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
}
|
|
193
|
-
catch (e) { }
|
|
194
|
-
try {
|
|
195
|
-
payment = p2wpkh({ output, network });
|
|
196
|
-
}
|
|
197
|
-
catch (e) { }
|
|
198
|
-
try {
|
|
199
|
-
payment = p2wsh({ output, network });
|
|
200
|
-
}
|
|
201
|
-
catch (e) { }
|
|
202
|
-
try {
|
|
203
|
-
payment = p2tr({ output, network });
|
|
204
|
-
}
|
|
205
|
-
catch (e) { }
|
|
206
|
-
if (!payment) {
|
|
207
|
-
throw new Error(`Error: invalid address ${matchedAddress}`);
|
|
208
|
-
}
|
|
209
|
-
__classPrivateFieldSet(this, _Descriptor_payment, payment, "f");
|
|
210
|
-
}
|
|
211
|
-
//pk(KEY)
|
|
212
|
-
else if (evaluatedExpression.match(RE.rePkAnchored)) {
|
|
213
|
-
__classPrivateFieldSet(this, _Descriptor_isSegwit, false, "f");
|
|
214
|
-
const keyExpression = evaluatedExpression.match(RE.reKeyExp)?.[0];
|
|
215
|
-
if (!keyExpression)
|
|
216
|
-
throw new Error(`Error: keyExpression could not me extracted`);
|
|
217
|
-
if (evaluatedExpression !== `pk(${keyExpression})`)
|
|
218
|
-
throw new Error(`Error: invalid expression ${expression}`);
|
|
219
|
-
__classPrivateFieldSet(this, _Descriptor_expandedExpression, 'pk(@0)', "f");
|
|
220
|
-
__classPrivateFieldSet(this, _Descriptor_expansionMap, {
|
|
221
|
-
'@0': parseKeyExpression({
|
|
222
|
-
keyExpression,
|
|
223
|
-
network,
|
|
224
|
-
isSegwit: __classPrivateFieldGet(this, _Descriptor_isSegwit, "f")
|
|
225
|
-
})
|
|
226
|
-
}, "f");
|
|
227
|
-
const pubkey = __classPrivateFieldGet(this, _Descriptor_expansionMap, "f")['@0'].pubkey;
|
|
228
|
-
//Note there exists no address for p2pk, but we can still use the script
|
|
229
|
-
__classPrivateFieldSet(this, _Descriptor_payment, p2pk({ pubkey, network }), "f");
|
|
230
|
-
}
|
|
231
|
-
//pkh(KEY) - legacy
|
|
232
|
-
else if (evaluatedExpression.match(RE.rePkhAnchored)) {
|
|
233
|
-
__classPrivateFieldSet(this, _Descriptor_isSegwit, false, "f");
|
|
234
|
-
const keyExpression = evaluatedExpression.match(RE.reKeyExp)?.[0];
|
|
235
|
-
if (!keyExpression)
|
|
236
|
-
throw new Error(`Error: keyExpression could not me extracted`);
|
|
237
|
-
if (evaluatedExpression !== `pkh(${keyExpression})`)
|
|
238
|
-
throw new Error(`Error: invalid expression ${expression}`);
|
|
239
|
-
__classPrivateFieldSet(this, _Descriptor_expandedExpression, 'pkh(@0)', "f");
|
|
240
|
-
__classPrivateFieldSet(this, _Descriptor_expansionMap, {
|
|
241
|
-
'@0': parseKeyExpression({
|
|
242
|
-
keyExpression,
|
|
243
|
-
network,
|
|
244
|
-
isSegwit: __classPrivateFieldGet(this, _Descriptor_isSegwit, "f")
|
|
245
|
-
})
|
|
246
|
-
}, "f");
|
|
247
|
-
const pubkey = __classPrivateFieldGet(this, _Descriptor_expansionMap, "f")['@0'].pubkey;
|
|
248
|
-
__classPrivateFieldSet(this, _Descriptor_payment, p2pkh({ pubkey, network }), "f");
|
|
249
|
-
}
|
|
250
|
-
//sh(wpkh(KEY)) - nested segwit
|
|
251
|
-
else if (evaluatedExpression.match(RE.reShWpkhAnchored)) {
|
|
252
|
-
__classPrivateFieldSet(this, _Descriptor_isSegwit, true, "f");
|
|
253
|
-
const keyExpression = evaluatedExpression.match(RE.reKeyExp)?.[0];
|
|
254
|
-
if (!keyExpression)
|
|
255
|
-
throw new Error(`Error: keyExpression could not me extracted`);
|
|
256
|
-
if (evaluatedExpression !== `sh(wpkh(${keyExpression}))`)
|
|
257
|
-
throw new Error(`Error: invalid expression ${expression}`);
|
|
258
|
-
__classPrivateFieldSet(this, _Descriptor_expandedExpression, 'sh(wpkh(@0))', "f");
|
|
259
|
-
__classPrivateFieldSet(this, _Descriptor_expansionMap, {
|
|
260
|
-
'@0': parseKeyExpression({
|
|
261
|
-
keyExpression,
|
|
262
|
-
network,
|
|
263
|
-
isSegwit: __classPrivateFieldGet(this, _Descriptor_isSegwit, "f")
|
|
264
|
-
})
|
|
265
|
-
}, "f");
|
|
266
|
-
const pubkey = __classPrivateFieldGet(this, _Descriptor_expansionMap, "f")['@0'].pubkey;
|
|
267
|
-
__classPrivateFieldSet(this, _Descriptor_payment, p2sh({ redeem: p2wpkh({ pubkey, network }), network }), "f");
|
|
268
|
-
const redeemScript = __classPrivateFieldGet(this, _Descriptor_payment, "f").redeem?.output;
|
|
269
|
-
if (!redeemScript)
|
|
270
|
-
throw new Error(`Error: could not calculate redeemScript for ${expression}`);
|
|
271
|
-
__classPrivateFieldSet(this, _Descriptor_redeemScript, redeemScript, "f");
|
|
272
|
-
}
|
|
273
|
-
//wpkh(KEY) - native segwit
|
|
274
|
-
else if (evaluatedExpression.match(RE.reWpkhAnchored)) {
|
|
275
|
-
__classPrivateFieldSet(this, _Descriptor_isSegwit, true, "f");
|
|
276
|
-
const keyExpression = evaluatedExpression.match(RE.reKeyExp)?.[0];
|
|
277
|
-
if (!keyExpression)
|
|
278
|
-
throw new Error(`Error: keyExpression could not me extracted`);
|
|
279
|
-
if (evaluatedExpression !== `wpkh(${keyExpression})`)
|
|
280
|
-
throw new Error(`Error: invalid expression ${expression}`);
|
|
281
|
-
__classPrivateFieldSet(this, _Descriptor_expandedExpression, 'wpkh(@0)', "f");
|
|
282
|
-
__classPrivateFieldSet(this, _Descriptor_expansionMap, {
|
|
283
|
-
'@0': parseKeyExpression({
|
|
284
|
-
keyExpression,
|
|
285
|
-
network,
|
|
286
|
-
isSegwit: __classPrivateFieldGet(this, _Descriptor_isSegwit, "f")
|
|
287
|
-
})
|
|
288
|
-
}, "f");
|
|
289
|
-
const pubkey = __classPrivateFieldGet(this, _Descriptor_expansionMap, "f")['@0'].pubkey;
|
|
290
|
-
__classPrivateFieldSet(this, _Descriptor_payment, p2wpkh({ pubkey, network }), "f");
|
|
291
|
-
}
|
|
292
|
-
//sh(wsh(miniscript))
|
|
293
|
-
else if (evaluatedExpression.match(RE.reShWshMiniscriptAnchored)) {
|
|
294
|
-
__classPrivateFieldSet(this, _Descriptor_isSegwit, true, "f");
|
|
295
|
-
const miniscript = evaluatedExpression.match(RE.reShWshMiniscriptAnchored)?.[1]; //[1]-> whatever is found sh(wsh(->HERE<-))
|
|
296
|
-
if (!miniscript)
|
|
297
|
-
throw new Error(`Error: could not get miniscript in ${evaluatedExpression}`);
|
|
298
|
-
__classPrivateFieldSet(this, _Descriptor_miniscript, miniscript, "f");
|
|
299
|
-
(_a = this, _b = this, {
|
|
300
|
-
expandedMiniscript: ({ set value(_g) { __classPrivateFieldSet(_a, _Descriptor_expandedMiniscript, _g, "f"); } }).value,
|
|
301
|
-
expansionMap: ({ set value(_g) { __classPrivateFieldSet(_b, _Descriptor_expansionMap, _g, "f"); } }).value
|
|
302
|
-
} = expandMiniscript({
|
|
303
|
-
miniscript,
|
|
304
|
-
isSegwit: __classPrivateFieldGet(this, _Descriptor_isSegwit, "f"),
|
|
305
|
-
network
|
|
306
|
-
}));
|
|
307
|
-
__classPrivateFieldSet(this, _Descriptor_expandedExpression, `sh(wsh(${__classPrivateFieldGet(this, _Descriptor_expandedMiniscript, "f")}))`, "f");
|
|
308
|
-
const script = (0, miniscript_1.miniscript2Script)({
|
|
309
|
-
expandedMiniscript: __classPrivateFieldGet(this, _Descriptor_expandedMiniscript, "f"),
|
|
310
|
-
expansionMap: __classPrivateFieldGet(this, _Descriptor_expansionMap, "f")
|
|
311
|
-
});
|
|
312
|
-
__classPrivateFieldSet(this, _Descriptor_witnessScript, script, "f");
|
|
313
|
-
if (script.byteLength > MAX_STANDARD_P2WSH_SCRIPT_SIZE) {
|
|
314
|
-
throw new Error(`Error: script is too large, ${script.byteLength} bytes is larger than ${MAX_STANDARD_P2WSH_SCRIPT_SIZE} bytes`);
|
|
315
|
-
}
|
|
316
|
-
const nonPushOnlyOps = countNonPushOnlyOPs(script);
|
|
317
|
-
if (nonPushOnlyOps > MAX_OPS_PER_SCRIPT) {
|
|
318
|
-
throw new Error(`Error: too many non-push ops, ${nonPushOnlyOps} non-push ops is larger than ${MAX_OPS_PER_SCRIPT}`);
|
|
319
|
-
}
|
|
320
|
-
__classPrivateFieldSet(this, _Descriptor_payment, p2sh({
|
|
321
|
-
redeem: p2wsh({ redeem: { output: script, network }, network }),
|
|
322
|
-
network
|
|
323
|
-
}), "f");
|
|
324
|
-
const redeemScript = __classPrivateFieldGet(this, _Descriptor_payment, "f").redeem?.output;
|
|
325
|
-
if (!redeemScript)
|
|
326
|
-
throw new Error(`Error: could not calculate redeemScript for ${expression}`);
|
|
327
|
-
__classPrivateFieldSet(this, _Descriptor_redeemScript, redeemScript, "f");
|
|
328
|
-
}
|
|
329
|
-
//sh(miniscript)
|
|
330
|
-
else if (evaluatedExpression.match(RE.reShMiniscriptAnchored)) {
|
|
331
|
-
//isSegwit false because we know it's a P2SH of a miniscript and not a
|
|
332
|
-
//P2SH that embeds a witness payment.
|
|
333
|
-
__classPrivateFieldSet(this, _Descriptor_isSegwit, false, "f");
|
|
334
|
-
const miniscript = evaluatedExpression.match(RE.reShMiniscriptAnchored)?.[1]; //[1]-> whatever is found sh(->HERE<-)
|
|
335
|
-
if (!miniscript)
|
|
336
|
-
throw new Error(`Error: could not get miniscript in ${evaluatedExpression}`);
|
|
337
|
-
if (allowMiniscriptInP2SH === false &&
|
|
338
|
-
//These top-level expressions within sh are allowed within sh.
|
|
339
|
-
//They can be parsed with miniscript2Script, but first we must make sure
|
|
340
|
-
//that other expressions are not accepted (unless forced with allowMiniscriptInP2SH).
|
|
341
|
-
miniscript.search(/^(pk\(|pkh\(|wpkh\(|combo\(|multi\(|sortedmulti\(|multi_a\(|sortedmulti_a\()/) !== 0) {
|
|
342
|
-
throw new Error(`Error: Miniscript expressions can only be used in wsh`);
|
|
343
|
-
}
|
|
344
|
-
__classPrivateFieldSet(this, _Descriptor_miniscript, miniscript, "f");
|
|
345
|
-
(_c = this, _d = this, {
|
|
346
|
-
expandedMiniscript: ({ set value(_g) { __classPrivateFieldSet(_c, _Descriptor_expandedMiniscript, _g, "f"); } }).value,
|
|
347
|
-
expansionMap: ({ set value(_g) { __classPrivateFieldSet(_d, _Descriptor_expansionMap, _g, "f"); } }).value
|
|
348
|
-
} = expandMiniscript({
|
|
349
|
-
miniscript,
|
|
350
|
-
isSegwit: __classPrivateFieldGet(this, _Descriptor_isSegwit, "f"),
|
|
351
|
-
network
|
|
352
|
-
}));
|
|
353
|
-
__classPrivateFieldSet(this, _Descriptor_expandedExpression, `sh(${__classPrivateFieldGet(this, _Descriptor_expandedMiniscript, "f")})`, "f");
|
|
354
|
-
const script = (0, miniscript_1.miniscript2Script)({
|
|
355
|
-
expandedMiniscript: __classPrivateFieldGet(this, _Descriptor_expandedMiniscript, "f"),
|
|
356
|
-
expansionMap: __classPrivateFieldGet(this, _Descriptor_expansionMap, "f")
|
|
357
|
-
});
|
|
358
|
-
__classPrivateFieldSet(this, _Descriptor_redeemScript, script, "f");
|
|
359
|
-
if (script.byteLength > MAX_SCRIPT_ELEMENT_SIZE) {
|
|
360
|
-
throw new Error(`Error: P2SH script is too large, ${script.byteLength} bytes is larger than ${MAX_SCRIPT_ELEMENT_SIZE} bytes`);
|
|
361
|
-
}
|
|
362
|
-
const nonPushOnlyOps = countNonPushOnlyOPs(script);
|
|
363
|
-
if (nonPushOnlyOps > MAX_OPS_PER_SCRIPT) {
|
|
364
|
-
throw new Error(`Error: too many non-push ops, ${nonPushOnlyOps} non-push ops is larger than ${MAX_OPS_PER_SCRIPT}`);
|
|
365
|
-
}
|
|
366
|
-
__classPrivateFieldSet(this, _Descriptor_payment, p2sh({ redeem: { output: script, network }, network }), "f");
|
|
367
|
-
if (Buffer.compare(script, this.getRedeemScript()) !== 0)
|
|
368
|
-
throw new Error(`Error: redeemScript was not correctly set to the payment in expression ${expression}`);
|
|
369
|
-
}
|
|
370
|
-
//wsh(miniscript)
|
|
371
|
-
else if (evaluatedExpression.match(RE.reWshMiniscriptAnchored)) {
|
|
372
|
-
__classPrivateFieldSet(this, _Descriptor_isSegwit, true, "f");
|
|
373
|
-
const miniscript = evaluatedExpression.match(RE.reWshMiniscriptAnchored)?.[1]; //[1]-> whatever is found wsh(->HERE<-)
|
|
374
|
-
if (!miniscript)
|
|
375
|
-
throw new Error(`Error: could not get miniscript in ${evaluatedExpression}`);
|
|
376
|
-
__classPrivateFieldSet(this, _Descriptor_miniscript, miniscript, "f");
|
|
377
|
-
(_e = this, _f = this, {
|
|
378
|
-
expandedMiniscript: ({ set value(_g) { __classPrivateFieldSet(_e, _Descriptor_expandedMiniscript, _g, "f"); } }).value,
|
|
379
|
-
expansionMap: ({ set value(_g) { __classPrivateFieldSet(_f, _Descriptor_expansionMap, _g, "f"); } }).value
|
|
380
|
-
} = expandMiniscript({
|
|
381
|
-
miniscript,
|
|
382
|
-
isSegwit: __classPrivateFieldGet(this, _Descriptor_isSegwit, "f"),
|
|
383
|
-
network
|
|
384
|
-
}));
|
|
385
|
-
__classPrivateFieldSet(this, _Descriptor_expandedExpression, `wsh(${__classPrivateFieldGet(this, _Descriptor_expandedMiniscript, "f")})`, "f");
|
|
386
|
-
const script = (0, miniscript_1.miniscript2Script)({
|
|
387
|
-
expandedMiniscript: __classPrivateFieldGet(this, _Descriptor_expandedMiniscript, "f"),
|
|
388
|
-
expansionMap: __classPrivateFieldGet(this, _Descriptor_expansionMap, "f")
|
|
389
|
-
});
|
|
390
|
-
__classPrivateFieldSet(this, _Descriptor_witnessScript, script, "f");
|
|
391
|
-
if (script.byteLength > MAX_STANDARD_P2WSH_SCRIPT_SIZE) {
|
|
392
|
-
throw new Error(`Error: script is too large, ${script.byteLength} bytes is larger than ${MAX_STANDARD_P2WSH_SCRIPT_SIZE} bytes`);
|
|
393
|
-
}
|
|
394
|
-
const nonPushOnlyOps = countNonPushOnlyOPs(script);
|
|
395
|
-
if (nonPushOnlyOps > MAX_OPS_PER_SCRIPT) {
|
|
396
|
-
throw new Error(`Error: too many non-push ops, ${nonPushOnlyOps} non-push ops is larger than ${MAX_OPS_PER_SCRIPT}`);
|
|
397
|
-
}
|
|
398
|
-
__classPrivateFieldSet(this, _Descriptor_payment, p2wsh({ redeem: { output: script, network }, network }), "f");
|
|
399
|
-
}
|
|
400
|
-
else {
|
|
401
|
-
throw new Error(`Error: Could not parse descriptor ${expression}`);
|
|
402
|
-
}
|
|
439
|
+
if (expandedResult.isRanged && index === undefined)
|
|
440
|
+
throw new Error(`Error: index was not provided for ranged descriptor`);
|
|
441
|
+
if (!expandedResult.payment)
|
|
442
|
+
throw new Error(`Error: could not extract a payment from ${expression}`);
|
|
443
|
+
__classPrivateFieldSet(this, _Descriptor_payment, expandedResult.payment, "f");
|
|
444
|
+
if (expandedResult.expandedExpression !== undefined)
|
|
445
|
+
__classPrivateFieldSet(this, _Descriptor_expandedExpression, expandedResult.expandedExpression, "f");
|
|
446
|
+
if (expandedResult.miniscript !== undefined)
|
|
447
|
+
__classPrivateFieldSet(this, _Descriptor_miniscript, expandedResult.miniscript, "f");
|
|
448
|
+
if (expandedResult.expansionMap !== undefined)
|
|
449
|
+
__classPrivateFieldSet(this, _Descriptor_expansionMap, expandedResult.expansionMap, "f");
|
|
450
|
+
if (expandedResult.isSegwit !== undefined)
|
|
451
|
+
__classPrivateFieldSet(this, _Descriptor_isSegwit, expandedResult.isSegwit, "f");
|
|
452
|
+
if (expandedResult.expandedMiniscript !== undefined)
|
|
453
|
+
__classPrivateFieldSet(this, _Descriptor_expandedMiniscript, expandedResult.expandedMiniscript, "f");
|
|
454
|
+
if (expandedResult.redeemScript !== undefined)
|
|
455
|
+
__classPrivateFieldSet(this, _Descriptor_redeemScript, expandedResult.redeemScript, "f");
|
|
456
|
+
if (expandedResult.witnessScript !== undefined)
|
|
457
|
+
__classPrivateFieldSet(this, _Descriptor_witnessScript, expandedResult.witnessScript, "f");
|
|
403
458
|
if (signersPubKeys) {
|
|
404
459
|
__classPrivateFieldSet(this, _Descriptor_signersPubKeys, signersPubKeys, "f");
|
|
405
460
|
}
|
|
406
461
|
else {
|
|
407
462
|
if (__classPrivateFieldGet(this, _Descriptor_expansionMap, "f")) {
|
|
408
|
-
__classPrivateFieldSet(this, _Descriptor_signersPubKeys, Object.values(__classPrivateFieldGet(this, _Descriptor_expansionMap, "f")).map(keyInfo =>
|
|
463
|
+
__classPrivateFieldSet(this, _Descriptor_signersPubKeys, Object.values(__classPrivateFieldGet(this, _Descriptor_expansionMap, "f")).map(keyInfo => {
|
|
464
|
+
const pubkey = keyInfo.pubkey;
|
|
465
|
+
if (!pubkey)
|
|
466
|
+
throw new Error(`Error: could not extract a pubkey from ${expression}`);
|
|
467
|
+
return pubkey;
|
|
468
|
+
}), "f");
|
|
409
469
|
}
|
|
410
470
|
else {
|
|
411
471
|
//We should only miss expansionMap in addr() expressions:
|
|
412
|
-
if (!
|
|
472
|
+
if (!expandedResult.canonicalExpression.match(RE.reAddrAnchored)) {
|
|
413
473
|
throw new Error(`Error: expansionMap not available for expression ${expression} that is not an address`);
|
|
414
474
|
}
|
|
415
475
|
__classPrivateFieldSet(this, _Descriptor_signersPubKeys, [this.getScriptPubKey()], "f");
|
|
@@ -631,6 +691,6 @@ function DescriptorsFactory(ecc) {
|
|
|
631
691
|
throw new Error(`Error: cannot finalize psbt index ${index} since it does not correspond to this descriptor`);
|
|
632
692
|
}
|
|
633
693
|
};
|
|
634
|
-
return { Descriptor, parseKeyExpression, ECPair, BIP32 };
|
|
694
|
+
return { Descriptor, parseKeyExpression, expand, ECPair, BIP32 };
|
|
635
695
|
}
|
|
636
696
|
exports.DescriptorsFactory = DescriptorsFactory;
|
package/dist/keyExpressions.js
CHANGED
|
@@ -44,17 +44,17 @@ const derivePath = (node, path) => {
|
|
|
44
44
|
return node.derivePath(parsedPath);
|
|
45
45
|
};
|
|
46
46
|
/*
|
|
47
|
-
*
|
|
48
|
-
* binary format
|
|
47
|
+
* Parses a key expression (xpub, xprv, pubkey or wif) into KeyInfo
|
|
49
48
|
*/
|
|
50
49
|
function parseKeyExpression({ keyExpression, isSegwit, ECPair, BIP32, network = bitcoinjs_lib_1.networks.bitcoin }) {
|
|
51
|
-
let pubkey;
|
|
50
|
+
let pubkey; //won't be computed for ranged keyExpressions
|
|
52
51
|
let ecpair;
|
|
53
52
|
let bip32;
|
|
54
53
|
let masterFingerprint;
|
|
55
54
|
let originPath;
|
|
56
55
|
let keyPath;
|
|
57
56
|
let path;
|
|
57
|
+
const isRanged = keyExpression.indexOf('*') !== -1;
|
|
58
58
|
//Validate the keyExpression:
|
|
59
59
|
const keyExpressions = keyExpression.match(RE.reKeyExp);
|
|
60
60
|
if (keyExpressions === null || keyExpressions[0] !== keyExpression) {
|
|
@@ -115,7 +115,8 @@ function parseKeyExpression({ keyExpression, isSegwit, ECPair, BIP32, network =
|
|
|
115
115
|
if (!keyPath)
|
|
116
116
|
throw new Error(`Error: could not extract a path`);
|
|
117
117
|
//fromBase58 and derivePath will throw if xPub or path are not valid
|
|
118
|
-
|
|
118
|
+
if (!isRanged)
|
|
119
|
+
pubkey = derivePath(bip32, keyPath).publicKey;
|
|
119
120
|
}
|
|
120
121
|
else {
|
|
121
122
|
pubkey = bip32.publicKey;
|
|
@@ -134,7 +135,8 @@ function parseKeyExpression({ keyExpression, isSegwit, ECPair, BIP32, network =
|
|
|
134
135
|
if (!keyPath)
|
|
135
136
|
throw new Error(`Error: could not extract a path`);
|
|
136
137
|
//fromBase58 and derivePath will throw if xPrv or path are not valid
|
|
137
|
-
|
|
138
|
+
if (!isRanged)
|
|
139
|
+
pubkey = derivePath(bip32, keyPath).publicKey;
|
|
138
140
|
}
|
|
139
141
|
else {
|
|
140
142
|
pubkey = bip32.publicKey;
|
|
@@ -143,12 +145,12 @@ function parseKeyExpression({ keyExpression, isSegwit, ECPair, BIP32, network =
|
|
|
143
145
|
else {
|
|
144
146
|
throw new Error(`Error: could not get pubkey for keyExpression ${keyExpression}`);
|
|
145
147
|
}
|
|
146
|
-
if (
|
|
147
|
-
path =
|
|
148
|
+
if (originPath || keyPath) {
|
|
149
|
+
path = `m${originPath ?? ''}${keyPath ?? ''}`;
|
|
148
150
|
}
|
|
149
151
|
return {
|
|
150
|
-
pubkey,
|
|
151
152
|
keyExpression,
|
|
153
|
+
...(pubkey !== undefined ? { pubkey } : {}),
|
|
152
154
|
...(ecpair !== undefined ? { ecpair } : {}),
|
|
153
155
|
...(bip32 !== undefined ? { bip32 } : {}),
|
|
154
156
|
...(masterFingerprint !== undefined ? { masterFingerprint } : {}),
|
package/dist/miniscript.js
CHANGED
|
@@ -51,8 +51,11 @@ function expandMiniscript({ miniscript, isSegwit, network = bitcoinjs_lib_1.netw
|
|
|
51
51
|
return key;
|
|
52
52
|
});
|
|
53
53
|
//Do some assertions. Miniscript must not have duplicate keys, also all
|
|
54
|
-
//keyExpressions must produce a valid pubkey
|
|
55
|
-
|
|
54
|
+
//keyExpressions must produce a valid pubkey (unless it's ranged and we want
|
|
55
|
+
//to expand a generalized form, then we don't check)
|
|
56
|
+
const pubkeysHex = Object.values(expansionMap)
|
|
57
|
+
.filter(keyInfo => keyInfo.keyExpression.indexOf('*') === -1)
|
|
58
|
+
.map(keyInfo => {
|
|
56
59
|
if (!keyInfo.pubkey)
|
|
57
60
|
throw new Error(`Error: keyExpression ${keyInfo.keyExpression} does not have a pubkey`);
|
|
58
61
|
return keyInfo.pubkey.toString('hex');
|
|
@@ -133,7 +136,7 @@ function satisfyMiniscript({ expandedMiniscript, expansionMap, signatures = [],
|
|
|
133
136
|
const expandedSignatureMap = {};
|
|
134
137
|
signatures.forEach(signature => {
|
|
135
138
|
const pubkeyHex = signature.pubkey.toString('hex');
|
|
136
|
-
const keyExpression = Object.keys(expansionMap).find(k => expansionMap[k]?.pubkey
|
|
139
|
+
const keyExpression = Object.keys(expansionMap).find(k => expansionMap[k]?.pubkey?.toString('hex') === pubkeyHex);
|
|
137
140
|
expandedSignatureMap['<sig(' + keyExpression + ')>'] =
|
|
138
141
|
'<' + signature.signature.toString('hex') + '>';
|
|
139
142
|
});
|
package/dist/psbt.js
CHANGED
|
@@ -168,11 +168,16 @@ function updatePsbt({ psbt, vout, txHex, txId, value, sequence, locktime, keysIn
|
|
|
168
168
|
}
|
|
169
169
|
const bip32Derivation = keysInfo
|
|
170
170
|
.filter((keyInfo) => keyInfo.pubkey && keyInfo.masterFingerprint && keyInfo.path)
|
|
171
|
-
.map((keyInfo) =>
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
171
|
+
.map((keyInfo) => {
|
|
172
|
+
const pubkey = keyInfo.pubkey;
|
|
173
|
+
if (!pubkey)
|
|
174
|
+
throw new Error(`key ${keyInfo.keyExpression} missing pubkey`);
|
|
175
|
+
return {
|
|
176
|
+
masterFingerprint: keyInfo.masterFingerprint,
|
|
177
|
+
pubkey,
|
|
178
|
+
path: keyInfo.path
|
|
179
|
+
};
|
|
180
|
+
});
|
|
176
181
|
if (bip32Derivation.length)
|
|
177
182
|
input.bip32Derivation = bip32Derivation;
|
|
178
183
|
if (isSegwit && txHex !== undefined) {
|
package/dist/types.d.ts
CHANGED
|
@@ -26,7 +26,7 @@ export type TimeConstraints = {
|
|
|
26
26
|
};
|
|
27
27
|
export type KeyInfo = {
|
|
28
28
|
keyExpression: string;
|
|
29
|
-
pubkey
|
|
29
|
+
pubkey?: Buffer;
|
|
30
30
|
ecpair?: ECPairInterface;
|
|
31
31
|
bip32?: BIP32Interface;
|
|
32
32
|
masterFingerprint?: Buffer;
|
|
@@ -44,6 +44,26 @@ export interface ParseKeyExpression {
|
|
|
44
44
|
network?: Network;
|
|
45
45
|
}): KeyInfo;
|
|
46
46
|
}
|
|
47
|
+
export interface Expand {
|
|
48
|
+
(params: {
|
|
49
|
+
expression: string;
|
|
50
|
+
index?: number;
|
|
51
|
+
checksumRequired?: boolean;
|
|
52
|
+
network?: Network;
|
|
53
|
+
allowMiniscriptInP2SH?: boolean;
|
|
54
|
+
}): {
|
|
55
|
+
payment?: Payment;
|
|
56
|
+
expandedExpression?: string;
|
|
57
|
+
miniscript?: string;
|
|
58
|
+
expansionMap?: ExpansionMap;
|
|
59
|
+
isSegwit?: boolean;
|
|
60
|
+
expandedMiniscript?: string;
|
|
61
|
+
redeemScript?: Buffer;
|
|
62
|
+
witnessScript?: Buffer;
|
|
63
|
+
isRanged: boolean;
|
|
64
|
+
canonicalExpression: string;
|
|
65
|
+
};
|
|
66
|
+
}
|
|
47
67
|
interface XOnlyPointAddTweakResult {
|
|
48
68
|
parity: 1 | 0;
|
|
49
69
|
xOnlyPubkey: Uint8Array;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bitcoinerlab/descriptors",
|
|
3
3
|
"homepage": "https://github.com/bitcoinerlab/descriptors",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.3.1",
|
|
5
5
|
"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.",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
@@ -64,8 +64,8 @@
|
|
|
64
64
|
"devDependencies": {
|
|
65
65
|
"@babel/plugin-transform-modules-commonjs": "^7.20.11",
|
|
66
66
|
"@ledgerhq/hw-transport-node-hid": "^6.27.12",
|
|
67
|
-
"@typescript-eslint/eslint-plugin": "^5.
|
|
68
|
-
"@typescript-eslint/parser": "^5.
|
|
67
|
+
"@typescript-eslint/eslint-plugin": "^5.60.1",
|
|
68
|
+
"@typescript-eslint/parser": "^5.60.1",
|
|
69
69
|
"babel-plugin-transform-import-meta": "^2.2.0",
|
|
70
70
|
"better-docs": "^2.7.2",
|
|
71
71
|
"bip39": "^3.0.4",
|
|
@@ -78,8 +78,9 @@
|
|
|
78
78
|
"jest": "^29.4.3",
|
|
79
79
|
"jsdoc": "^3.6.11",
|
|
80
80
|
"path": "^0.12.7",
|
|
81
|
-
"prettier": "^2.8.
|
|
81
|
+
"prettier": "^2.8.8",
|
|
82
82
|
"regtest-client": "^0.2.0",
|
|
83
|
-
"ts-node-dev": "^2.0.0"
|
|
83
|
+
"ts-node-dev": "^2.0.0",
|
|
84
|
+
"typescript": "5.0"
|
|
84
85
|
}
|
|
85
86
|
}
|