@bitcoinerlab/descriptors 0.2.2 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +48 -0
- package/dist/descriptors.d.ts +2 -1
- package/dist/descriptors.js +288 -233
- package/dist/keyExpressions.js +10 -8
- package/dist/miniscript.js +6 -3
- package/dist/psbt.js +10 -5
- package/dist/types.d.ts +18 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -131,6 +131,54 @@ 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
|
+
allowMiniscriptInP2SH: true // Optional flag to allow miniscript in P2SH
|
|
146
|
+
});
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
The `expand()` function returns an object with the following properties:
|
|
150
|
+
|
|
151
|
+
- `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.
|
|
152
|
+
- `expandedExpression: string | undefined`: The expanded descriptor expression.
|
|
153
|
+
- `miniscript: string | undefined`: The extracted miniscript from the expression, if any.
|
|
154
|
+
- `expansionMap: ExpansionMap | undefined`: A map of key expressions in the descriptor to their corresponding expanded keys.
|
|
155
|
+
- `isSegwit: boolean | undefined`: A boolean indicating whether the descriptor represents a SegWit script.
|
|
156
|
+
- `expandedMiniscript: string | undefined`: The expanded miniscript, if any.
|
|
157
|
+
- `redeemScript: Buffer | undefined`: The redeem script for the descriptor, if applicable.
|
|
158
|
+
- `witnessScript: Buffer | undefined`: The witness script for the descriptor, if applicable.
|
|
159
|
+
|
|
160
|
+
For the example expression provided, the `expandedExpression` and a portion of the `expansionMap` would be as follows:
|
|
161
|
+
|
|
162
|
+
```javascript
|
|
163
|
+
// expression: 'sh(wsh(andor(pk(0252972572d465d016d4c501887b8df303eee3ed602c056b1eb09260dfa0da0ab2),older(8640),pk([d34db33f/49'/0'/0']tpubDCdxmvzJ5QBjTN8oCjjyT2V58AyZvA1fkmCeZRC75QMoaHcVP2m45Bv3hmnR7ttAwkb2UNYyoXdHVt4gwBqRrJqLUU2JrM43HippxiWpHra/1/2/3/4/*))))'
|
|
164
|
+
|
|
165
|
+
expandedExpression: 'sh(wsh(andor(pk(@0),older(8640),pk(@1))))',
|
|
166
|
+
expansionMap: {
|
|
167
|
+
'@0': {
|
|
168
|
+
keyExpression:
|
|
169
|
+
'0252972572d465d016d4c501887b8df303eee3ed602c056b1eb09260dfa0da0ab2'
|
|
170
|
+
},
|
|
171
|
+
'@1': {
|
|
172
|
+
keyExpression:
|
|
173
|
+
"[d34db33f/49'/0'/0']tpubDCdxmvzJ5QBjTN8oCjjyT2V58AyZvA1fkmCeZRC75QMoaHcVP2m45Bv3hmnR7ttAwkb2UNYyoXdHVt4gwBqRrJqLUU2JrM43HippxiWpHra/1/2/3/4/*",
|
|
174
|
+
keyPath: '/1/2/3/4/*',
|
|
175
|
+
originPath: "/49'/0'/0'",
|
|
176
|
+
path: "m/49'/0'/0'/1/2/3/4/*",
|
|
177
|
+
// Other relevant properties returned: `pubkey`, `ecpair` & `bip32` interfaces, `masterFingerprint`, etc.
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
134
182
|
### keyExpressions and scriptExpressions
|
|
135
183
|
|
|
136
184
|
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
|
@@ -114,6 +114,264 @@ 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
|
+
*
|
|
136
|
+
* @throws {Error} Throws an error if the descriptor cannot be parsed or does not conform to the expected format.
|
|
137
|
+
*/
|
|
138
|
+
const expand = ({ expression, loggedExpression, //this is the expression that will be used for logging error messages
|
|
139
|
+
network = bitcoinjs_lib_1.networks.bitcoin, allowMiniscriptInP2SH = false }) => {
|
|
140
|
+
//remove the checksum before proceeding:
|
|
141
|
+
expression = expression.replace(new RegExp(RE.reChecksum + '$'), '');
|
|
142
|
+
let expandedExpression;
|
|
143
|
+
let miniscript;
|
|
144
|
+
let expansionMap;
|
|
145
|
+
let isSegwit;
|
|
146
|
+
let expandedMiniscript;
|
|
147
|
+
let payment;
|
|
148
|
+
let witnessScript;
|
|
149
|
+
let redeemScript;
|
|
150
|
+
const isRanged = expression.indexOf('*') !== -1;
|
|
151
|
+
if (!loggedExpression)
|
|
152
|
+
loggedExpression = expression;
|
|
153
|
+
//addr(ADDR)
|
|
154
|
+
if (expression.match(RE.reAddrAnchored)) {
|
|
155
|
+
if (isRanged)
|
|
156
|
+
throw new Error(`Error: addr() cannot be ranged`);
|
|
157
|
+
const matchedAddress = expression.match(RE.reAddrAnchored)?.[1]; //[1]-> whatever is found addr(->HERE<-)
|
|
158
|
+
if (!matchedAddress)
|
|
159
|
+
throw new Error(`Error: could not get an address in ${loggedExpression}`);
|
|
160
|
+
let output;
|
|
161
|
+
try {
|
|
162
|
+
output = bitcoinjs_lib_1.address.toOutputScript(matchedAddress, network);
|
|
163
|
+
}
|
|
164
|
+
catch (e) {
|
|
165
|
+
throw new Error(`Error: invalid address ${matchedAddress}`);
|
|
166
|
+
}
|
|
167
|
+
try {
|
|
168
|
+
payment = p2pkh({ output, network });
|
|
169
|
+
}
|
|
170
|
+
catch (e) { }
|
|
171
|
+
try {
|
|
172
|
+
payment = p2sh({ output, network });
|
|
173
|
+
}
|
|
174
|
+
catch (e) { }
|
|
175
|
+
try {
|
|
176
|
+
payment = p2wpkh({ output, network });
|
|
177
|
+
}
|
|
178
|
+
catch (e) { }
|
|
179
|
+
try {
|
|
180
|
+
payment = p2wsh({ output, network });
|
|
181
|
+
}
|
|
182
|
+
catch (e) { }
|
|
183
|
+
try {
|
|
184
|
+
payment = p2tr({ output, network });
|
|
185
|
+
}
|
|
186
|
+
catch (e) { }
|
|
187
|
+
if (!payment) {
|
|
188
|
+
throw new Error(`Error: invalid address ${matchedAddress}`);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
//pk(KEY)
|
|
192
|
+
else if (expression.match(RE.rePkAnchored)) {
|
|
193
|
+
isSegwit = false;
|
|
194
|
+
const keyExpression = expression.match(RE.reKeyExp)?.[0];
|
|
195
|
+
if (!keyExpression)
|
|
196
|
+
throw new Error(`Error: keyExpression could not me extracted`);
|
|
197
|
+
if (expression !== `pk(${keyExpression})`)
|
|
198
|
+
throw new Error(`Error: invalid expression ${loggedExpression}`);
|
|
199
|
+
expandedExpression = 'pk(@0)';
|
|
200
|
+
expansionMap = {
|
|
201
|
+
'@0': parseKeyExpression({ keyExpression, network, isSegwit })
|
|
202
|
+
};
|
|
203
|
+
if (!isRanged) {
|
|
204
|
+
const pubkey = expansionMap['@0'].pubkey;
|
|
205
|
+
//Note there exists no address for p2pk, but we can still use the script
|
|
206
|
+
if (!pubkey)
|
|
207
|
+
throw new Error(`Error: could not extract a pubkey from ${loggedExpression}`);
|
|
208
|
+
payment = p2pk({ pubkey, network });
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
//pkh(KEY) - legacy
|
|
212
|
+
else if (expression.match(RE.rePkhAnchored)) {
|
|
213
|
+
isSegwit = false;
|
|
214
|
+
const keyExpression = expression.match(RE.reKeyExp)?.[0];
|
|
215
|
+
if (!keyExpression)
|
|
216
|
+
throw new Error(`Error: keyExpression could not me extracted`);
|
|
217
|
+
if (expression !== `pkh(${keyExpression})`)
|
|
218
|
+
throw new Error(`Error: invalid expression ${loggedExpression}`);
|
|
219
|
+
expandedExpression = 'pkh(@0)';
|
|
220
|
+
expansionMap = {
|
|
221
|
+
'@0': parseKeyExpression({ keyExpression, network, isSegwit })
|
|
222
|
+
};
|
|
223
|
+
if (!isRanged) {
|
|
224
|
+
const pubkey = expansionMap['@0'].pubkey;
|
|
225
|
+
if (!pubkey)
|
|
226
|
+
throw new Error(`Error: could not extract a pubkey from ${loggedExpression}`);
|
|
227
|
+
payment = p2pkh({ pubkey, network });
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
//sh(wpkh(KEY)) - nested segwit
|
|
231
|
+
else if (expression.match(RE.reShWpkhAnchored)) {
|
|
232
|
+
isSegwit = true;
|
|
233
|
+
const keyExpression = expression.match(RE.reKeyExp)?.[0];
|
|
234
|
+
if (!keyExpression)
|
|
235
|
+
throw new Error(`Error: keyExpression could not me extracted`);
|
|
236
|
+
if (expression !== `sh(wpkh(${keyExpression}))`)
|
|
237
|
+
throw new Error(`Error: invalid expression ${loggedExpression}`);
|
|
238
|
+
expandedExpression = 'sh(wpkh(@0))';
|
|
239
|
+
expansionMap = {
|
|
240
|
+
'@0': parseKeyExpression({ keyExpression, network, isSegwit })
|
|
241
|
+
};
|
|
242
|
+
if (!isRanged) {
|
|
243
|
+
const pubkey = expansionMap['@0'].pubkey;
|
|
244
|
+
if (!pubkey)
|
|
245
|
+
throw new Error(`Error: could not extract a pubkey from ${loggedExpression}`);
|
|
246
|
+
payment = p2sh({ redeem: p2wpkh({ pubkey, network }), network });
|
|
247
|
+
redeemScript = payment.redeem?.output;
|
|
248
|
+
if (!redeemScript)
|
|
249
|
+
throw new Error(`Error: could not calculate redeemScript for ${loggedExpression}`);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
//wpkh(KEY) - native segwit
|
|
253
|
+
else if (expression.match(RE.reWpkhAnchored)) {
|
|
254
|
+
isSegwit = true;
|
|
255
|
+
const keyExpression = expression.match(RE.reKeyExp)?.[0];
|
|
256
|
+
if (!keyExpression)
|
|
257
|
+
throw new Error(`Error: keyExpression could not me extracted`);
|
|
258
|
+
if (expression !== `wpkh(${keyExpression})`)
|
|
259
|
+
throw new Error(`Error: invalid expression ${loggedExpression}`);
|
|
260
|
+
expandedExpression = 'wpkh(@0)';
|
|
261
|
+
expansionMap = {
|
|
262
|
+
'@0': parseKeyExpression({ keyExpression, network, isSegwit })
|
|
263
|
+
};
|
|
264
|
+
if (!isRanged) {
|
|
265
|
+
const pubkey = expansionMap['@0'].pubkey;
|
|
266
|
+
if (!pubkey)
|
|
267
|
+
throw new Error(`Error: could not extract a pubkey from ${loggedExpression}`);
|
|
268
|
+
payment = p2wpkh({ pubkey, network });
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
//sh(wsh(miniscript))
|
|
272
|
+
else if (expression.match(RE.reShWshMiniscriptAnchored)) {
|
|
273
|
+
isSegwit = true;
|
|
274
|
+
miniscript = expression.match(RE.reShWshMiniscriptAnchored)?.[1]; //[1]-> whatever is found sh(wsh(->HERE<-))
|
|
275
|
+
if (!miniscript)
|
|
276
|
+
throw new Error(`Error: could not get miniscript in ${loggedExpression}`);
|
|
277
|
+
({ expandedMiniscript, expansionMap } = expandMiniscript({
|
|
278
|
+
miniscript,
|
|
279
|
+
isSegwit,
|
|
280
|
+
network
|
|
281
|
+
}));
|
|
282
|
+
expandedExpression = `sh(wsh(${expandedMiniscript}))`;
|
|
283
|
+
if (!isRanged) {
|
|
284
|
+
const script = (0, miniscript_1.miniscript2Script)({ expandedMiniscript, expansionMap });
|
|
285
|
+
witnessScript = script;
|
|
286
|
+
if (script.byteLength > MAX_STANDARD_P2WSH_SCRIPT_SIZE) {
|
|
287
|
+
throw new Error(`Error: script is too large, ${script.byteLength} bytes is larger than ${MAX_STANDARD_P2WSH_SCRIPT_SIZE} bytes`);
|
|
288
|
+
}
|
|
289
|
+
const nonPushOnlyOps = countNonPushOnlyOPs(script);
|
|
290
|
+
if (nonPushOnlyOps > MAX_OPS_PER_SCRIPT) {
|
|
291
|
+
throw new Error(`Error: too many non-push ops, ${nonPushOnlyOps} non-push ops is larger than ${MAX_OPS_PER_SCRIPT}`);
|
|
292
|
+
}
|
|
293
|
+
payment = p2sh({
|
|
294
|
+
redeem: p2wsh({ redeem: { output: script, network }, network }),
|
|
295
|
+
network
|
|
296
|
+
});
|
|
297
|
+
redeemScript = payment.redeem?.output;
|
|
298
|
+
if (!redeemScript)
|
|
299
|
+
throw new Error(`Error: could not calculate redeemScript for ${loggedExpression}`);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
//sh(miniscript)
|
|
303
|
+
else if (expression.match(RE.reShMiniscriptAnchored)) {
|
|
304
|
+
//isSegwit false because we know it's a P2SH of a miniscript and not a
|
|
305
|
+
//P2SH that embeds a witness payment.
|
|
306
|
+
isSegwit = false;
|
|
307
|
+
miniscript = expression.match(RE.reShMiniscriptAnchored)?.[1]; //[1]-> whatever is found sh(->HERE<-)
|
|
308
|
+
if (!miniscript)
|
|
309
|
+
throw new Error(`Error: could not get miniscript in ${loggedExpression}`);
|
|
310
|
+
if (allowMiniscriptInP2SH === false &&
|
|
311
|
+
//These top-level expressions within sh are allowed within sh.
|
|
312
|
+
//They can be parsed with miniscript2Script, but first we must make sure
|
|
313
|
+
//that other expressions are not accepted (unless forced with allowMiniscriptInP2SH).
|
|
314
|
+
miniscript.search(/^(pk\(|pkh\(|wpkh\(|combo\(|multi\(|sortedmulti\(|multi_a\(|sortedmulti_a\()/) !== 0) {
|
|
315
|
+
throw new Error(`Error: Miniscript expressions can only be used in wsh`);
|
|
316
|
+
}
|
|
317
|
+
({ expandedMiniscript, expansionMap } = expandMiniscript({
|
|
318
|
+
miniscript,
|
|
319
|
+
isSegwit,
|
|
320
|
+
network
|
|
321
|
+
}));
|
|
322
|
+
expandedExpression = `sh(${expandedMiniscript})`;
|
|
323
|
+
if (!isRanged) {
|
|
324
|
+
const script = (0, miniscript_1.miniscript2Script)({ expandedMiniscript, expansionMap });
|
|
325
|
+
redeemScript = script;
|
|
326
|
+
if (script.byteLength > MAX_SCRIPT_ELEMENT_SIZE) {
|
|
327
|
+
throw new Error(`Error: P2SH script is too large, ${script.byteLength} bytes is larger than ${MAX_SCRIPT_ELEMENT_SIZE} bytes`);
|
|
328
|
+
}
|
|
329
|
+
const nonPushOnlyOps = countNonPushOnlyOPs(script);
|
|
330
|
+
if (nonPushOnlyOps > MAX_OPS_PER_SCRIPT) {
|
|
331
|
+
throw new Error(`Error: too many non-push ops, ${nonPushOnlyOps} non-push ops is larger than ${MAX_OPS_PER_SCRIPT}`);
|
|
332
|
+
}
|
|
333
|
+
payment = p2sh({ redeem: { output: script, network }, network });
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
//wsh(miniscript)
|
|
337
|
+
else if (expression.match(RE.reWshMiniscriptAnchored)) {
|
|
338
|
+
isSegwit = true;
|
|
339
|
+
miniscript = expression.match(RE.reWshMiniscriptAnchored)?.[1]; //[1]-> whatever is found wsh(->HERE<-)
|
|
340
|
+
if (!miniscript)
|
|
341
|
+
throw new Error(`Error: could not get miniscript in ${loggedExpression}`);
|
|
342
|
+
({ expandedMiniscript, expansionMap } = expandMiniscript({
|
|
343
|
+
miniscript,
|
|
344
|
+
isSegwit,
|
|
345
|
+
network
|
|
346
|
+
}));
|
|
347
|
+
expandedExpression = `wsh(${expandedMiniscript})`;
|
|
348
|
+
if (!isRanged) {
|
|
349
|
+
const script = (0, miniscript_1.miniscript2Script)({ expandedMiniscript, expansionMap });
|
|
350
|
+
witnessScript = script;
|
|
351
|
+
if (script.byteLength > MAX_STANDARD_P2WSH_SCRIPT_SIZE) {
|
|
352
|
+
throw new Error(`Error: script is too large, ${script.byteLength} bytes is larger than ${MAX_STANDARD_P2WSH_SCRIPT_SIZE} bytes`);
|
|
353
|
+
}
|
|
354
|
+
const nonPushOnlyOps = countNonPushOnlyOPs(script);
|
|
355
|
+
if (nonPushOnlyOps > MAX_OPS_PER_SCRIPT) {
|
|
356
|
+
throw new Error(`Error: too many non-push ops, ${nonPushOnlyOps} non-push ops is larger than ${MAX_OPS_PER_SCRIPT}`);
|
|
357
|
+
}
|
|
358
|
+
payment = p2wsh({ redeem: { output: script, network }, network });
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
else {
|
|
362
|
+
throw new Error(`Error: Could not parse descriptor ${loggedExpression}`);
|
|
363
|
+
}
|
|
364
|
+
return {
|
|
365
|
+
...(payment !== undefined ? { payment } : {}),
|
|
366
|
+
...(expandedExpression !== undefined ? { expandedExpression } : {}),
|
|
367
|
+
...(miniscript !== undefined ? { miniscript } : {}),
|
|
368
|
+
...(expansionMap !== undefined ? { expansionMap } : {}),
|
|
369
|
+
...(isSegwit !== undefined ? { isSegwit } : {}),
|
|
370
|
+
...(expandedMiniscript !== undefined ? { expandedMiniscript } : {}),
|
|
371
|
+
...(redeemScript !== undefined ? { redeemScript } : {}),
|
|
372
|
+
...(witnessScript !== undefined ? { witnessScript } : {})
|
|
373
|
+
};
|
|
374
|
+
};
|
|
117
375
|
/**
|
|
118
376
|
* Expand a miniscript to a generalized form using variables instead of key
|
|
119
377
|
* expressions. Variables will be of this form: @0, @1, ...
|
|
@@ -144,7 +402,6 @@ function DescriptorsFactory(ecc) {
|
|
|
144
402
|
* @throws {Error} - when descriptor is invalid
|
|
145
403
|
*/
|
|
146
404
|
constructor({ expression, index, checksumRequired = false, allowMiniscriptInP2SH = false, network = bitcoinjs_lib_1.networks.bitcoin, preimages = [], signersPubKeys }) {
|
|
147
|
-
var _a, _b, _c, _d, _e, _f;
|
|
148
405
|
_Descriptor_instances.add(this);
|
|
149
406
|
_Descriptor_payment.set(this, void 0);
|
|
150
407
|
_Descriptor_preimages.set(this, []);
|
|
@@ -170,242 +427,40 @@ function DescriptorsFactory(ecc) {
|
|
|
170
427
|
...(index !== undefined ? { index } : {}),
|
|
171
428
|
checksumRequired
|
|
172
429
|
});
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
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
|
-
}
|
|
430
|
+
const expandedResult = expand({
|
|
431
|
+
expression: evaluatedExpression,
|
|
432
|
+
loggedExpression: expression,
|
|
433
|
+
network,
|
|
434
|
+
allowMiniscriptInP2SH
|
|
435
|
+
});
|
|
436
|
+
if (!expandedResult.payment)
|
|
437
|
+
throw new Error(`Error: could not extract a payment from ${expression}`);
|
|
438
|
+
__classPrivateFieldSet(this, _Descriptor_payment, expandedResult.payment, "f");
|
|
439
|
+
if (expandedResult.expandedExpression !== undefined)
|
|
440
|
+
__classPrivateFieldSet(this, _Descriptor_expandedExpression, expandedResult.expandedExpression, "f");
|
|
441
|
+
if (expandedResult.miniscript !== undefined)
|
|
442
|
+
__classPrivateFieldSet(this, _Descriptor_miniscript, expandedResult.miniscript, "f");
|
|
443
|
+
if (expandedResult.expansionMap !== undefined)
|
|
444
|
+
__classPrivateFieldSet(this, _Descriptor_expansionMap, expandedResult.expansionMap, "f");
|
|
445
|
+
if (expandedResult.isSegwit !== undefined)
|
|
446
|
+
__classPrivateFieldSet(this, _Descriptor_isSegwit, expandedResult.isSegwit, "f");
|
|
447
|
+
if (expandedResult.expandedMiniscript !== undefined)
|
|
448
|
+
__classPrivateFieldSet(this, _Descriptor_expandedMiniscript, expandedResult.expandedMiniscript, "f");
|
|
449
|
+
if (expandedResult.redeemScript !== undefined)
|
|
450
|
+
__classPrivateFieldSet(this, _Descriptor_redeemScript, expandedResult.redeemScript, "f");
|
|
451
|
+
if (expandedResult.witnessScript !== undefined)
|
|
452
|
+
__classPrivateFieldSet(this, _Descriptor_witnessScript, expandedResult.witnessScript, "f");
|
|
403
453
|
if (signersPubKeys) {
|
|
404
454
|
__classPrivateFieldSet(this, _Descriptor_signersPubKeys, signersPubKeys, "f");
|
|
405
455
|
}
|
|
406
456
|
else {
|
|
407
457
|
if (__classPrivateFieldGet(this, _Descriptor_expansionMap, "f")) {
|
|
408
|
-
__classPrivateFieldSet(this, _Descriptor_signersPubKeys, Object.values(__classPrivateFieldGet(this, _Descriptor_expansionMap, "f")).map(keyInfo =>
|
|
458
|
+
__classPrivateFieldSet(this, _Descriptor_signersPubKeys, Object.values(__classPrivateFieldGet(this, _Descriptor_expansionMap, "f")).map(keyInfo => {
|
|
459
|
+
const pubkey = keyInfo.pubkey;
|
|
460
|
+
if (!pubkey)
|
|
461
|
+
throw new Error(`Error: could not extract a pubkey from ${expression}`);
|
|
462
|
+
return pubkey;
|
|
463
|
+
}), "f");
|
|
409
464
|
}
|
|
410
465
|
else {
|
|
411
466
|
//We should only miss expansionMap in addr() expressions:
|
|
@@ -631,6 +686,6 @@ function DescriptorsFactory(ecc) {
|
|
|
631
686
|
throw new Error(`Error: cannot finalize psbt index ${index} since it does not correspond to this descriptor`);
|
|
632
687
|
}
|
|
633
688
|
};
|
|
634
|
-
return { Descriptor, parseKeyExpression, ECPair, BIP32 };
|
|
689
|
+
return { Descriptor, parseKeyExpression, expand, ECPair, BIP32 };
|
|
635
690
|
}
|
|
636
691
|
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,23 @@ export interface ParseKeyExpression {
|
|
|
44
44
|
network?: Network;
|
|
45
45
|
}): KeyInfo;
|
|
46
46
|
}
|
|
47
|
+
export interface Expand {
|
|
48
|
+
(params: {
|
|
49
|
+
expression: string;
|
|
50
|
+
loggedExpression?: string;
|
|
51
|
+
network?: Network;
|
|
52
|
+
allowMiniscriptInP2SH?: boolean;
|
|
53
|
+
}): {
|
|
54
|
+
payment?: Payment;
|
|
55
|
+
expandedExpression?: string;
|
|
56
|
+
miniscript?: string;
|
|
57
|
+
expansionMap?: ExpansionMap;
|
|
58
|
+
isSegwit?: boolean;
|
|
59
|
+
expandedMiniscript?: string;
|
|
60
|
+
redeemScript?: Buffer;
|
|
61
|
+
witnessScript?: Buffer;
|
|
62
|
+
};
|
|
63
|
+
}
|
|
47
64
|
interface XOnlyPointAddTweakResult {
|
|
48
65
|
parity: 1 | 0;
|
|
49
66
|
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.0",
|
|
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",
|
|
@@ -78,7 +78,7 @@
|
|
|
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
83
|
"ts-node-dev": "^2.0.0"
|
|
84
84
|
}
|