@bitcoinerlab/descriptors 2.3.6 → 3.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 +173 -77
- package/dist/applyPR2137.js +36 -17
- package/dist/bitcoinjs-lib-internals.d.ts +10 -0
- package/dist/bitcoinjs-lib-internals.js +18 -0
- package/dist/descriptors.d.ts +161 -392
- package/dist/descriptors.js +512 -281
- package/dist/index.d.ts +2 -29
- package/dist/index.js +0 -14
- package/dist/keyExpressions.d.ts +4 -13
- package/dist/keyExpressions.js +15 -18
- package/dist/ledger.d.ts +14 -37
- package/dist/ledger.js +118 -100
- package/dist/miniscript.d.ts +20 -6
- package/dist/miniscript.js +59 -17
- package/dist/multipath.d.ts +13 -0
- package/dist/multipath.js +76 -0
- package/dist/networkUtils.d.ts +3 -0
- package/dist/networkUtils.js +16 -0
- package/dist/parseUtils.d.ts +7 -0
- package/dist/parseUtils.js +46 -0
- package/dist/psbt.d.ts +17 -13
- package/dist/psbt.js +34 -50
- package/dist/resourceLimits.d.ts +25 -0
- package/dist/resourceLimits.js +89 -0
- package/dist/scriptExpressions.d.ts +29 -77
- package/dist/scriptExpressions.js +19 -16
- package/dist/signers.d.ts +1 -21
- package/dist/signers.js +85 -129
- package/dist/stackResourceLimits.d.ts +17 -0
- package/dist/stackResourceLimits.js +35 -0
- package/dist/tapMiniscript.d.ts +193 -0
- package/dist/tapMiniscript.js +428 -0
- package/dist/tapTree.d.ts +76 -0
- package/dist/tapTree.js +163 -0
- package/dist/types.d.ts +46 -6
- package/package.json +13 -13
package/README.md
CHANGED
|
@@ -1,12 +1,64 @@
|
|
|
1
1
|
# Bitcoin Descriptors Library
|
|
2
2
|
|
|
3
|
-
This library is designed to parse and create Bitcoin Descriptors, including Miniscript
|
|
3
|
+
This library is designed to parse and create Bitcoin Descriptors, including Miniscript and Taproot script trees and generate Partially Signed Bitcoin Transactions (PSBTs). It also provides PSBT signers and finalizers for single-signature, BIP32 and Hardware Wallets.
|
|
4
|
+
|
|
5
|
+
## TL;DR (quick start)
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @bitcoinerlab/descriptors @bitcoinerlab/secp256k1 @bitcoinerlab/miniscript-policies
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
This quick example compiles a timelocked Miniscript policy, creates a descriptor address to fund, then builds, signs, and finalizes a PSBT that spends that funded UTXO and prints the final transaction hex.
|
|
12
|
+
|
|
13
|
+
```javascript
|
|
14
|
+
const ecpair = ECPair.makeRandom(); // Creates a signer for a single-key wallet
|
|
15
|
+
|
|
16
|
+
// Timelocked policy: signature + relative timelock (older)
|
|
17
|
+
const { miniscript } = compilePolicy('and(pk(@bob),older(10))');
|
|
18
|
+
|
|
19
|
+
const descriptor = `wsh(${miniscript.replace('@bob', toHex(ecpair.publicKey))})`;
|
|
20
|
+
|
|
21
|
+
// 1) Build the output description
|
|
22
|
+
const fundedOutput = new Output({ descriptor });
|
|
23
|
+
const address = fundedOutput.getAddress(); // Fund this address
|
|
24
|
+
|
|
25
|
+
// 2) Prepare PSBT input/output
|
|
26
|
+
const psbt = new Psbt();
|
|
27
|
+
|
|
28
|
+
const txHex = 'FUNDING_TX_HEX'; // hex of the tx that funded the address above
|
|
29
|
+
const vout = 0; // Output index (vout) of that UTXO within FUNDING_TX_HEX
|
|
30
|
+
const finalizeInput = fundedOutput.updatePsbtAsInput({ psbt, txHex, vout });
|
|
31
|
+
|
|
32
|
+
const recipient = new Output({
|
|
33
|
+
descriptor: 'addr(bc1qgw6xanldsz959z45y4dszehx4xkuzf7nfhya8x)'
|
|
34
|
+
}); // Final address where we'll send the funds after timelock
|
|
35
|
+
recipient.updatePsbtAsOutput({ psbt, value: 10000n }); // input covers this+fees
|
|
36
|
+
|
|
37
|
+
// 3) Sign and finalize
|
|
38
|
+
signers.signECPair({ psbt, ecpair });
|
|
39
|
+
finalizeInput({ psbt });
|
|
40
|
+
|
|
41
|
+
console.log('Push this: ' + psbt.extractTransaction().toHex());
|
|
42
|
+
```
|
|
4
43
|
|
|
5
44
|
## Features
|
|
6
45
|
|
|
7
46
|
- Parses and creates [Bitcoin Descriptors](https://github.com/bitcoin/bitcoin/blob/master/doc/descriptors.md) (including those based on the [Miniscript language](https://bitcoinerlab.com/modules/miniscript)).
|
|
47
|
+
- Supports Taproot descriptors with trees: `tr(KEY,TREE)` (tapscript).
|
|
8
48
|
- Generates Partially Signed Bitcoin Transactions (PSBTs).
|
|
9
|
-
- Provides PSBT finalizers and signers for single-signature, BIP32
|
|
49
|
+
- Provides PSBT finalizers and signers for single-signature, BIP32 and Hardware Wallets (currently supports Ledger devices; more devices are planned).
|
|
50
|
+
|
|
51
|
+
### Version Compatibility
|
|
52
|
+
|
|
53
|
+
Starting in `3.x`, this library is aligned with the modern bitcoinjs stack (`bitcoinjs-lib 7.x`).
|
|
54
|
+
|
|
55
|
+
In practical terms, this means:
|
|
56
|
+
|
|
57
|
+
- byte arrays are represented as `Uint8Array`;
|
|
58
|
+
- satoshi values are represented as `bigint`.
|
|
59
|
+
|
|
60
|
+
If you need older bitcoinjs versions, keep using `@bitcoinerlab/descriptors 2.x`.
|
|
61
|
+
If you want Taproot trees (`tr(KEY,TREE)`), use `3.x`.
|
|
10
62
|
|
|
11
63
|
## Concepts
|
|
12
64
|
|
|
@@ -23,7 +75,7 @@ In Bitcoin, a transaction consists of a set of inputs that are spent into a diff
|
|
|
23
75
|
|
|
24
76
|
For example, `wpkh(02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9)` is a descriptor that describes a pay-to-witness-public-key-hash (P2WPKH) type of output with the specified public key. If you know the corresponding private key for the transaction for which this descriptor is an output, you can spend it.
|
|
25
77
|
|
|
26
|
-
Descriptors can express much more complex conditions, such as multi-party cooperation, time-locked outputs
|
|
78
|
+
Descriptors can express much more complex conditions, such as multi-party cooperation, time-locked outputs and more. These conditions can be expressed using the Bitcoin Miniscript language, which is a way of writing Bitcoin Scripts in a structured and more easily understandable way.
|
|
27
79
|
|
|
28
80
|
### Partially Signed Bitcoin Transactions (PSBTs)
|
|
29
81
|
|
|
@@ -37,7 +89,7 @@ PSBTs come in handy when working with descriptors, especially when using scripts
|
|
|
37
89
|
|
|
38
90
|
Before we dive in, it's worth mentioning that we have several comprehensive guides available covering different aspects of the library. These guides provide explanations and code examples in interactive playgrounds, allowing you to see the changes in the output as you modify the code. This hands-on learning experience, combined with clear explanations, helps you better understand how to use the library effectively. [Check out the available guides here](https://bitcoinerlab.com/guides).
|
|
39
91
|
|
|
40
|
-
Furthermore, we've meticulously documented our API. For an in-depth look into Classes, functions
|
|
92
|
+
Furthermore, we've meticulously documented our API. For an in-depth look into Classes, functions and types, head over [here](https://bitcoinerlab.com/modules/descriptors/api).
|
|
41
93
|
|
|
42
94
|
To use this library (and accompanying libraries), you can install them using:
|
|
43
95
|
|
|
@@ -73,21 +125,37 @@ const wpkhOutput = new Output({
|
|
|
73
125
|
});
|
|
74
126
|
```
|
|
75
127
|
|
|
76
|
-
For
|
|
128
|
+
For advanced spend-path control (for example `signersPubKeys`, `taprootSpendPath`, and `tapLeaf`), see [Spending-path selection: from WSH Miniscript to Taproot](#spending-path-selection-from-wsh-miniscript-to-taproot).
|
|
129
|
+
|
|
130
|
+
Detailed information about constructor parameters can be found in [the API documentation](https://bitcoinerlab.com/modules/descriptors/api/classes/_Internal_.Output.html#constructor) and in [this Stack Exchange answer](https://bitcoin.stackexchange.com/a/118036/89665).
|
|
131
|
+
|
|
132
|
+
Some commonly used constructor parameters are:
|
|
133
|
+
|
|
134
|
+
- `index`: for ranged descriptors ending in `*`.
|
|
135
|
+
- `change`: for multipath key expressions such as `/**` or `/<0;1>/*`. For example, in `/<0;1>/*`, use `change: 0` or `change: 1`.
|
|
77
136
|
|
|
78
137
|
The `Output` class [offers various helpful methods](https://bitcoinerlab.com/modules/descriptors/api/classes/_Internal_.Output.html), including `getAddress()`, which returns the address associated with the descriptor, `getScriptPubKey()`, which returns the `scriptPubKey` for the descriptor, `expand()`, which decomposes a descriptor into its elemental parts, `updatePsbtAsInput()` and `updatePsbtAsOutput()`.
|
|
79
138
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
-
|
|
139
|
+
The library supports a wide range of descriptor types, including:
|
|
140
|
+
|
|
141
|
+
- Pay-to-Public-Key-Hash (P2PKH): `pkh(KEY)`
|
|
142
|
+
- Pay-to-Witness-Public-Key-Hash (P2WPKH): `wpkh(KEY)`
|
|
143
|
+
- Pay-to-Script-Hash (P2SH): `sh(SCRIPT)`
|
|
144
|
+
- Pay-to-Witness-Script-Hash (P2WSH): `wsh(SCRIPT)`
|
|
145
|
+
- Pay-to-Taproot (P2TR) with key-only or script tree: `tr(KEY)` and `tr(KEY,TREE)`
|
|
146
|
+
- Address-based descriptors: `addr(ADDRESS)`
|
|
147
|
+
|
|
148
|
+
These descriptors can be used with various key expressions, including raw public keys, BIP32 derivation paths and more.
|
|
149
|
+
|
|
150
|
+
For example, a Taproot descriptor with script leaves can look like:
|
|
151
|
+
|
|
152
|
+
```
|
|
153
|
+
tr(INTERNAL_KEY,{pk(KEY_A),{pk(KEY_B),and_v(v:pk(KEY_C),older(144))}})
|
|
154
|
+
```
|
|
87
155
|
|
|
88
|
-
|
|
156
|
+
This means the output has an internal-key spend path and additional script-path options inside the tree.
|
|
89
157
|
|
|
90
|
-
The `updatePsbtAsInput()` method is an essential part of the library, responsible for adding an input to the PSBT corresponding to the UTXO
|
|
158
|
+
The `updatePsbtAsInput()` method is an essential part of the library, responsible for adding an input to the PSBT corresponding to the UTXO described by the descriptor. Additionally, when the descriptor expresses an absolute time-spending condition, such as "This UTXO can only be spent after block N", `updatePsbtAsInput()` adds timelock information to the PSBT.
|
|
91
159
|
|
|
92
160
|
To call `updatePsbtAsInput()`, use the following syntax:
|
|
93
161
|
|
|
@@ -101,68 +169,100 @@ Here, `psbt` refers to an instance of the [bitcoinjs-lib Psbt class](https://git
|
|
|
101
169
|
|
|
102
170
|
The method returns the `inputFinalizer()` function. This finalizer function completes a PSBT input by adding the unlocking script (`scriptWitness` or `scriptSig`) that satisfies the previous output's spending conditions. Bear in mind that both `scriptSig` and `scriptWitness` incorporate signatures. As such, you should complete all necessary signing operations before calling `inputFinalizer()`. Detailed [explanations on the `inputFinalizer` method](#signers-and-finalizers-finalize-psbt-input) can be found in the Signers and Finalizers section.
|
|
103
171
|
|
|
104
|
-
|
|
172
|
+
Similarly, `updatePsbtAsOutput` allows you to add an output to a PSBT. For instance, to configure a `psbt` that sends `10,000` sats to the SegWit address `bc1qgw6xanldsz959z45y4dszehx4xkuzf7nfhya8x`:
|
|
105
173
|
|
|
106
174
|
```javascript
|
|
107
|
-
const recipientOutput =
|
|
108
|
-
|
|
109
|
-
|
|
175
|
+
const recipientOutput = new Output({
|
|
176
|
+
descriptor: `addr(bc1qgw6xanldsz959z45y4dszehx4xkuzf7nfhya8x)`
|
|
177
|
+
});
|
|
178
|
+
recipientOutput.updatePsbtAsOutput({ psbt, value: 10000n });
|
|
110
179
|
```
|
|
111
180
|
|
|
112
|
-
For further information on using the `Output` class, refer to the [comprehensive guides](https://bitcoinerlab.com/guides) that offer explanations and playgrounds to help learn the module. For specific details on the methods, refer directly to [the API](https://bitcoinerlab.com/modules/descriptors/api/classes/_Internal_.Output.html).
|
|
181
|
+
For further information on using the `Output` class, refer to the [comprehensive guides](https://bitcoinerlab.com/guides) that offer explanations and playgrounds to help learn the module. For specific details on the methods, refer directly to [the API](https://bitcoinerlab.com/modules/descriptors/api/classes/_Internal_.Output.html).
|
|
113
182
|
|
|
114
183
|
#### Parsing Descriptors with `expand()`
|
|
115
184
|
|
|
116
|
-
|
|
185
|
+
Most applications do not need `expand()` for normal receive/spend flows. It is mainly useful for debugging and introspection (for example, checking expanded expressions, key mappings, scripts, and parsed metadata).
|
|
186
|
+
|
|
187
|
+
```javascript
|
|
188
|
+
const { expand } = descriptors.DescriptorsFactory(ecc);
|
|
189
|
+
const info = expand({ descriptor });
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
For full details on returned fields, refer to [the API](https://bitcoinerlab.com/modules/descriptors/api/types/Expansion.html).
|
|
193
|
+
|
|
194
|
+
#### Spending-path selection: from WSH Miniscript to Taproot
|
|
117
195
|
|
|
118
|
-
|
|
196
|
+
When a descriptor has more than one valid way to spend, the library needs to know which path you intend to use.
|
|
119
197
|
|
|
120
|
-
|
|
198
|
+
For `wsh(miniscript)` and `sh(wsh(miniscript))`, this is usually done with `signersPubKeys`: you pass the public keys expected to sign and the library selects the most optimal satisfiable branch.
|
|
199
|
+
|
|
200
|
+
`signersPubKeys` is passed as an array of public keys (`Uint8Array[]`).
|
|
201
|
+
If omitted, the library assumes all descriptor keys may sign.
|
|
202
|
+
|
|
203
|
+
Example with two BIP32-derived keys and one preferred signer:
|
|
121
204
|
|
|
122
205
|
```javascript
|
|
123
|
-
|
|
124
|
-
|
|
206
|
+
import { randomBytes } from 'crypto';
|
|
207
|
+
import * as ecc from '@bitcoinerlab/secp256k1';
|
|
208
|
+
import { DescriptorsFactory, keyExpressionBIP32 } from '@bitcoinerlab/descriptors';
|
|
209
|
+
|
|
210
|
+
const { Output, BIP32 } = DescriptorsFactory(ecc);
|
|
211
|
+
|
|
212
|
+
const masterNode = BIP32.fromSeed(randomBytes(64));
|
|
213
|
+
|
|
214
|
+
const originPath = "/84'/0'/0'";
|
|
215
|
+
|
|
216
|
+
const keyPathA = '/0/0';
|
|
217
|
+
const keyPathB = '/0/1';
|
|
218
|
+
|
|
219
|
+
const keyExprA = keyExpressionBIP32({ masterNode, originPath, keyPath: keyPathA });
|
|
220
|
+
const keyExprB = keyExpressionBIP32({ masterNode, originPath, keyPath: keyPathB });
|
|
221
|
+
|
|
222
|
+
const signerPubKeyA = masterNode.derivePath( `m${originPath}${keyPathA}`).publicKey;
|
|
223
|
+
|
|
224
|
+
// Two possible branches:
|
|
225
|
+
// - branch 1: signature by keyA + older(10)
|
|
226
|
+
// - branch 2: signature by keyB (no timelock)
|
|
227
|
+
const output = new Output({
|
|
228
|
+
descriptor: `wsh(andor(pk(${keyExprA}),older(10),pk(${keyExprB})))`,
|
|
229
|
+
signersPubKeys: [signerPubKeyA] // choose the keyA (timelock branch)
|
|
230
|
+
});
|
|
125
231
|
```
|
|
126
232
|
|
|
127
|
-
|
|
233
|
+
Taproot uses the same idea. For `tr(KEY,TREE)`, `signersPubKeys` helps determine which leaves are satisfiable and which satisfiable path is more optimal. In addition, Taproot provides two optional controls:
|
|
128
234
|
|
|
129
|
-
|
|
235
|
+
- `taprootSpendPath` (`'key' | 'script'`) to force key-path or script-path spending.
|
|
236
|
+
- `tapLeaf` to force a specific script leaf when using script path.
|
|
237
|
+
|
|
238
|
+
If `taprootSpendPath` is omitted for `tr(KEY,TREE)`, the library uses script path and auto-selects the most optimal satisfiable leaf from available spending data (including `signersPubKeys` and preimages when relevant).
|
|
239
|
+
|
|
240
|
+
If you specifically plan to spend from the internal key, set:
|
|
130
241
|
|
|
131
242
|
```javascript
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
243
|
+
new Output({
|
|
244
|
+
descriptor: 'tr(INTERNAL_KEY,{pk(KEY_A),pk(KEY_B)})',
|
|
245
|
+
taprootSpendPath: 'key'
|
|
135
246
|
});
|
|
136
247
|
```
|
|
137
248
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
For illustration, given the descriptor above, the corresponding `expandedExpression` and a section of the `expansionMap` would appear as:
|
|
249
|
+
If you want to force a specific script leaf:
|
|
141
250
|
|
|
142
251
|
```javascript
|
|
143
|
-
{
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
'0252972572d465d016d4c501887b8df303eee3ed602c056b1eb09260dfa0da0ab2'
|
|
149
|
-
},
|
|
150
|
-
'@1': {
|
|
151
|
-
keyExpression:
|
|
152
|
-
"[d34db33f/49'/0'/0']tpubDCdxmvzJ5QBjTN8oCjjyT2V58AyZvA1fkmCeZRC75QMoaHcVP2m45Bv3hmnR7ttAwkb2UNYyoXdHVt4gwBqRrJqLUU2JrM43HippxiWpHra/1/2/3/4/*",
|
|
153
|
-
keyPath: '/1/2/3/4/*',
|
|
154
|
-
originPath: "/49'/0'/0'",
|
|
155
|
-
path: "m/49'/0'/0'/1/2/3/4/*",
|
|
156
|
-
// Other relevant properties returned: `pubkey`, `ecpair` & `bip32` interfaces, `masterFingerprint`, etc.
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
//...
|
|
160
|
-
}
|
|
252
|
+
new Output({
|
|
253
|
+
descriptor: 'tr(INTERNAL_KEY,{pk(KEY_A),pk(KEY_B)})',
|
|
254
|
+
taprootSpendPath: 'script',
|
|
255
|
+
tapLeaf: 'pk(KEY_A)'
|
|
256
|
+
});
|
|
161
257
|
```
|
|
162
258
|
|
|
259
|
+
These spending-path parameters (`signersPubKeys`, `taprootSpendPath`, `tapLeaf`) are only needed when spending/finalizing UTXOs with multiple candidate paths. They are not needed just to derive addresses or `scriptPubKeys`.
|
|
260
|
+
|
|
261
|
+
For a focused walkthrough of constructor choices (including `signersPubKeys`) and practical usage of `updatePsbtAsInput`, `getAddress` and `getScriptPubKey`, see this [Stack Exchange answer](https://bitcoin.stackexchange.com/a/118036/89665).
|
|
262
|
+
|
|
163
263
|
### Signers and Finalizers
|
|
164
264
|
|
|
165
|
-
This library encompasses a PSBT finalizer as well as three distinct signers: ECPair for single-signatures, BIP32
|
|
265
|
+
This library encompasses a PSBT finalizer as well as three distinct signers: ECPair for single-signatures, BIP32 and Ledger (specifically crafted for Ledger Wallet devices, with upcoming support for other devices planned).
|
|
166
266
|
|
|
167
267
|
To incorporate these functionalities, use the following import statement:
|
|
168
268
|
|
|
@@ -191,7 +291,7 @@ Detailed information on Ledger integration will be provided in subsequent sectio
|
|
|
191
291
|
|
|
192
292
|
When finalizing the `psbt`, the [`updatePsbtAsInput` method](https://bitcoinerlab.com/modules/descriptors/api/classes/_Internal_.Output.html#updatePsbtAsInput) plays a key role. When invoked, the `output.updatePsbtAsInput()` sets up the `psbt` by designating the output as an input and, if required, adjusts the transaction locktime. In addition, it returns a `inputFinalizer` function tailored for this specific `psbt` input.
|
|
193
293
|
|
|
194
|
-
##### Procedure
|
|
294
|
+
##### Procedure
|
|
195
295
|
|
|
196
296
|
1. For each unspent output from a previous transaction that you're referencing in a `psbt` as an input to be spent, call the `updatePsbtAsInput` method:
|
|
197
297
|
|
|
@@ -205,11 +305,11 @@ When finalizing the `psbt`, the [`updatePsbtAsInput` method](https://bitcoinerla
|
|
|
205
305
|
inputFinalizer({ psbt });
|
|
206
306
|
```
|
|
207
307
|
|
|
208
|
-
##### Important Notes
|
|
308
|
+
##### Important Notes
|
|
209
309
|
|
|
210
310
|
- The finalizer function returned from `updatePsbtAsInput` adds the necessary unlocking script (`scriptWitness` or `scriptSig`) that satisfies the `Output`'s spending conditions. Remember, both `scriptSig` and `scriptWitness` contain signatures. Ensure that all necessary signing operations are completed before finalizing.
|
|
211
311
|
|
|
212
|
-
- When using `updatePsbtAsInput`, the `txHex` parameter is crucial. For Segwit inputs, you can choose to pass `txId` and `value` instead of `txHex
|
|
312
|
+
- When using `updatePsbtAsInput`, the `txHex` parameter is crucial. For Segwit inputs, you can choose to pass `txId` and `value` instead of `txHex` (`value` is `bigint` in v3). However, ensure the accuracy of the `value` to avoid potential fee attacks. When unsure, use `txHex` and skip `txId` and `value`.
|
|
213
313
|
|
|
214
314
|
- Hardware wallets require the [full `txHex` for Segwit](https://blog.trezor.io/details-of-firmware-updates-for-trezor-one-version-1-9-1-and-trezor-model-t-version-2-3-1-1eba8f60f2dd).
|
|
215
315
|
|
|
@@ -221,7 +321,7 @@ This library also provides a series of function helpers designed to streamline t
|
|
|
221
321
|
import { scriptExpressions } from '@bitcoinerlab/descriptors';
|
|
222
322
|
```
|
|
223
323
|
|
|
224
|
-
Within the `scriptExpressions` module, there are functions designed to generate descriptors for commonly used scripts. Some examples include `pkhBIP32()`, `shWpkhBIP32()`, `wpkhBIP32()`, `pkhLedger()`, `shWpkhLedger()
|
|
324
|
+
Within the `scriptExpressions` module, there are functions designed to generate descriptors for commonly used scripts. Some examples include `pkhBIP32()`, `shWpkhBIP32()`, `wpkhBIP32()`, `pkhLedger()`, `shWpkhLedger()` and `wpkhLedger()`. Refer to [the API](https://bitcoinerlab.com/modules/descriptors/api/modules/scriptExpressions.html#expand) for a detailed list and further information.
|
|
225
325
|
|
|
226
326
|
When using BIP32-based descriptors, the following parameters are required for the `scriptExpressions` functions:
|
|
227
327
|
|
|
@@ -237,17 +337,14 @@ pkhBIP32(params: {
|
|
|
237
337
|
})
|
|
238
338
|
```
|
|
239
339
|
|
|
240
|
-
For functions suffixed with
|
|
340
|
+
For functions suffixed with _Ledger_ (designed to generate descriptors for Ledger Hardware devices), replace `masterNode` with `ledgerManager`. Detailed information on Ledger integration will be provided in the following section.
|
|
241
341
|
|
|
242
342
|
The `keyExpressions` category includes functions that generate string representations of key expressions for public keys.
|
|
243
343
|
|
|
244
344
|
This library includes the following `keyExpressions`: [`keyExpressionBIP32`](https://bitcoinerlab.com/modules/descriptors/api/functions/keyExpressionBIP32.html) and [`keyExpressionLedger`](https://bitcoinerlab.com/modules/descriptors/api/functions/keyExpressionLedger.html). They can be imported as follows:
|
|
245
345
|
|
|
246
346
|
```javascript
|
|
247
|
-
import {
|
|
248
|
-
keyExpressionBIP32,
|
|
249
|
-
keyExpressionLedger
|
|
250
|
-
} from '@bitcoinerlab/descriptors';
|
|
347
|
+
import { keyExpressionBIP32, keyExpressionLedger } from '@bitcoinerlab/descriptors';
|
|
251
348
|
```
|
|
252
349
|
|
|
253
350
|
The parameters required for these functions are:
|
|
@@ -258,7 +355,7 @@ function keyExpressionBIP32({
|
|
|
258
355
|
originPath: string;
|
|
259
356
|
change?: number | undefined; //0 -> external (receive), 1 -> internal (change)
|
|
260
357
|
index?: number | undefined | '*';
|
|
261
|
-
keyPath?: string | undefined; //In the case of the Ledger, keyPath
|
|
358
|
+
keyPath?: string | undefined; //In the case of the Ledger, keyPath can also use multipath (e.g. /<0;1>/number)
|
|
262
359
|
isPublic?: boolean;
|
|
263
360
|
});
|
|
264
361
|
```
|
|
@@ -266,9 +363,11 @@ function keyExpressionBIP32({
|
|
|
266
363
|
For the `keyExpressionLedger` function, you'd use `ledgerManager` instead of `masterNode`.
|
|
267
364
|
|
|
268
365
|
Both functions will generate strings that fully define BIP32 keys. For example:
|
|
366
|
+
|
|
269
367
|
```text
|
|
270
368
|
[d34db33f/44'/0'/0']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/1/*
|
|
271
369
|
```
|
|
370
|
+
|
|
272
371
|
Read [Bitcoin Core descriptors documentation](https://github.com/bitcoin/bitcoin/blob/master/doc/descriptors.md) to learn more about Key Expressions.
|
|
273
372
|
|
|
274
373
|
### Hardware Wallet Integration
|
|
@@ -280,27 +379,23 @@ Before we dive in, note that, in addition to the documentation below, it is high
|
|
|
280
379
|
To use this library with Ledger devices, you must first install Ledger support:
|
|
281
380
|
|
|
282
381
|
```bash
|
|
283
|
-
npm install ledger-bitcoin
|
|
382
|
+
npm install @ledgerhq/ledger-bitcoin @ledgerhq/hw-transport-node-hid
|
|
284
383
|
```
|
|
285
384
|
|
|
286
385
|
For Ledger device signing, import the necessary functions as follows:
|
|
287
386
|
|
|
288
387
|
```javascript
|
|
289
388
|
import Transport from '@ledgerhq/hw-transport-node-hid'; //or hw-transport-web-hid, for web
|
|
290
|
-
import { AppClient } from 'ledger-bitcoin';
|
|
389
|
+
import { AppClient } from '@ledgerhq/ledger-bitcoin';
|
|
291
390
|
import { ledger } from '@bitcoinerlab/descriptors';
|
|
292
391
|
```
|
|
293
392
|
|
|
294
|
-
Then, use the following code to assert that the Ledger app is running Bitcoin Test version 2.1.0 or higher
|
|
393
|
+
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:
|
|
295
394
|
|
|
296
395
|
```javascript
|
|
297
396
|
const transport = await Transport.create();
|
|
298
397
|
//Throws if not running Bitcoin Test >= 2.1.0
|
|
299
|
-
await ledger.assertLedgerApp({
|
|
300
|
-
transport,
|
|
301
|
-
name: 'Bitcoin Test',
|
|
302
|
-
minVersion: '2.1.0'
|
|
303
|
-
});
|
|
398
|
+
await ledger.assertLedgerApp({ transport, name: 'Bitcoin Test', minVersion: '2.1.0' });
|
|
304
399
|
|
|
305
400
|
const ledgerClient = new AppClient(transport);
|
|
306
401
|
const ledgerManager = { ledgerClient, ledgerState: {}, ecc, network };
|
|
@@ -320,7 +415,7 @@ await ledger.registerLedgerWallet({
|
|
|
320
415
|
|
|
321
416
|
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.
|
|
322
417
|
|
|
323
|
-
Finally, `ledgerManager.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
|
|
418
|
+
Finally, `ledgerManager.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.
|
|
324
419
|
|
|
325
420
|
The [API reference for the ledger module](https://bitcoinerlab.com/modules/descriptors/api/variables/ledger.html) provides a comprehensive list of functions related to the Ledger Hardware Wallet, along with detailed explanations of their parameters and behavior.
|
|
326
421
|
|
|
@@ -331,8 +426,8 @@ The [API reference for the ledger module](https://bitcoinerlab.com/modules/descr
|
|
|
331
426
|
For more information, refer to the following resources:
|
|
332
427
|
|
|
333
428
|
- **[Guides](https://bitcoinerlab.com/guides)**: Comprehensive explanations and playgrounds to help you learn how to use the module.
|
|
334
|
-
- **[API](https://bitcoinerlab.com/modules/descriptors/api)**: Dive into the details of the Classes, functions
|
|
335
|
-
- **[Stack Exchange answer](https://bitcoin.stackexchange.com/a/118036/89665)**: Focused explanation on the constructor, specifically the `signersPubKeys` parameter
|
|
429
|
+
- **[API](https://bitcoinerlab.com/modules/descriptors/api)**: Dive into the details of the Classes, functions and types.
|
|
430
|
+
- **[Stack Exchange answer](https://bitcoin.stackexchange.com/a/118036/89665)**: Focused explanation on the constructor, specifically the `signersPubKeys` parameter and the usage of `updatePsbtAsInput`, `getAddress` and `getScriptPubKey`.
|
|
336
431
|
- **[Integration tests](https://github.com/bitcoinerlab/descriptors/tree/main/test/integration)**: Well-commented code examples showcasing the usage of all functions in the module.
|
|
337
432
|
- **Local Documentation**: Generate comprehensive API documentation from the source code:
|
|
338
433
|
|
|
@@ -377,21 +472,22 @@ This will build the project and generate the necessary files in the `dist` direc
|
|
|
377
472
|
|
|
378
473
|
### Testing
|
|
379
474
|
|
|
380
|
-
Before committing any code, make sure it passes all tests.
|
|
475
|
+
Before committing any code, make sure it passes all tests.
|
|
381
476
|
|
|
382
|
-
|
|
477
|
+
Run unit tests:
|
|
383
478
|
|
|
384
479
|
```bash
|
|
385
|
-
|
|
386
|
-
docker run -d -p 8080:8080 -p 60401:60401 -p 3002:3002 bitcoinerlab/tester
|
|
480
|
+
npm run test:unit
|
|
387
481
|
```
|
|
388
482
|
|
|
389
|
-
|
|
483
|
+
Run the full test pipeline (lint + build + unit + integration):
|
|
390
484
|
|
|
391
485
|
```bash
|
|
392
486
|
npm run test
|
|
393
487
|
```
|
|
394
488
|
|
|
489
|
+
Integration tests require Docker. Make sure the `docker` command is installed and available in your PATH. When integration tests run, they automatically start or reuse a local container with the regtest services needed by this repository.
|
|
490
|
+
|
|
395
491
|
And, in case you have a Ledger device:
|
|
396
492
|
|
|
397
493
|
```bash
|
package/dist/applyPR2137.js
CHANGED
|
@@ -1,31 +1,37 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.applyPR2137 = void 0;
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const
|
|
4
|
+
//While this PR is not merged: https://github.com/bitcoinjs/bitcoinjs-lib/pull/2137
|
|
5
|
+
//The Async functions have not been "fixed"
|
|
6
|
+
//Note that a further fix (look for FIX BITCOINERLAB) was done
|
|
7
|
+
const bitcoinjs_lib_1 = require("bitcoinjs-lib");
|
|
8
|
+
const bip174_1 = require("bip174");
|
|
9
|
+
const bitcoinjs_lib_internals_1 = require("./bitcoinjs-lib-internals");
|
|
10
|
+
const uint8array_tools_1 = require("uint8array-tools");
|
|
8
11
|
const toXOnly = (pubKey) => pubKey.length === 32 ? pubKey : pubKey.slice(1, 33);
|
|
9
12
|
function range(n) {
|
|
10
13
|
return [...Array(n).keys()];
|
|
11
14
|
}
|
|
12
15
|
function tapBranchHash(a, b) {
|
|
13
|
-
return
|
|
16
|
+
return bitcoinjs_lib_1.crypto.taggedHash('TapBranch', (0, uint8array_tools_1.concat)([a, b]));
|
|
14
17
|
}
|
|
15
18
|
function calculateScriptTreeMerkleRoot(leafHashes) {
|
|
16
19
|
if (!leafHashes || leafHashes.length === 0) {
|
|
17
20
|
return undefined;
|
|
18
21
|
}
|
|
22
|
+
const leafHashCopies = leafHashes.map(leafHash => Uint8Array.from(leafHash));
|
|
19
23
|
// sort the leaf nodes
|
|
20
|
-
|
|
24
|
+
leafHashCopies.sort(uint8array_tools_1.compare);
|
|
21
25
|
// create the initial hash node
|
|
22
|
-
let currentLevel =
|
|
26
|
+
let currentLevel = leafHashCopies;
|
|
23
27
|
// build Merkle Tree
|
|
24
28
|
while (currentLevel.length > 1) {
|
|
25
29
|
const nextLevel = [];
|
|
26
30
|
for (let i = 0; i < currentLevel.length; i += 2) {
|
|
27
31
|
const left = currentLevel[i];
|
|
28
|
-
|
|
32
|
+
if (!left)
|
|
33
|
+
throw new Error('Invalid tapleaf hash tree level');
|
|
34
|
+
const right = i + 1 < currentLevel.length ? (currentLevel[i + 1] ?? left) : left;
|
|
29
35
|
nextLevel.push(i + 1 < currentLevel.length ? tapBranchHash(left, right) : left);
|
|
30
36
|
}
|
|
31
37
|
currentLevel = nextLevel;
|
|
@@ -33,13 +39,13 @@ function calculateScriptTreeMerkleRoot(leafHashes) {
|
|
|
33
39
|
return currentLevel[0];
|
|
34
40
|
}
|
|
35
41
|
function getTweakSignersFromHD(inputIndex, inputs, hdKeyPair) {
|
|
36
|
-
const input = (0,
|
|
42
|
+
const input = (0, bip174_1.checkForInput)(inputs, inputIndex);
|
|
37
43
|
if (!input.tapBip32Derivation || input.tapBip32Derivation.length === 0) {
|
|
38
44
|
throw new Error('Need tapBip32Derivation to sign with HD');
|
|
39
45
|
}
|
|
40
46
|
const myDerivations = input.tapBip32Derivation
|
|
41
47
|
.map(bipDv => {
|
|
42
|
-
if (bipDv.masterFingerprint
|
|
48
|
+
if ((0, uint8array_tools_1.compare)(bipDv.masterFingerprint, hdKeyPair.fingerprint) === 0) {
|
|
43
49
|
return bipDv;
|
|
44
50
|
}
|
|
45
51
|
else {
|
|
@@ -52,7 +58,7 @@ function getTweakSignersFromHD(inputIndex, inputs, hdKeyPair) {
|
|
|
52
58
|
}
|
|
53
59
|
const signers = myDerivations.map(bipDv => {
|
|
54
60
|
const node = hdKeyPair.derivePath(bipDv.path);
|
|
55
|
-
if (
|
|
61
|
+
if ((0, uint8array_tools_1.compare)(bipDv.pubkey, toXOnly(node.publicKey)) !== 0) {
|
|
56
62
|
throw new Error('pubkey did not match tapBip32Derivation');
|
|
57
63
|
}
|
|
58
64
|
//FIX BITCOINERLAB:
|
|
@@ -66,14 +72,14 @@ function getTweakSignersFromHD(inputIndex, inputs, hdKeyPair) {
|
|
|
66
72
|
if (input.tapLeafScript && input.tapLeafScript.length > 0)
|
|
67
73
|
return node;
|
|
68
74
|
const h = calculateScriptTreeMerkleRoot(bipDv.leafHashes);
|
|
69
|
-
const tweakValue = (0,
|
|
75
|
+
const tweakValue = (0, bitcoinjs_lib_internals_1.tapTweakHash)(toXOnly(node.publicKey), h);
|
|
70
76
|
return node.tweak(tweakValue);
|
|
71
77
|
});
|
|
72
78
|
return signers;
|
|
73
79
|
}
|
|
74
80
|
function getSignersFromHD(inputIndex, inputs, hdKeyPair) {
|
|
75
|
-
const input = (0,
|
|
76
|
-
if ((0,
|
|
81
|
+
const input = (0, bip174_1.checkForInput)(inputs, inputIndex);
|
|
82
|
+
if ((0, bitcoinjs_lib_internals_1.isTaprootInput)(input)) {
|
|
77
83
|
return getTweakSignersFromHD(inputIndex, inputs, hdKeyPair);
|
|
78
84
|
}
|
|
79
85
|
if (!input.bip32Derivation || input.bip32Derivation.length === 0) {
|
|
@@ -81,7 +87,7 @@ function getSignersFromHD(inputIndex, inputs, hdKeyPair) {
|
|
|
81
87
|
}
|
|
82
88
|
const myDerivations = input.bip32Derivation
|
|
83
89
|
.map(bipDv => {
|
|
84
|
-
if (bipDv.masterFingerprint
|
|
90
|
+
if ((0, uint8array_tools_1.compare)(bipDv.masterFingerprint, hdKeyPair.fingerprint) === 0) {
|
|
85
91
|
return bipDv;
|
|
86
92
|
}
|
|
87
93
|
else {
|
|
@@ -94,7 +100,7 @@ function getSignersFromHD(inputIndex, inputs, hdKeyPair) {
|
|
|
94
100
|
}
|
|
95
101
|
const signers = myDerivations.map(bipDv => {
|
|
96
102
|
const node = hdKeyPair.derivePath(bipDv.path);
|
|
97
|
-
if (
|
|
103
|
+
if ((0, uint8array_tools_1.compare)(bipDv.pubkey, node.publicKey) !== 0) {
|
|
98
104
|
throw new Error('pubkey did not match bip32Derivation');
|
|
99
105
|
}
|
|
100
106
|
return node;
|
|
@@ -107,7 +113,20 @@ const applyPR2137 = (psbt) => {
|
|
|
107
113
|
throw new Error('Need HDSigner to sign input');
|
|
108
114
|
}
|
|
109
115
|
const signers = getSignersFromHD(inputIndex, this.data.inputs, hdKeyPair);
|
|
110
|
-
|
|
116
|
+
const results = [];
|
|
117
|
+
for (const signer of signers) {
|
|
118
|
+
try {
|
|
119
|
+
this.signInput(inputIndex, signer, sighashTypes);
|
|
120
|
+
results.push(true);
|
|
121
|
+
}
|
|
122
|
+
catch (err) {
|
|
123
|
+
void err;
|
|
124
|
+
results.push(false);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
if (results.every(v => v === false)) {
|
|
128
|
+
throw new Error('No inputs were signed');
|
|
129
|
+
}
|
|
111
130
|
return this;
|
|
112
131
|
};
|
|
113
132
|
psbt.signAllInputsHD = function signAllInputsHD(hdKeyPair, sighashTypes) {
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type * as Bip341 from 'bitcoinjs-lib/src/cjs/payments/bip341';
|
|
2
|
+
import type * as Bip371 from 'bitcoinjs-lib/src/cjs/psbt/bip371';
|
|
3
|
+
import type * as PsbtUtils from 'bitcoinjs-lib/src/cjs/psbt/psbtutils';
|
|
4
|
+
export declare const findScriptPath: typeof Bip341.findScriptPath;
|
|
5
|
+
export declare const tapleafHash: typeof Bip341.tapleafHash;
|
|
6
|
+
export declare const tapTweakHash: typeof Bip341.tapTweakHash;
|
|
7
|
+
export declare const toHashTree: typeof Bip341.toHashTree;
|
|
8
|
+
export declare const tweakKey: typeof Bip341.tweakKey;
|
|
9
|
+
export declare const witnessStackToScriptWitness: typeof PsbtUtils.witnessStackToScriptWitness;
|
|
10
|
+
export declare const isTaprootInput: typeof Bip371.isTaprootInput;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/* eslint-disable @typescript-eslint/no-require-imports */
|
|
3
|
+
/*
|
|
4
|
+
* bitcoinjs-lib v7 does not export all the taproot/psbt helpers we need from
|
|
5
|
+
* its top-level API, so this module centralizes only the required deep imports.
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.isTaprootInput = exports.witnessStackToScriptWitness = exports.tweakKey = exports.toHashTree = exports.tapTweakHash = exports.tapleafHash = exports.findScriptPath = void 0;
|
|
9
|
+
const bip341 = require('bitcoinjs-lib/src/payments/bip341');
|
|
10
|
+
const bip371 = require('bitcoinjs-lib/src/psbt/bip371');
|
|
11
|
+
const psbtUtils = require('bitcoinjs-lib/src/psbt/psbtutils');
|
|
12
|
+
exports.findScriptPath = bip341.findScriptPath;
|
|
13
|
+
exports.tapleafHash = bip341.tapleafHash;
|
|
14
|
+
exports.tapTweakHash = bip341.tapTweakHash;
|
|
15
|
+
exports.toHashTree = bip341.toHashTree;
|
|
16
|
+
exports.tweakKey = bip341.tweakKey;
|
|
17
|
+
exports.witnessStackToScriptWitness = psbtUtils.witnessStackToScriptWitness;
|
|
18
|
+
exports.isTaprootInput = bip371.isTaprootInput;
|