@bitcoinerlab/descriptors 0.3.0 → 1.0.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 +29 -8
- package/dist/descriptors.js +94 -89
- package/dist/index.d.ts +0 -2
- package/dist/index.js +1 -3
- package/dist/keyExpressions.d.ts +1 -2
- package/dist/ledger.d.ts +25 -7
- package/dist/ledger.js +69 -3
- package/dist/scriptExpressions.d.ts +3 -4
- package/dist/signers.d.ts +2 -3
- package/dist/signers.js +12 -7
- package/dist/types.d.ts +4 -1
- package/package.json +15 -7
- package/README.draft.md +0 -84
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
|
|
@@ -142,7 +142,15 @@ const result = expand({
|
|
|
142
142
|
network: networks.testnet, // One of bitcoinjs-lib `networks`
|
|
143
143
|
// (https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/src/networks.js)
|
|
144
144
|
// or another one with the same interface.
|
|
145
|
-
|
|
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.
|
|
146
154
|
});
|
|
147
155
|
```
|
|
148
156
|
|
|
@@ -156,6 +164,8 @@ The `expand()` function returns an object with the following properties:
|
|
|
156
164
|
- `expandedMiniscript: string | undefined`: The expanded miniscript, if any.
|
|
157
165
|
- `redeemScript: Buffer | undefined`: The redeem script for the descriptor, if applicable.
|
|
158
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.
|
|
159
169
|
|
|
160
170
|
For the example expression provided, the `expandedExpression` and a portion of the `expansionMap` would be as follows:
|
|
161
171
|
|
|
@@ -263,15 +273,26 @@ To finalize the `psbt`, you can either call the method `finalizePsbtInput({ inde
|
|
|
263
273
|
|
|
264
274
|
This library currently provides integration with Ledger wallets. Support for more devices is planned.
|
|
265
275
|
|
|
266
|
-
|
|
276
|
+
Before we dive in, note that, in addition to the documentation below, it is highly recommended to visit the [Ledger Playground](https://bitcoinerlab.com/guides/ledger-programming) with an interactive code sandbox of this lib interacting with a Ledger device.
|
|
277
|
+
|
|
278
|
+
To use this library with Ledger devices, you must first install Ledger support:
|
|
279
|
+
|
|
280
|
+
```bash
|
|
281
|
+
npm install ledger-bitcoin
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
For Ledger device signing, import the necessary functions as follows:
|
|
267
285
|
|
|
268
286
|
```javascript
|
|
287
|
+
import Transport from '@ledgerhq/hw-transport-node-hid'; //or hw-transport-web-hid, for web
|
|
288
|
+
import { AppClient } from 'ledger-bitcoin';
|
|
269
289
|
import { ledger } from '@bitcoinerlab/descriptors';
|
|
270
290
|
```
|
|
271
291
|
|
|
272
|
-
|
|
292
|
+
Then, use the following code to assert that the Ledger app is running Bitcoin Test version 2.1.0 or higher, and to create a new Ledger client:
|
|
273
293
|
|
|
274
294
|
```javascript
|
|
295
|
+
const transport = await Transport.create();
|
|
275
296
|
//Throws if not running Bitcoin Test >= 2.1.0
|
|
276
297
|
await ledger.assertLedgerApp({
|
|
277
298
|
transport,
|
|
@@ -279,12 +300,12 @@ await ledger.assertLedgerApp({
|
|
|
279
300
|
minVersion: '2.1.0'
|
|
280
301
|
});
|
|
281
302
|
|
|
282
|
-
const ledgerClient = new
|
|
303
|
+
const ledgerClient = new AppClient(transport);
|
|
283
304
|
```
|
|
284
305
|
|
|
285
306
|
Here, `transport` is an instance of a Transport object that allows communication with Ledger devices. You can use any of the transports [provided by Ledger](https://github.com/LedgerHQ/ledger-live#libs---libraries).
|
|
286
307
|
|
|
287
|
-
To register the policies of non-standard descriptors on the Ledger device,
|
|
308
|
+
To register the policies of non-standard descriptors on the Ledger device, use the following code:
|
|
288
309
|
|
|
289
310
|
```javascript
|
|
290
311
|
await ledger.registerLedgerWallet({
|
|
@@ -297,7 +318,7 @@ await ledger.registerLedgerWallet({
|
|
|
297
318
|
|
|
298
319
|
This code will auto-skip the policy registration process if it already exists. Please refer to [Ledger documentation](https://github.com/LedgerHQ/app-bitcoin-new/blob/develop/doc/wallet.md) to learn more about their Wallet Policies registration procedures.
|
|
299
320
|
|
|
300
|
-
Finally, `ledgerState` is an object used to store information related to Ledger devices. Although Ledger devices themselves are stateless, this object can be used to store information such as xpubs, master fingerprints, and wallet policies. You can pass an initially empty object that will be updated with more information as it is used. The object can be serialized and stored.
|
|
321
|
+
Finally, `ledgerState` is an object used to store information related to Ledger devices. Although Ledger devices themselves are stateless, this object can be used to store information such as xpubs, master fingerprints, and wallet policies. You can pass an initially empty object that will be updated with more information as it is used. The object can be serialized and stored for future use.
|
|
301
322
|
|
|
302
323
|
<a name="documentation"></a>
|
|
303
324
|
|
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
|
}
|
|
@@ -132,13 +132,15 @@ function DescriptorsFactory(ecc) {
|
|
|
132
132
|
* - expandedMiniscript: The expanded miniscript, if any.
|
|
133
133
|
* - redeemScript: The redeem script for the descriptor, if applicable.
|
|
134
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.
|
|
135
140
|
*
|
|
136
141
|
* @throws {Error} Throws an error if the descriptor cannot be parsed or does not conform to the expected format.
|
|
137
142
|
*/
|
|
138
|
-
const expand = ({ expression,
|
|
139
|
-
network = bitcoinjs_lib_1.networks.bitcoin, allowMiniscriptInP2SH = false }) => {
|
|
140
|
-
//remove the checksum before proceeding:
|
|
141
|
-
expression = expression.replace(new RegExp(RE.reChecksum + '$'), '');
|
|
143
|
+
const expand = ({ expression, index, checksumRequired = false, network = bitcoinjs_lib_1.networks.bitcoin, allowMiniscriptInP2SH = false }) => {
|
|
142
144
|
let expandedExpression;
|
|
143
145
|
let miniscript;
|
|
144
146
|
let expansionMap;
|
|
@@ -148,15 +150,24 @@ function DescriptorsFactory(ecc) {
|
|
|
148
150
|
let witnessScript;
|
|
149
151
|
let redeemScript;
|
|
150
152
|
const isRanged = expression.indexOf('*') !== -1;
|
|
151
|
-
if (
|
|
152
|
-
|
|
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;
|
|
153
164
|
//addr(ADDR)
|
|
154
|
-
if (
|
|
165
|
+
if (canonicalExpression.match(RE.reAddrAnchored)) {
|
|
155
166
|
if (isRanged)
|
|
156
167
|
throw new Error(`Error: addr() cannot be ranged`);
|
|
157
|
-
const matchedAddress =
|
|
168
|
+
const matchedAddress = canonicalExpression.match(RE.reAddrAnchored)?.[1]; //[1]-> whatever is found addr(->HERE<-)
|
|
158
169
|
if (!matchedAddress)
|
|
159
|
-
throw new Error(`Error: could not get an address in ${
|
|
170
|
+
throw new Error(`Error: could not get an address in ${expression}`);
|
|
160
171
|
let output;
|
|
161
172
|
try {
|
|
162
173
|
output = bitcoinjs_lib_1.address.toOutputScript(matchedAddress, network);
|
|
@@ -189,98 +200,94 @@ function DescriptorsFactory(ecc) {
|
|
|
189
200
|
}
|
|
190
201
|
}
|
|
191
202
|
//pk(KEY)
|
|
192
|
-
else if (
|
|
203
|
+
else if (canonicalExpression.match(RE.rePkAnchored)) {
|
|
193
204
|
isSegwit = false;
|
|
194
|
-
const keyExpression =
|
|
205
|
+
const keyExpression = canonicalExpression.match(RE.reKeyExp)?.[0];
|
|
195
206
|
if (!keyExpression)
|
|
196
207
|
throw new Error(`Error: keyExpression could not me extracted`);
|
|
197
|
-
if (
|
|
198
|
-
throw new Error(`Error: invalid expression ${
|
|
208
|
+
if (canonicalExpression !== `pk(${keyExpression})`)
|
|
209
|
+
throw new Error(`Error: invalid expression ${expression}`);
|
|
199
210
|
expandedExpression = 'pk(@0)';
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
const pubkey = expansionMap['@0'].pubkey;
|
|
211
|
+
const pKE = parseKeyExpression({ keyExpression, network, isSegwit });
|
|
212
|
+
expansionMap = { '@0': pKE };
|
|
213
|
+
if (!isCanonicalRanged) {
|
|
214
|
+
const pubkey = pKE.pubkey;
|
|
205
215
|
//Note there exists no address for p2pk, but we can still use the script
|
|
206
216
|
if (!pubkey)
|
|
207
|
-
throw new Error(`Error: could not extract a pubkey from ${
|
|
217
|
+
throw new Error(`Error: could not extract a pubkey from ${expression}`);
|
|
208
218
|
payment = p2pk({ pubkey, network });
|
|
209
219
|
}
|
|
210
220
|
}
|
|
211
221
|
//pkh(KEY) - legacy
|
|
212
|
-
else if (
|
|
222
|
+
else if (canonicalExpression.match(RE.rePkhAnchored)) {
|
|
213
223
|
isSegwit = false;
|
|
214
|
-
const keyExpression =
|
|
224
|
+
const keyExpression = canonicalExpression.match(RE.reKeyExp)?.[0];
|
|
215
225
|
if (!keyExpression)
|
|
216
226
|
throw new Error(`Error: keyExpression could not me extracted`);
|
|
217
|
-
if (
|
|
218
|
-
throw new Error(`Error: invalid expression ${
|
|
227
|
+
if (canonicalExpression !== `pkh(${keyExpression})`)
|
|
228
|
+
throw new Error(`Error: invalid expression ${expression}`);
|
|
219
229
|
expandedExpression = 'pkh(@0)';
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
const pubkey = expansionMap['@0'].pubkey;
|
|
230
|
+
const pKE = parseKeyExpression({ keyExpression, network, isSegwit });
|
|
231
|
+
expansionMap = { '@0': pKE };
|
|
232
|
+
if (!isCanonicalRanged) {
|
|
233
|
+
const pubkey = pKE.pubkey;
|
|
225
234
|
if (!pubkey)
|
|
226
|
-
throw new Error(`Error: could not extract a pubkey from ${
|
|
235
|
+
throw new Error(`Error: could not extract a pubkey from ${expression}`);
|
|
227
236
|
payment = p2pkh({ pubkey, network });
|
|
228
237
|
}
|
|
229
238
|
}
|
|
230
239
|
//sh(wpkh(KEY)) - nested segwit
|
|
231
|
-
else if (
|
|
240
|
+
else if (canonicalExpression.match(RE.reShWpkhAnchored)) {
|
|
232
241
|
isSegwit = true;
|
|
233
|
-
const keyExpression =
|
|
242
|
+
const keyExpression = canonicalExpression.match(RE.reKeyExp)?.[0];
|
|
234
243
|
if (!keyExpression)
|
|
235
244
|
throw new Error(`Error: keyExpression could not me extracted`);
|
|
236
|
-
if (
|
|
237
|
-
throw new Error(`Error: invalid expression ${
|
|
245
|
+
if (canonicalExpression !== `sh(wpkh(${keyExpression}))`)
|
|
246
|
+
throw new Error(`Error: invalid expression ${expression}`);
|
|
238
247
|
expandedExpression = 'sh(wpkh(@0))';
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
const pubkey = expansionMap['@0'].pubkey;
|
|
248
|
+
const pKE = parseKeyExpression({ keyExpression, network, isSegwit });
|
|
249
|
+
expansionMap = { '@0': pKE };
|
|
250
|
+
if (!isCanonicalRanged) {
|
|
251
|
+
const pubkey = pKE.pubkey;
|
|
244
252
|
if (!pubkey)
|
|
245
|
-
throw new Error(`Error: could not extract a pubkey from ${
|
|
253
|
+
throw new Error(`Error: could not extract a pubkey from ${expression}`);
|
|
246
254
|
payment = p2sh({ redeem: p2wpkh({ pubkey, network }), network });
|
|
247
255
|
redeemScript = payment.redeem?.output;
|
|
248
256
|
if (!redeemScript)
|
|
249
|
-
throw new Error(`Error: could not calculate redeemScript for ${
|
|
257
|
+
throw new Error(`Error: could not calculate redeemScript for ${expression}`);
|
|
250
258
|
}
|
|
251
259
|
}
|
|
252
260
|
//wpkh(KEY) - native segwit
|
|
253
|
-
else if (
|
|
261
|
+
else if (canonicalExpression.match(RE.reWpkhAnchored)) {
|
|
254
262
|
isSegwit = true;
|
|
255
|
-
const keyExpression =
|
|
263
|
+
const keyExpression = canonicalExpression.match(RE.reKeyExp)?.[0];
|
|
256
264
|
if (!keyExpression)
|
|
257
265
|
throw new Error(`Error: keyExpression could not me extracted`);
|
|
258
|
-
if (
|
|
259
|
-
throw new Error(`Error: invalid expression ${
|
|
266
|
+
if (canonicalExpression !== `wpkh(${keyExpression})`)
|
|
267
|
+
throw new Error(`Error: invalid expression ${expression}`);
|
|
260
268
|
expandedExpression = 'wpkh(@0)';
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
const pubkey = expansionMap['@0'].pubkey;
|
|
269
|
+
const pKE = parseKeyExpression({ keyExpression, network, isSegwit });
|
|
270
|
+
expansionMap = { '@0': pKE };
|
|
271
|
+
if (!isCanonicalRanged) {
|
|
272
|
+
const pubkey = pKE.pubkey;
|
|
266
273
|
if (!pubkey)
|
|
267
|
-
throw new Error(`Error: could not extract a pubkey from ${
|
|
274
|
+
throw new Error(`Error: could not extract a pubkey from ${expression}`);
|
|
268
275
|
payment = p2wpkh({ pubkey, network });
|
|
269
276
|
}
|
|
270
277
|
}
|
|
271
278
|
//sh(wsh(miniscript))
|
|
272
|
-
else if (
|
|
279
|
+
else if (canonicalExpression.match(RE.reShWshMiniscriptAnchored)) {
|
|
273
280
|
isSegwit = true;
|
|
274
|
-
miniscript =
|
|
281
|
+
miniscript = canonicalExpression.match(RE.reShWshMiniscriptAnchored)?.[1]; //[1]-> whatever is found sh(wsh(->HERE<-))
|
|
275
282
|
if (!miniscript)
|
|
276
|
-
throw new Error(`Error: could not get miniscript in ${
|
|
283
|
+
throw new Error(`Error: could not get miniscript in ${expression}`);
|
|
277
284
|
({ expandedMiniscript, expansionMap } = expandMiniscript({
|
|
278
285
|
miniscript,
|
|
279
286
|
isSegwit,
|
|
280
287
|
network
|
|
281
288
|
}));
|
|
282
289
|
expandedExpression = `sh(wsh(${expandedMiniscript}))`;
|
|
283
|
-
if (!
|
|
290
|
+
if (!isCanonicalRanged) {
|
|
284
291
|
const script = (0, miniscript_1.miniscript2Script)({ expandedMiniscript, expansionMap });
|
|
285
292
|
witnessScript = script;
|
|
286
293
|
if (script.byteLength > MAX_STANDARD_P2WSH_SCRIPT_SIZE) {
|
|
@@ -296,17 +303,17 @@ function DescriptorsFactory(ecc) {
|
|
|
296
303
|
});
|
|
297
304
|
redeemScript = payment.redeem?.output;
|
|
298
305
|
if (!redeemScript)
|
|
299
|
-
throw new Error(`Error: could not calculate redeemScript for ${
|
|
306
|
+
throw new Error(`Error: could not calculate redeemScript for ${expression}`);
|
|
300
307
|
}
|
|
301
308
|
}
|
|
302
309
|
//sh(miniscript)
|
|
303
|
-
else if (
|
|
310
|
+
else if (canonicalExpression.match(RE.reShMiniscriptAnchored)) {
|
|
304
311
|
//isSegwit false because we know it's a P2SH of a miniscript and not a
|
|
305
312
|
//P2SH that embeds a witness payment.
|
|
306
313
|
isSegwit = false;
|
|
307
|
-
miniscript =
|
|
314
|
+
miniscript = canonicalExpression.match(RE.reShMiniscriptAnchored)?.[1]; //[1]-> whatever is found sh(->HERE<-)
|
|
308
315
|
if (!miniscript)
|
|
309
|
-
throw new Error(`Error: could not get miniscript in ${
|
|
316
|
+
throw new Error(`Error: could not get miniscript in ${expression}`);
|
|
310
317
|
if (allowMiniscriptInP2SH === false &&
|
|
311
318
|
//These top-level expressions within sh are allowed within sh.
|
|
312
319
|
//They can be parsed with miniscript2Script, but first we must make sure
|
|
@@ -320,7 +327,7 @@ function DescriptorsFactory(ecc) {
|
|
|
320
327
|
network
|
|
321
328
|
}));
|
|
322
329
|
expandedExpression = `sh(${expandedMiniscript})`;
|
|
323
|
-
if (!
|
|
330
|
+
if (!isCanonicalRanged) {
|
|
324
331
|
const script = (0, miniscript_1.miniscript2Script)({ expandedMiniscript, expansionMap });
|
|
325
332
|
redeemScript = script;
|
|
326
333
|
if (script.byteLength > MAX_SCRIPT_ELEMENT_SIZE) {
|
|
@@ -334,18 +341,18 @@ function DescriptorsFactory(ecc) {
|
|
|
334
341
|
}
|
|
335
342
|
}
|
|
336
343
|
//wsh(miniscript)
|
|
337
|
-
else if (
|
|
344
|
+
else if (canonicalExpression.match(RE.reWshMiniscriptAnchored)) {
|
|
338
345
|
isSegwit = true;
|
|
339
|
-
miniscript =
|
|
346
|
+
miniscript = canonicalExpression.match(RE.reWshMiniscriptAnchored)?.[1]; //[1]-> whatever is found wsh(->HERE<-)
|
|
340
347
|
if (!miniscript)
|
|
341
|
-
throw new Error(`Error: could not get miniscript in ${
|
|
348
|
+
throw new Error(`Error: could not get miniscript in ${expression}`);
|
|
342
349
|
({ expandedMiniscript, expansionMap } = expandMiniscript({
|
|
343
350
|
miniscript,
|
|
344
351
|
isSegwit,
|
|
345
352
|
network
|
|
346
353
|
}));
|
|
347
354
|
expandedExpression = `wsh(${expandedMiniscript})`;
|
|
348
|
-
if (!
|
|
355
|
+
if (!isCanonicalRanged) {
|
|
349
356
|
const script = (0, miniscript_1.miniscript2Script)({ expandedMiniscript, expansionMap });
|
|
350
357
|
witnessScript = script;
|
|
351
358
|
if (script.byteLength > MAX_STANDARD_P2WSH_SCRIPT_SIZE) {
|
|
@@ -359,7 +366,7 @@ function DescriptorsFactory(ecc) {
|
|
|
359
366
|
}
|
|
360
367
|
}
|
|
361
368
|
else {
|
|
362
|
-
throw new Error(`Error: Could not parse descriptor ${
|
|
369
|
+
throw new Error(`Error: Could not parse descriptor ${expression}`);
|
|
363
370
|
}
|
|
364
371
|
return {
|
|
365
372
|
...(payment !== undefined ? { payment } : {}),
|
|
@@ -369,7 +376,9 @@ function DescriptorsFactory(ecc) {
|
|
|
369
376
|
...(isSegwit !== undefined ? { isSegwit } : {}),
|
|
370
377
|
...(expandedMiniscript !== undefined ? { expandedMiniscript } : {}),
|
|
371
378
|
...(redeemScript !== undefined ? { redeemScript } : {}),
|
|
372
|
-
...(witnessScript !== undefined ? { witnessScript } : {})
|
|
379
|
+
...(witnessScript !== undefined ? { witnessScript } : {}),
|
|
380
|
+
isRanged,
|
|
381
|
+
canonicalExpression
|
|
373
382
|
};
|
|
374
383
|
};
|
|
375
384
|
/**
|
|
@@ -420,19 +429,15 @@ function DescriptorsFactory(ecc) {
|
|
|
420
429
|
__classPrivateFieldSet(this, _Descriptor_preimages, preimages, "f");
|
|
421
430
|
if (typeof expression !== 'string')
|
|
422
431
|
throw new Error(`Error: invalid descriptor type`);
|
|
423
|
-
|
|
424
|
-
//particularize range descriptor for index (if desc is range descriptor)
|
|
425
|
-
const evaluatedExpression = evaluate({
|
|
432
|
+
const expandedResult = expand({
|
|
426
433
|
expression,
|
|
427
434
|
...(index !== undefined ? { index } : {}),
|
|
428
|
-
checksumRequired
|
|
429
|
-
});
|
|
430
|
-
const expandedResult = expand({
|
|
431
|
-
expression: evaluatedExpression,
|
|
432
|
-
loggedExpression: expression,
|
|
435
|
+
checksumRequired,
|
|
433
436
|
network,
|
|
434
437
|
allowMiniscriptInP2SH
|
|
435
438
|
});
|
|
439
|
+
if (expandedResult.isRanged && index === undefined)
|
|
440
|
+
throw new Error(`Error: index was not provided for ranged descriptor`);
|
|
436
441
|
if (!expandedResult.payment)
|
|
437
442
|
throw new Error(`Error: could not extract a payment from ${expression}`);
|
|
438
443
|
__classPrivateFieldSet(this, _Descriptor_payment, expandedResult.payment, "f");
|
|
@@ -464,7 +469,7 @@ function DescriptorsFactory(ecc) {
|
|
|
464
469
|
}
|
|
465
470
|
else {
|
|
466
471
|
//We should only miss expansionMap in addr() expressions:
|
|
467
|
-
if (!
|
|
472
|
+
if (!expandedResult.canonicalExpression.match(RE.reAddrAnchored)) {
|
|
468
473
|
throw new Error(`Error: expansionMap not available for expression ${expression} that is not an address`);
|
|
469
474
|
}
|
|
470
475
|
__classPrivateFieldSet(this, _Descriptor_signersPubKeys, [this.getScriptPubKey()], "f");
|
package/dist/index.d.ts
CHANGED
|
@@ -13,13 +13,11 @@ export declare function finalizePsbt({ psbt, descriptors, validate }: {
|
|
|
13
13
|
export { keyExpressionBIP32, keyExpressionLedger } from './keyExpressions';
|
|
14
14
|
import * as scriptExpressions from './scriptExpressions';
|
|
15
15
|
export { scriptExpressions };
|
|
16
|
-
import { AppClient } from 'ledger-bitcoin';
|
|
17
16
|
import { LedgerState, getLedgerMasterFingerPrint, getLedgerXpub, registerLedgerWallet, assertLedgerApp } from './ledger';
|
|
18
17
|
export declare const ledger: {
|
|
19
18
|
getLedgerMasterFingerPrint: typeof getLedgerMasterFingerPrint;
|
|
20
19
|
getLedgerXpub: typeof getLedgerXpub;
|
|
21
20
|
registerLedgerWallet: typeof registerLedgerWallet;
|
|
22
21
|
assertLedgerApp: typeof assertLedgerApp;
|
|
23
|
-
AppClient: typeof AppClient;
|
|
24
22
|
};
|
|
25
23
|
export type { LedgerState };
|
package/dist/index.js
CHANGED
|
@@ -42,12 +42,10 @@ Object.defineProperty(exports, "keyExpressionBIP32", { enumerable: true, get: fu
|
|
|
42
42
|
Object.defineProperty(exports, "keyExpressionLedger", { enumerable: true, get: function () { return keyExpressions_1.keyExpressionLedger; } });
|
|
43
43
|
const scriptExpressions = __importStar(require("./scriptExpressions"));
|
|
44
44
|
exports.scriptExpressions = scriptExpressions;
|
|
45
|
-
const ledger_bitcoin_1 = require("ledger-bitcoin");
|
|
46
45
|
const ledger_1 = require("./ledger");
|
|
47
46
|
exports.ledger = {
|
|
48
47
|
getLedgerMasterFingerPrint: ledger_1.getLedgerMasterFingerPrint,
|
|
49
48
|
getLedgerXpub: ledger_1.getLedgerXpub,
|
|
50
49
|
registerLedgerWallet: ledger_1.registerLedgerWallet,
|
|
51
|
-
assertLedgerApp: ledger_1.assertLedgerApp
|
|
52
|
-
AppClient: ledger_bitcoin_1.AppClient
|
|
50
|
+
assertLedgerApp: ledger_1.assertLedgerApp
|
|
53
51
|
};
|
package/dist/keyExpressions.d.ts
CHANGED
|
@@ -3,7 +3,6 @@ import type { ECPairAPI } from 'ecpair';
|
|
|
3
3
|
import type { BIP32API, BIP32Interface } from 'bip32';
|
|
4
4
|
import type { KeyInfo } from './types';
|
|
5
5
|
import { LedgerState } from './ledger';
|
|
6
|
-
import type { AppClient } from 'ledger-bitcoin';
|
|
7
6
|
export declare function parseKeyExpression({ keyExpression, isSegwit, ECPair, BIP32, network }: {
|
|
8
7
|
keyExpression: string;
|
|
9
8
|
network?: Network;
|
|
@@ -12,7 +11,7 @@ export declare function parseKeyExpression({ keyExpression, isSegwit, ECPair, BI
|
|
|
12
11
|
BIP32: BIP32API;
|
|
13
12
|
}): KeyInfo;
|
|
14
13
|
export declare function keyExpressionLedger({ ledgerClient, ledgerState, originPath, keyPath, change, index }: {
|
|
15
|
-
ledgerClient:
|
|
14
|
+
ledgerClient: unknown;
|
|
16
15
|
ledgerState: LedgerState;
|
|
17
16
|
originPath: string;
|
|
18
17
|
change?: number | undefined;
|
package/dist/ledger.d.ts
CHANGED
|
@@ -1,6 +1,24 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
2
|
import type { DescriptorInterface } from './types';
|
|
3
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Dynamically imports the 'ledger-bitcoin' module and, if provided, checks if `ledgerClient` is an instance of `AppClient`.
|
|
5
|
+
*
|
|
6
|
+
* @async
|
|
7
|
+
* @param {unknown} ledgerClient - An optional parameter that, if provided, is checked to see if it's an instance of `AppClient`.
|
|
8
|
+
* @throws {Error} Throws an error if `ledgerClient` is provided but is not an instance of `AppClient`.
|
|
9
|
+
* @throws {Error} Throws an error if the 'ledger-bitcoin' module cannot be imported. This typically indicates that the 'ledger-bitcoin' peer dependency is not installed.
|
|
10
|
+
* @returns {Promise<any>} Returns a promise that resolves with the entire 'ledger-bitcoin' module if it can be successfully imported.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
*
|
|
14
|
+
* importAndValidateLedgerBitcoin(ledgerClient)
|
|
15
|
+
* .then((module) => {
|
|
16
|
+
* const { AppClient, PsbtV2, DefaultWalletPolicy, WalletPolicy, DefaultDescriptorTemplate, PartialSignature } = module;
|
|
17
|
+
* // Use the imported objects...
|
|
18
|
+
* })
|
|
19
|
+
* .catch((error) => console.error(error));
|
|
20
|
+
*/
|
|
21
|
+
export declare function importAndValidateLedgerBitcoin(ledgerClient?: unknown): Promise<typeof import("ledger-bitcoin")>;
|
|
4
22
|
export declare function assertLedgerApp({ transport, name, minVersion }: {
|
|
5
23
|
transport: any;
|
|
6
24
|
name: string;
|
|
@@ -21,12 +39,12 @@ export type LedgerState = {
|
|
|
21
39
|
};
|
|
22
40
|
};
|
|
23
41
|
export declare function getLedgerMasterFingerPrint({ ledgerClient, ledgerState }: {
|
|
24
|
-
ledgerClient:
|
|
42
|
+
ledgerClient: unknown;
|
|
25
43
|
ledgerState: LedgerState;
|
|
26
44
|
}): Promise<Buffer>;
|
|
27
45
|
export declare function getLedgerXpub({ originPath, ledgerClient, ledgerState }: {
|
|
28
46
|
originPath: string;
|
|
29
|
-
ledgerClient:
|
|
47
|
+
ledgerClient: unknown;
|
|
30
48
|
ledgerState: LedgerState;
|
|
31
49
|
}): Promise<string>;
|
|
32
50
|
/**
|
|
@@ -56,7 +74,7 @@ export declare function getLedgerXpub({ originPath, ledgerClient, ledgerState }:
|
|
|
56
74
|
*/
|
|
57
75
|
export declare function descriptorToLedgerFormat({ descriptor, ledgerClient, ledgerState }: {
|
|
58
76
|
descriptor: DescriptorInterface;
|
|
59
|
-
ledgerClient:
|
|
77
|
+
ledgerClient: unknown;
|
|
60
78
|
ledgerState: LedgerState;
|
|
61
79
|
}): Promise<{
|
|
62
80
|
ledgerTemplate: string;
|
|
@@ -71,7 +89,7 @@ export declare function descriptorToLedgerFormat({ descriptor, ledgerClient, led
|
|
|
71
89
|
**/
|
|
72
90
|
export declare function registerLedgerWallet({ descriptor, ledgerClient, ledgerState, policyName }: {
|
|
73
91
|
descriptor: DescriptorInterface;
|
|
74
|
-
ledgerClient:
|
|
92
|
+
ledgerClient: unknown;
|
|
75
93
|
ledgerState: LedgerState;
|
|
76
94
|
policyName: string;
|
|
77
95
|
}): Promise<void>;
|
|
@@ -80,7 +98,7 @@ export declare function registerLedgerWallet({ descriptor, ledgerClient, ledgerS
|
|
|
80
98
|
**/
|
|
81
99
|
export declare function ledgerPolicyFromStandard({ descriptor, ledgerClient, ledgerState }: {
|
|
82
100
|
descriptor: DescriptorInterface;
|
|
83
|
-
ledgerClient:
|
|
101
|
+
ledgerClient: unknown;
|
|
84
102
|
ledgerState: LedgerState;
|
|
85
103
|
}): Promise<LedgerPolicy | null>;
|
|
86
104
|
export declare function comparePolicies(policyA: LedgerPolicy, policyB: LedgerPolicy): boolean;
|
|
@@ -89,6 +107,6 @@ export declare function comparePolicies(policyA: LedgerPolicy, policyB: LedgerPo
|
|
|
89
107
|
**/
|
|
90
108
|
export declare function ledgerPolicyFromState({ descriptor, ledgerClient, ledgerState }: {
|
|
91
109
|
descriptor: DescriptorInterface;
|
|
92
|
-
ledgerClient:
|
|
110
|
+
ledgerClient: unknown;
|
|
93
111
|
ledgerState: LedgerState;
|
|
94
112
|
}): Promise<LedgerPolicy | null>;
|
package/dist/ledger.js
CHANGED
|
@@ -1,11 +1,66 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
// Copyright (c) 2023 Jose-Luis Landabaso - https://bitcoinerlab.com
|
|
3
3
|
// Distributed under the MIT software license
|
|
4
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
5
|
+
if (k2 === undefined) k2 = k;
|
|
6
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
7
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
8
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
9
|
+
}
|
|
10
|
+
Object.defineProperty(o, k2, desc);
|
|
11
|
+
}) : (function(o, m, k, k2) {
|
|
12
|
+
if (k2 === undefined) k2 = k;
|
|
13
|
+
o[k2] = m[k];
|
|
14
|
+
}));
|
|
15
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
16
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
17
|
+
}) : function(o, v) {
|
|
18
|
+
o["default"] = v;
|
|
19
|
+
});
|
|
20
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
21
|
+
if (mod && mod.__esModule) return mod;
|
|
22
|
+
var result = {};
|
|
23
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
24
|
+
__setModuleDefault(result, mod);
|
|
25
|
+
return result;
|
|
26
|
+
};
|
|
4
27
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
-
exports.ledgerPolicyFromState = exports.comparePolicies = exports.ledgerPolicyFromStandard = exports.registerLedgerWallet = exports.descriptorToLedgerFormat = exports.getLedgerXpub = exports.getLedgerMasterFingerPrint = exports.assertLedgerApp = void 0;
|
|
6
|
-
const ledger_bitcoin_1 = require("ledger-bitcoin");
|
|
28
|
+
exports.ledgerPolicyFromState = exports.comparePolicies = exports.ledgerPolicyFromStandard = exports.registerLedgerWallet = exports.descriptorToLedgerFormat = exports.getLedgerXpub = exports.getLedgerMasterFingerPrint = exports.assertLedgerApp = exports.importAndValidateLedgerBitcoin = void 0;
|
|
7
29
|
const bitcoinjs_lib_1 = require("bitcoinjs-lib");
|
|
8
30
|
const re_1 = require("./re");
|
|
31
|
+
/**
|
|
32
|
+
* Dynamically imports the 'ledger-bitcoin' module and, if provided, checks if `ledgerClient` is an instance of `AppClient`.
|
|
33
|
+
*
|
|
34
|
+
* @async
|
|
35
|
+
* @param {unknown} ledgerClient - An optional parameter that, if provided, is checked to see if it's an instance of `AppClient`.
|
|
36
|
+
* @throws {Error} Throws an error if `ledgerClient` is provided but is not an instance of `AppClient`.
|
|
37
|
+
* @throws {Error} Throws an error if the 'ledger-bitcoin' module cannot be imported. This typically indicates that the 'ledger-bitcoin' peer dependency is not installed.
|
|
38
|
+
* @returns {Promise<any>} Returns a promise that resolves with the entire 'ledger-bitcoin' module if it can be successfully imported.
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
*
|
|
42
|
+
* importAndValidateLedgerBitcoin(ledgerClient)
|
|
43
|
+
* .then((module) => {
|
|
44
|
+
* const { AppClient, PsbtV2, DefaultWalletPolicy, WalletPolicy, DefaultDescriptorTemplate, PartialSignature } = module;
|
|
45
|
+
* // Use the imported objects...
|
|
46
|
+
* })
|
|
47
|
+
* .catch((error) => console.error(error));
|
|
48
|
+
*/
|
|
49
|
+
async function importAndValidateLedgerBitcoin(ledgerClient) {
|
|
50
|
+
let ledgerBitcoinModule;
|
|
51
|
+
try {
|
|
52
|
+
ledgerBitcoinModule = await Promise.resolve().then(() => __importStar(require('ledger-bitcoin')));
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
throw new Error('Could not import "ledger-bitcoin". This is a peer dependency and needs to be installed explicitly. Please run "npm install ledger-bitcoin" to use Ledger Hardware Wallet functionality.');
|
|
56
|
+
}
|
|
57
|
+
const { AppClient } = ledgerBitcoinModule;
|
|
58
|
+
if (ledgerClient !== undefined && !(ledgerClient instanceof AppClient)) {
|
|
59
|
+
throw new Error('Error: invalid AppClient instance');
|
|
60
|
+
}
|
|
61
|
+
return ledgerBitcoinModule;
|
|
62
|
+
}
|
|
63
|
+
exports.importAndValidateLedgerBitcoin = importAndValidateLedgerBitcoin;
|
|
9
64
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
10
65
|
async function ledgerAppInfo(transport) {
|
|
11
66
|
const r = await transport.send(0xb0, 0x01, 0x00, 0x00);
|
|
@@ -60,6 +115,9 @@ function isLedgerStandard({ ledgerTemplate, keyRoots, network = bitcoinjs_lib_1.
|
|
|
60
115
|
return false;
|
|
61
116
|
}
|
|
62
117
|
async function getLedgerMasterFingerPrint({ ledgerClient, ledgerState }) {
|
|
118
|
+
const { AppClient } = await importAndValidateLedgerBitcoin(ledgerClient);
|
|
119
|
+
if (!(ledgerClient instanceof AppClient))
|
|
120
|
+
throw new Error(`Error: pass a valid ledgerClient`);
|
|
63
121
|
let masterFingerprint = ledgerState.masterFingerprint;
|
|
64
122
|
if (!masterFingerprint) {
|
|
65
123
|
masterFingerprint = Buffer.from(await ledgerClient.getMasterFingerprint(), 'hex');
|
|
@@ -69,6 +127,9 @@ async function getLedgerMasterFingerPrint({ ledgerClient, ledgerState }) {
|
|
|
69
127
|
}
|
|
70
128
|
exports.getLedgerMasterFingerPrint = getLedgerMasterFingerPrint;
|
|
71
129
|
async function getLedgerXpub({ originPath, ledgerClient, ledgerState }) {
|
|
130
|
+
const { AppClient } = await importAndValidateLedgerBitcoin(ledgerClient);
|
|
131
|
+
if (!(ledgerClient instanceof AppClient))
|
|
132
|
+
throw new Error(`Error: pass a valid ledgerClient`);
|
|
72
133
|
if (!ledgerState.xpubs)
|
|
73
134
|
ledgerState.xpubs = {};
|
|
74
135
|
let xpub = ledgerState.xpubs[originPath];
|
|
@@ -80,6 +141,8 @@ async function getLedgerXpub({ originPath, ledgerClient, ledgerState }) {
|
|
|
80
141
|
catch (err) {
|
|
81
142
|
xpub = await ledgerClient.getExtendedPubkey(`m${originPath}`, true);
|
|
82
143
|
}
|
|
144
|
+
if (typeof xpub !== 'string')
|
|
145
|
+
throw new Error(`Error: ledgerClient did not return a valid xpub`);
|
|
83
146
|
ledgerState.xpubs[originPath] = xpub;
|
|
84
147
|
}
|
|
85
148
|
return xpub;
|
|
@@ -179,6 +242,9 @@ exports.descriptorToLedgerFormat = descriptorToLedgerFormat;
|
|
|
179
242
|
*
|
|
180
243
|
**/
|
|
181
244
|
async function registerLedgerWallet({ descriptor, ledgerClient, ledgerState, policyName }) {
|
|
245
|
+
const { WalletPolicy, AppClient } = await importAndValidateLedgerBitcoin(ledgerClient);
|
|
246
|
+
if (!(ledgerClient instanceof AppClient))
|
|
247
|
+
throw new Error(`Error: pass a valid ledgerClient`);
|
|
182
248
|
const result = await descriptorToLedgerFormat({
|
|
183
249
|
descriptor,
|
|
184
250
|
ledgerClient,
|
|
@@ -204,7 +270,7 @@ async function registerLedgerWallet({ descriptor, ledgerClient, ledgerState, pol
|
|
|
204
270
|
//It already existed. No need to register it again.
|
|
205
271
|
}
|
|
206
272
|
else {
|
|
207
|
-
walletPolicy = new
|
|
273
|
+
walletPolicy = new WalletPolicy(policyName, ledgerTemplate, keyRoots);
|
|
208
274
|
let policyId;
|
|
209
275
|
[policyId, policyHmac] = await ledgerClient.registerWallet(walletPolicy);
|
|
210
276
|
const policy = {
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { AppClient } from 'ledger-bitcoin';
|
|
2
1
|
import { Network } from 'bitcoinjs-lib';
|
|
3
2
|
import type { LedgerState } from './ledger';
|
|
4
3
|
import type { BIP32Interface } from 'bip32';
|
|
@@ -30,7 +29,7 @@ export declare const wpkhBIP32: ({ masterNode, network, keyPath, account, change
|
|
|
30
29
|
isPublic?: boolean;
|
|
31
30
|
}) => string;
|
|
32
31
|
export declare const pkhLedger: ({ ledgerClient, ledgerState, network, account, keyPath, change, index }: {
|
|
33
|
-
ledgerClient:
|
|
32
|
+
ledgerClient: unknown;
|
|
34
33
|
ledgerState: LedgerState;
|
|
35
34
|
network?: Network;
|
|
36
35
|
account: number;
|
|
@@ -39,7 +38,7 @@ export declare const pkhLedger: ({ ledgerClient, ledgerState, network, account,
|
|
|
39
38
|
index?: number | undefined | '*';
|
|
40
39
|
}) => Promise<string>;
|
|
41
40
|
export declare const shWpkhLedger: ({ ledgerClient, ledgerState, network, account, keyPath, change, index }: {
|
|
42
|
-
ledgerClient:
|
|
41
|
+
ledgerClient: unknown;
|
|
43
42
|
ledgerState: LedgerState;
|
|
44
43
|
network?: Network;
|
|
45
44
|
account: number;
|
|
@@ -48,7 +47,7 @@ export declare const shWpkhLedger: ({ ledgerClient, ledgerState, network, accoun
|
|
|
48
47
|
index?: number | undefined | '*';
|
|
49
48
|
}) => Promise<string>;
|
|
50
49
|
export declare const wpkhLedger: ({ ledgerClient, ledgerState, network, account, keyPath, change, index }: {
|
|
51
|
-
ledgerClient:
|
|
50
|
+
ledgerClient: unknown;
|
|
52
51
|
ledgerState: LedgerState;
|
|
53
52
|
network?: Network;
|
|
54
53
|
account: number;
|
package/dist/signers.d.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { Psbt } from 'bitcoinjs-lib';
|
|
2
2
|
import type { ECPairInterface } from 'ecpair';
|
|
3
3
|
import type { BIP32Interface } from 'bip32';
|
|
4
|
-
import { AppClient } from 'ledger-bitcoin';
|
|
5
4
|
import type { DescriptorInterface } from './types';
|
|
6
5
|
import { LedgerState } from './ledger';
|
|
7
6
|
export declare function signInputECPair({ psbt, index, ecpair }: {
|
|
@@ -26,12 +25,12 @@ export declare function signInputLedger({ psbt, index, descriptor, ledgerClient,
|
|
|
26
25
|
psbt: Psbt;
|
|
27
26
|
index: number;
|
|
28
27
|
descriptor: DescriptorInterface;
|
|
29
|
-
ledgerClient:
|
|
28
|
+
ledgerClient: unknown;
|
|
30
29
|
ledgerState: LedgerState;
|
|
31
30
|
}): Promise<void>;
|
|
32
31
|
export declare function signLedger({ psbt, descriptors, ledgerClient, ledgerState }: {
|
|
33
32
|
psbt: Psbt;
|
|
34
33
|
descriptors: DescriptorInterface[];
|
|
35
|
-
ledgerClient:
|
|
34
|
+
ledgerClient: unknown;
|
|
36
35
|
ledgerState: LedgerState;
|
|
37
36
|
}): Promise<void>;
|
package/dist/signers.js
CHANGED
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
// Distributed under the MIT software license
|
|
4
4
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
5
|
exports.signLedger = exports.signInputLedger = exports.signBIP32 = exports.signInputBIP32 = exports.signECPair = exports.signInputECPair = void 0;
|
|
6
|
-
const ledger_bitcoin_1 = require("ledger-bitcoin");
|
|
7
6
|
const ledger_1 = require("./ledger");
|
|
8
7
|
function signInputECPair({ psbt, index, ecpair }) {
|
|
9
8
|
psbt.signInput(index, ecpair);
|
|
@@ -28,6 +27,9 @@ const ledgerSignaturesForInputIndex = (index, ledgerSignatures) => ledgerSignatu
|
|
|
28
27
|
signature: partialSignature.signature
|
|
29
28
|
}));
|
|
30
29
|
async function signInputLedger({ psbt, index, descriptor, ledgerClient, ledgerState }) {
|
|
30
|
+
const { PsbtV2, DefaultWalletPolicy, WalletPolicy, AppClient } = await (0, ledger_1.importAndValidateLedgerBitcoin)(ledgerClient);
|
|
31
|
+
if (!(ledgerClient instanceof AppClient))
|
|
32
|
+
throw new Error(`Error: pass a valid ledgerClient`);
|
|
31
33
|
const result = await (0, ledger_1.descriptorToLedgerFormat)({
|
|
32
34
|
descriptor,
|
|
33
35
|
ledgerClient,
|
|
@@ -43,7 +45,7 @@ async function signInputLedger({ psbt, index, descriptor, ledgerClient, ledgerSt
|
|
|
43
45
|
ledgerState
|
|
44
46
|
});
|
|
45
47
|
if (standardPolicy) {
|
|
46
|
-
ledgerSignatures = await ledgerClient.signPsbt(new
|
|
48
|
+
ledgerSignatures = await ledgerClient.signPsbt(new PsbtV2().fromBitcoinJS(psbt), new DefaultWalletPolicy(ledgerTemplate, keyRoots[0]), null);
|
|
47
49
|
}
|
|
48
50
|
else {
|
|
49
51
|
const policy = await (0, ledger_1.ledgerPolicyFromState)({
|
|
@@ -53,8 +55,8 @@ async function signInputLedger({ psbt, index, descriptor, ledgerClient, ledgerSt
|
|
|
53
55
|
});
|
|
54
56
|
if (!policy || !policy.policyName || !policy.policyHmac)
|
|
55
57
|
throw new Error(`Error: the descriptor's policy is not registered`);
|
|
56
|
-
const walletPolicy = new
|
|
57
|
-
ledgerSignatures = await ledgerClient.signPsbt(new
|
|
58
|
+
const walletPolicy = new WalletPolicy(policy.policyName, ledgerTemplate, keyRoots);
|
|
59
|
+
ledgerSignatures = await ledgerClient.signPsbt(new PsbtV2().fromBitcoinJS(psbt), walletPolicy, policy.policyHmac);
|
|
58
60
|
}
|
|
59
61
|
//Add the signatures to the Psbt object using PartialSig format:
|
|
60
62
|
psbt.updateInput(index, {
|
|
@@ -66,6 +68,9 @@ exports.signInputLedger = signInputLedger;
|
|
|
66
68
|
//it clusters together wallet policy types before signing
|
|
67
69
|
//it throws if it cannot sign any input.
|
|
68
70
|
async function signLedger({ psbt, descriptors, ledgerClient, ledgerState }) {
|
|
71
|
+
const { PsbtV2, DefaultWalletPolicy, WalletPolicy, AppClient } = await (0, ledger_1.importAndValidateLedgerBitcoin)(ledgerClient);
|
|
72
|
+
if (!(ledgerClient instanceof AppClient))
|
|
73
|
+
throw new Error(`Error: pass a valid ledgerClient`);
|
|
69
74
|
const ledgerPolicies = [];
|
|
70
75
|
for (const descriptor of descriptors) {
|
|
71
76
|
const policy = (await (0, ledger_1.ledgerPolicyFromState)({
|
|
@@ -95,12 +100,12 @@ async function signLedger({ psbt, descriptors, ledgerClient, ledgerState }) {
|
|
|
95
100
|
uniquePolicy.policyHmac &&
|
|
96
101
|
uniquePolicy.policyId) {
|
|
97
102
|
//non-standard policy
|
|
98
|
-
const walletPolicy = new
|
|
99
|
-
ledgerSignatures = await ledgerClient.signPsbt(new
|
|
103
|
+
const walletPolicy = new WalletPolicy(uniquePolicy.policyName, uniquePolicy.ledgerTemplate, uniquePolicy.keyRoots);
|
|
104
|
+
ledgerSignatures = await ledgerClient.signPsbt(new PsbtV2().fromBitcoinJS(psbt), walletPolicy, uniquePolicy.policyHmac);
|
|
100
105
|
}
|
|
101
106
|
else {
|
|
102
107
|
//standard policy
|
|
103
|
-
ledgerSignatures = await ledgerClient.signPsbt(new
|
|
108
|
+
ledgerSignatures = await ledgerClient.signPsbt(new PsbtV2().fromBitcoinJS(psbt), new DefaultWalletPolicy(uniquePolicy.ledgerTemplate, uniquePolicy.keyRoots[0]), null);
|
|
104
109
|
}
|
|
105
110
|
for (const [index, ,] of ledgerSignatures) {
|
|
106
111
|
psbt.updateInput(index, {
|
package/dist/types.d.ts
CHANGED
|
@@ -47,7 +47,8 @@ export interface ParseKeyExpression {
|
|
|
47
47
|
export interface Expand {
|
|
48
48
|
(params: {
|
|
49
49
|
expression: string;
|
|
50
|
-
|
|
50
|
+
index?: number;
|
|
51
|
+
checksumRequired?: boolean;
|
|
51
52
|
network?: Network;
|
|
52
53
|
allowMiniscriptInP2SH?: boolean;
|
|
53
54
|
}): {
|
|
@@ -59,6 +60,8 @@ export interface Expand {
|
|
|
59
60
|
expandedMiniscript?: string;
|
|
60
61
|
redeemScript?: Buffer;
|
|
61
62
|
witnessScript?: Buffer;
|
|
63
|
+
isRanged: boolean;
|
|
64
|
+
canonicalExpression: string;
|
|
62
65
|
};
|
|
63
66
|
}
|
|
64
67
|
interface XOnlyPointAddTweakResult {
|
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": "1.0.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",
|
|
@@ -52,20 +52,26 @@
|
|
|
52
52
|
"files": [
|
|
53
53
|
"dist"
|
|
54
54
|
],
|
|
55
|
-
"COMMENT wrt ledger-bitcoin below": "ledger-bitcoin is installed using an alias. This package can be replaced with the official `ledger-bitcoin` npm package once [this PR](https://github.com/LedgerHQ/app-bitcoin-new/pull/147) is published to npm (likely in version > 0.2.1)",
|
|
56
55
|
"dependencies": {
|
|
57
56
|
"@bitcoinerlab/miniscript": "^1.2.1",
|
|
58
57
|
"@bitcoinerlab/secp256k1": "^1.0.2",
|
|
59
58
|
"bip32": "^3.1.0",
|
|
60
59
|
"bitcoinjs-lib": "^6.1.0",
|
|
61
|
-
"ecpair": "^2.1.0"
|
|
62
|
-
|
|
60
|
+
"ecpair": "^2.1.0"
|
|
61
|
+
},
|
|
62
|
+
"peerDependencies": {
|
|
63
|
+
"ledger-bitcoin": "^0.2.2"
|
|
64
|
+
},
|
|
65
|
+
"peerDependenciesMeta": {
|
|
66
|
+
"ledger-bitcoin": {
|
|
67
|
+
"optional": true
|
|
68
|
+
}
|
|
63
69
|
},
|
|
64
70
|
"devDependencies": {
|
|
65
71
|
"@babel/plugin-transform-modules-commonjs": "^7.20.11",
|
|
66
72
|
"@ledgerhq/hw-transport-node-hid": "^6.27.12",
|
|
67
|
-
"@typescript-eslint/eslint-plugin": "^5.
|
|
68
|
-
"@typescript-eslint/parser": "^5.
|
|
73
|
+
"@typescript-eslint/eslint-plugin": "^5.60.1",
|
|
74
|
+
"@typescript-eslint/parser": "^5.60.1",
|
|
69
75
|
"babel-plugin-transform-import-meta": "^2.2.0",
|
|
70
76
|
"better-docs": "^2.7.2",
|
|
71
77
|
"bip39": "^3.0.4",
|
|
@@ -77,9 +83,11 @@
|
|
|
77
83
|
"fs": "^0.0.1-security",
|
|
78
84
|
"jest": "^29.4.3",
|
|
79
85
|
"jsdoc": "^3.6.11",
|
|
86
|
+
"ledger-bitcoin": "^0.2.2",
|
|
80
87
|
"path": "^0.12.7",
|
|
81
88
|
"prettier": "^2.8.8",
|
|
82
89
|
"regtest-client": "^0.2.0",
|
|
83
|
-
"ts-node-dev": "^2.0.0"
|
|
90
|
+
"ts-node-dev": "^2.0.0",
|
|
91
|
+
"typescript": "5.0"
|
|
84
92
|
}
|
|
85
93
|
}
|
package/README.draft.md
DELETED
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
A JavaScript library for parsing and signing Bitcoin descriptors, including those based on the Miniscript language.
|
|
2
|
-
|
|
3
|
-
Key features:
|
|
4
|
-
It allows signing using Hierarchical Deterministic keeps (those that use word mnemonics) and also Hardware Wallets (the Ledger wallet so far; more are comming).
|
|
5
|
-
It integrates miniscript, making it super easy to program advanced transactions. For example, timelock-based vaults, multisigs vaults or inheritance are now a breeze.
|
|
6
|
-
Dessigned to be used with psbt (Partially Signed Bitcion transactions) so that it can be used in mkuti-party scenarios.
|
|
7
|
-
Can be used with Typescript and Javascript.
|
|
8
|
-
Integrates with the bitcoinjs-lib family of modules.
|
|
9
|
-
|
|
10
|
-
Usage:
|
|
11
|
-
|
|
12
|
-
Installation:
|
|
13
|
-
npm install @bitcoinerlab/descriptors
|
|
14
|
-
|
|
15
|
-
Usage:
|
|
16
|
-
|
|
17
|
-
This section describes how to use this library, starting from a very easy transaction and ends up using a smart contract that only allows spending a transaction after a certain time, providing a certain secret (a preimage) and can only be unlocked by co-signing with a Ledger wallet and a Software Wallet.
|
|
18
|
-
|
|
19
|
-
We will show examples that have been run on the testnet network. We share the mnemonic we used so that you can replicate the transactions: . You won't be able to spend the transactions again but you will be able to replicate everything.
|
|
20
|
-
|
|
21
|
-
```
|
|
22
|
-
MNEMONIC: "drum turtle globe inherit autumn flavor slice illness sniff distance carbon elder"
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
TIP: you can use https://iancoleman.io/bip39/ to verify some of the steps below.
|
|
26
|
-
|
|
27
|
-
Simple case using standard transactions:
|
|
28
|
-
|
|
29
|
-
Let's start with an easy case. Let's send some sats from a Bitcoin Legacy address to a Segwit one.
|
|
30
|
-
|
|
31
|
-
We are ready to use the library. Here we skip all initialization. You will be able to see the complete code on a shared repository with the tests.
|
|
32
|
-
|
|
33
|
-
Examples are written using Javascript. If you want to use Typescript (recommended), please take a look to the test/integration folder for some advanced use use cases.
|
|
34
|
-
Please, note that the code below is just for explanation purposes. When writting code for production you must assert all intermediate steps.
|
|
35
|
-
|
|
36
|
-
```javascript
|
|
37
|
-
//NETWORK is set to testnet
|
|
38
|
-
const masterNode = BIP32.fromSeed(mnemonicToSeedSync(MNEMONIC), NETWORK);
|
|
39
|
-
const descriptorLegacy = pkhBIP32({ masterNode, network: NETWORK, account: 0, change: 0, index: 1 });
|
|
40
|
-
|
|
41
|
-
console.log(descriptorLegacy.getAddress()); //moovc1JqGrz4v6FA2U8ks8ZqjSwjv3yRKQ
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
So we sent some sats (`1679037`) to that address above. Those sats were sent in `TX_ID = "ee02b5a12c2f22e892bed376781fc9ed435f0d192a1b67ca47a7190804d8e868"`. [See it here](https://blockstream.info/testnet/tx/ee02b5a12c2f22e892bed376781fc9ed435f0d192a1b67ca47a7190804d8e868).
|
|
45
|
-
|
|
46
|
-
Now let's spend them. We will send all the funds, except for a fee to our first internal address (change 1) in account 0 of the P2WPKH (Segwit) wallet:
|
|
47
|
-
|
|
48
|
-
<!--https://coinfaucet.eu/en/btc-testnet/-->
|
|
49
|
-
|
|
50
|
-
```javascript
|
|
51
|
-
const TX_ID = "ee02b5a12c2f22e892bed376781fc9ed435f0d192a1b67ca47a7190804d8e868";
|
|
52
|
-
const FEE = 100;
|
|
53
|
-
|
|
54
|
-
//Define the Segwit descriptor where we will receive the sats:
|
|
55
|
-
const descriptorSegwit = wpkhBIP32({ masterNode, network: NETWORK, account: 0, change: 1, index: 0 });
|
|
56
|
-
|
|
57
|
-
//Get some info from the network about the Legacy utxo we will spend from:
|
|
58
|
-
const txHex = await (await fetch(`https://blockstream.info/testnet/api/tx/${TX_ID}/hex`)).text();
|
|
59
|
-
const txOuts = (await (await fetch(`https://blockstream.info/testnet/api/tx/${TX_ID}`)).json()).vout;
|
|
60
|
-
const vout = txOuts.findIndex(txOut => txOut.scriptpubkey === descriptorLegacy.getScriptPubKey());
|
|
61
|
-
const initialValue = txOut[vout].value; //1679037, as mentioned above
|
|
62
|
-
|
|
63
|
-
//Create a transaction (Partially Signed Bitcoin Transaction) now:
|
|
64
|
-
const psbt = new Psbt({network: NETWORK});
|
|
65
|
-
//Update the transaction with the Legacy descriptor input:
|
|
66
|
-
const legacyInputNumber = descriptorLegacy.updatePsbt({ psbt, vout, txHex });
|
|
67
|
-
//Now add the Segwit address as the new output. Let's give some FEE to the miners
|
|
68
|
-
psbt.addOutput({ address: descriptorSegwit.getAddress(), value: initialValue - FEE});
|
|
69
|
-
|
|
70
|
-
//Sign the transaction, finalize it and submit it to the miners:
|
|
71
|
-
signBIP32({ psbt, masterNode });
|
|
72
|
-
descriptorLegacy.finalizePsbtInput({psbt, index: legacyInputNumber});
|
|
73
|
-
const spendTx = psbt.extractTransaction();
|
|
74
|
-
console.log(spendTx.getId()); //
|
|
75
|
-
await fetch('https://blockstream.info/testnet/api/tx', {
|
|
76
|
-
method: 'POST',
|
|
77
|
-
body: spendTx.toHex()
|
|
78
|
-
});
|
|
79
|
-
```
|
|
80
|
-
We have sent the 1679037 - 100 to ourselves in just a few lines.
|
|
81
|
-
Easy! See the tx here: ....
|
|
82
|
-
This has only started. Let's do really cool stuff now.
|
|
83
|
-
|
|
84
|
-
|