@bitcoinerlab/descriptors 2.3.5 → 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 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, and generate Partially Signed Bitcoin Transactions (PSBTs). It also provides PSBT signers and finalizers for single-signature, BIP32, and Hardware Wallets.
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, and Hardware Wallets (currently supports Ledger devices; more devices are planned).
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, 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.
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, and types, head over [here](https://bitcoinerlab.com/modules/descriptors/api).
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 miniscript-based descriptors, the `signersPubKeys` parameter in the constuctor becomes particularly important. It specifies the spending path of a previous output with multiple spending paths. Detailed information about the constructor parameters, including `signersPubKeys`, 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).
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
- The library supports a wide range of descriptor types, including:
81
- - Pay-to-Public-Key-Hash (P2PKH): `pkh(KEY)`
82
- - Pay-to-Witness-Public-Key-Hash (P2WPKH): `wpkh(KEY)`
83
- - Pay-to-Script-Hash (P2SH): `sh(SCRIPT)`
84
- - Pay-to-Witness-Script-Hash (P2WSH): `wsh(SCRIPT)`
85
- - Pay-to-Taproot (P2TR) with single key: `tr(KEY)`
86
- - Address-based descriptors: `addr(ADDRESS)`, including Taproot addresses
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
- These descriptors can be used with various key expressions, including raw public keys, BIP32 derivation paths, and more.
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 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.
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
- Conversely, `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`:
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
- new Output({ descriptor: `addr(bc1qgw6xanldsz959z45y4dszehx4xkuzf7nfhya8x)` });
109
- recipientOutput.updatePsbtAsOutput({ psbt, value: 10000 });
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). For insights into the constructor, especially regarding the `signersPubKeys` parameter, as well as the usage of `updatePsbtAsInput`, `getAddress`, and `getScriptPubKey`, see this detailed [Stack Exchange answer](https://bitcoin.stackexchange.com/a/118036/89665).
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
- The `expand()` function serves as a mechanism to parse Bitcoin descriptors, unveiling a detailed breakdown of the descriptor's content. There are two main pathways to utilize this function:
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
- ##### 1. Directly from an `Output` Instance
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
- If you have already instantiated the `Output` class and created an output, you can directly use the [`expand()` method](https://bitcoinerlab.com/modules/descriptors/api/classes/_Internal_.Output.html#expand) on that `Output` instance. This method provides a straightforward way to parse descriptors without the need for additional utilities.
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
- const output = new Output({ descriptor: "your-descriptor-here" });
124
- const result = output.expand();
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
- ##### 2. Through the `DescriptorsFactory`
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
- If you haven't instantiated the `Output` class or simply prefer a standalone utility, the `DescriptorsFactory` provides an `expand()` function that allows you to directly parse the descriptor. For a comprehensive understanding of all the function arguments, refer to [this reference](https://bitcoinerlab.com/modules/descriptors/api/functions/DescriptorsFactory.html#DescriptorsFactory). Here's how you can use it:
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
- const { expand } = descriptors.DescriptorsFactory(ecc);
133
- const result = expand({
134
- descriptor: "sh(wsh(andor(pk(0252972572d465d016d4c501887b8df303eee3ed602c056b1eb09260dfa0da0ab2),older(8640),pk([d34db33f/49'/0'/0']tpubDCdxmvzJ5QBjTN8oCjjyT2V58AyZvA1fkmCeZRC75QMoaHcVP2m45Bv3hmnR7ttAwkb2UNYyoXdHVt4gwBqRrJqLUU2JrM43HippxiWpHra/1/2/3/4/*))))"
243
+ new Output({
244
+ descriptor: 'tr(INTERNAL_KEY,{pk(KEY_A),pk(KEY_B)})',
245
+ taprootSpendPath: 'key'
135
246
  });
136
247
  ```
137
248
 
138
- Regardless of your chosen pathway, the outcome from `expand()` grants an insightful exploration into the descriptor's structure. For an exhaustive list of return properties, you can refer to [the API](https://bitcoinerlab.com/modules/descriptors/api/types/Expansion.html).
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
- expandedExpression: 'sh(wsh(andor(pk(@0),older(8640),pk(@1))))',
145
- expansionMap: {
146
- '@0': {
147
- keyExpression:
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, and Ledger (specifically crafted for Ledger Wallet devices, with upcoming support for other devices planned).
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`. However, ensure the accuracy of the `value` to avoid potential fee attacks. When unsure, use `txHex` and skip `txId` and `value`.
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()`, and `wpkhLedger()`. Refer to [the API](https://bitcoinerlab.com/modules/descriptors/api/modules/scriptExpressions.html#expand) for a detailed list and further information.
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 *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.
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 must be /<1;0>/number
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, and to create a new Ledger client:
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, 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.
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, and types.
335
- - **[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`.
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. First, make sure that you have a Bitcoin regtest node running and that you have set up [this Express-based bitcoind manager](https://github.com/bitcoinjs/regtest-server) running on 127.0.0.1:8080.
475
+ Before committing any code, make sure it passes all tests.
381
476
 
382
- The easiest way to set up these services is to use a Docker image that comes preconfigured with them. You can use the following commands to download and run the Docker image:
477
+ Run unit tests:
383
478
 
384
479
  ```bash
385
- docker pull bitcoinerlab/tester
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
- This will start a container running a Bitcoin regtest node and the bitcoind manager on your machine. Once you have your node and manager set up, you can run the tests using the following command:
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
@@ -1,31 +1,37 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.applyPR2137 = void 0;
4
- const utils_1 = require("bip174/src/lib/utils");
5
- const bip341_1 = require("bitcoinjs-lib/src/payments/bip341");
6
- const bip371_1 = require("bitcoinjs-lib/src/psbt/bip371");
7
- const crypto_1 = require("bitcoinjs-lib/src/crypto");
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 (0, crypto_1.taggedHash)('TapBranch', Buffer.concat([a, b]));
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
- leafHashes.sort(Buffer.compare);
24
+ leafHashCopies.sort(uint8array_tools_1.compare);
21
25
  // create the initial hash node
22
- let currentLevel = leafHashes;
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
- const right = i + 1 < currentLevel.length ? currentLevel[i + 1] : left;
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, utils_1.checkForInput)(inputs, inputIndex);
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.equals(hdKeyPair.fingerprint)) {
48
+ if ((0, uint8array_tools_1.compare)(bipDv.masterFingerprint, hdKeyPair.fingerprint) === 0) {
43
49
  return bipDv;
44
50
  }
45
51
  else {
@@ -52,18 +58,28 @@ function getTweakSignersFromHD(inputIndex, inputs, hdKeyPair) {
52
58
  }
53
59
  const signers = myDerivations.map(bipDv => {
54
60
  const node = hdKeyPair.derivePath(bipDv.path);
55
- if (!bipDv.pubkey.equals(toXOnly(node.publicKey))) {
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
  }
64
+ //FIX BITCOINERLAB:
65
+ //The 3 lines below detect script-path spends and disable key-path tweaking.
66
+ //Reasoning:
67
+ //- In Taproot, key-path spends require tweaking the internal key.
68
+ //- Script-path spends MUST NOT tweak the key; signatures use the raw internal key.
69
+ const input = inputs[inputIndex];
70
+ if (!input)
71
+ throw new Error('could not find the input');
72
+ if (input.tapLeafScript && input.tapLeafScript.length > 0)
73
+ return node;
58
74
  const h = calculateScriptTreeMerkleRoot(bipDv.leafHashes);
59
- const tweakValue = (0, bip341_1.tapTweakHash)(toXOnly(node.publicKey), h);
75
+ const tweakValue = (0, bitcoinjs_lib_internals_1.tapTweakHash)(toXOnly(node.publicKey), h);
60
76
  return node.tweak(tweakValue);
61
77
  });
62
78
  return signers;
63
79
  }
64
80
  function getSignersFromHD(inputIndex, inputs, hdKeyPair) {
65
- const input = (0, utils_1.checkForInput)(inputs, inputIndex);
66
- if ((0, bip371_1.isTaprootInput)(input)) {
81
+ const input = (0, bip174_1.checkForInput)(inputs, inputIndex);
82
+ if ((0, bitcoinjs_lib_internals_1.isTaprootInput)(input)) {
67
83
  return getTweakSignersFromHD(inputIndex, inputs, hdKeyPair);
68
84
  }
69
85
  if (!input.bip32Derivation || input.bip32Derivation.length === 0) {
@@ -71,7 +87,7 @@ function getSignersFromHD(inputIndex, inputs, hdKeyPair) {
71
87
  }
72
88
  const myDerivations = input.bip32Derivation
73
89
  .map(bipDv => {
74
- if (bipDv.masterFingerprint.equals(hdKeyPair.fingerprint)) {
90
+ if ((0, uint8array_tools_1.compare)(bipDv.masterFingerprint, hdKeyPair.fingerprint) === 0) {
75
91
  return bipDv;
76
92
  }
77
93
  else {
@@ -84,7 +100,7 @@ function getSignersFromHD(inputIndex, inputs, hdKeyPair) {
84
100
  }
85
101
  const signers = myDerivations.map(bipDv => {
86
102
  const node = hdKeyPair.derivePath(bipDv.path);
87
- if (!bipDv.pubkey.equals(node.publicKey)) {
103
+ if ((0, uint8array_tools_1.compare)(bipDv.pubkey, node.publicKey) !== 0) {
88
104
  throw new Error('pubkey did not match bip32Derivation');
89
105
  }
90
106
  return node;
@@ -97,7 +113,20 @@ const applyPR2137 = (psbt) => {
97
113
  throw new Error('Need HDSigner to sign input');
98
114
  }
99
115
  const signers = getSignersFromHD(inputIndex, this.data.inputs, hdKeyPair);
100
- signers.forEach(signer => this.signInput(inputIndex, signer, sighashTypes));
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
+ }
101
130
  return this;
102
131
  };
103
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;