@kukks/bitcoin-descriptors 3.0.2 → 3.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -6,12 +6,13 @@ This library parses and creates Bitcoin Miniscript Descriptors and generates Par
6
6
 
7
7
  ## Differences from upstream
8
8
 
9
- This fork migrates the entire library from `bitcoinjs-lib` to the [`@scure/btc-signer`](https://github.com/nicolo-ribaudo/scure-btc-signer) and [`@noble`](https://github.com/nicolo-ribaudo/noble-curves) ecosystem. Key differences:
9
+ This fork migrates the entire library from `bitcoinjs-lib` to the [`@scure/btc-signer`](https://github.com/paulmillr/scure-btc-signer) and [`@noble`](https://github.com/paulmillr/noble-curves) ecosystem. Key differences:
10
10
 
11
11
  - **`Buffer` replaced with `Uint8Array`** across the entire public API. All methods that previously returned or accepted `Buffer` now use `Uint8Array`. This is a **breaking change**.
12
- - **Dependencies replaced**: `bitcoinjs-lib`, `ecpair`, `bip32`, `tiny-secp256k1` are no longer used. The library now depends on `@scure/btc-signer`, `@scure/bip32`, `@noble/curves`, `@noble/hashes`, and `@scure/base`.
12
+ - **Dependencies replaced**: `bitcoinjs-lib`, `ecpair`, `bip32`, `tiny-secp256k1` are no longer used. The library now depends on [`@scure/btc-signer`](https://github.com/paulmillr/scure-btc-signer), [`@scure/bip32`](https://github.com/paulmillr/scure-bip32), [`@noble/curves`](https://github.com/paulmillr/noble-curves), [`@noble/hashes`](https://github.com/paulmillr/noble-hashes), and [`@scure/base`](https://github.com/paulmillr/scure-base).
13
+ - **Built-in adapters**: Ships `nobleECPair` and `scureBIP32` adapters — no more boilerplate. `DescriptorsFactory()` works with zero arguments.
13
14
  - **PSBT class**: Uses `Transaction` from `@scure/btc-signer` instead of `Psbt` from `bitcoinjs-lib`.
14
- - **Ledger support removed**: The `ledger` module and all Ledger-related functions (`signLedger`, `keyExpressionLedger`, `pkhLedger`, `shWpkhLedger`, `wpkhLedger`, etc.) have been removed.
15
+ - **Ledger support removed**: The `ledger` module and all Ledger-related functions have been removed.
15
16
  - **`lodash.memoize` removed**: Replaced with an inline memoize helper.
16
17
  - **Package renamed** from `@bitcoinerlab/descriptors` to `@kukks/bitcoin-descriptors`.
17
18
 
@@ -19,18 +20,62 @@ This fork migrates the entire library from `bitcoinjs-lib` to the [`@scure/btc-s
19
20
 
20
21
  ```bash
21
22
  npm install @kukks/bitcoin-descriptors
23
+ ```
24
+
25
+ If you use **miniscript-based descriptors** (`sh(wsh(...))`, `sh(...)`, `wsh(...)` with miniscript expressions), install the optional peer dependency:
26
+
27
+ ```bash
22
28
  npm install @bitcoinerlab/miniscript
23
29
  ```
24
30
 
31
+ Standard descriptors (`pkh`, `wpkh`, `sh(wpkh(...))`, `tr`, `multi`, `sortedmulti`, `addr`) work without it.
32
+
33
+ ## Quick Start
34
+
35
+ ```typescript
36
+ import { DescriptorsFactory } from '@kukks/bitcoin-descriptors';
37
+
38
+ // Zero-config — uses built-in @noble/curves + @scure/bip32 adapters
39
+ const { Output, expand } = DescriptorsFactory();
40
+
41
+ const output = new Output({
42
+ descriptor: 'wpkh(02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9)'
43
+ });
44
+
45
+ console.log(output.getAddress());
46
+ ```
47
+
48
+ Or use the pre-built default factory:
49
+
50
+ ```typescript
51
+ import { defaultFactory } from '@kukks/bitcoin-descriptors';
52
+
53
+ const { Output } = defaultFactory;
54
+ ```
55
+
56
+ ### Bring your own adapters
57
+
58
+ If you need custom `ECPairAPI` or `BIP32API` implementations, you can still pass them explicitly:
59
+
60
+ ```typescript
61
+ import { DescriptorsFactory } from '@kukks/bitcoin-descriptors';
62
+ import type { ECPairAPI, BIP32API } from '@kukks/bitcoin-descriptors';
63
+
64
+ const { Output } = DescriptorsFactory({ ECPair: myECPair, BIP32: myBIP32 });
65
+ ```
66
+
67
+ The built-in adapters are also available as standalone exports:
68
+
69
+ ```typescript
70
+ import { nobleECPair, scureBIP32 } from '@kukks/bitcoin-descriptors';
71
+ ```
72
+
25
73
  ## Features
26
74
 
27
75
  - 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)).
28
76
  - Generates Partially Signed Bitcoin Transactions (PSBTs).
29
77
  - Provides PSBT finalizers and signers for single-signature and BIP32 wallets.
30
-
31
- ## Concepts
32
-
33
- This library has two main capabilities related to Bitcoin descriptors. Firstly, it can generate `addresses` and `scriptPubKeys` from descriptors. These `addresses` and `scriptPubKeys` can be used to receive funds from other parties. Secondly, the library is able to sign transactions and spend unspent outputs described by those same descriptors. In order to do this, the descriptors must first be set into a PSBT.
78
+ - Ships built-in adapters for `@noble/curves` and `@scure/bip32` — zero boilerplate needed.
34
79
 
35
80
  <details>
36
81
  <summary>Concepts</summary>
@@ -61,21 +106,15 @@ The library can be split into three main parts:
61
106
 
62
107
  ### Output class
63
108
 
64
- The `Output` class is dynamically created by providing `ECPair` and `BIP32` factory APIs:
65
-
66
- ```javascript
67
- import * as descriptors from '@kukks/bitcoin-descriptors';
68
- const { Output } = descriptors.DescriptorsFactory({ ECPair, BIP32 });
69
- ```
109
+ The `Output` class is created via `DescriptorsFactory`:
70
110
 
71
- Here, `ECPair` and `BIP32` are implementations of the `ECPairAPI` and `BIP32API` interfaces. These interfaces now use `Uint8Array` instead of `Buffer` for all binary data (public keys, private keys, signatures, etc.).
111
+ ```typescript
112
+ import { DescriptorsFactory } from '@kukks/bitcoin-descriptors';
72
113
 
73
- Once set up, you can obtain an instance for an output:
114
+ const { Output } = DescriptorsFactory();
74
115
 
75
- ```javascript
76
116
  const wpkhOutput = new Output({
77
- descriptor:
78
- 'wpkh(02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9)'
117
+ descriptor: 'wpkh(02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9)'
79
118
  });
80
119
  ```
81
120
 
@@ -83,33 +122,35 @@ For miniscript-based descriptors, the `signersPubKeys` parameter in the construc
83
122
 
84
123
  The `Output` class offers various helpful methods, including `getAddress()`, `getScriptPubKey()` (returns `Uint8Array`), `expand()`, `updatePsbtAsInput()` and `updatePsbtAsOutput()`.
85
124
 
86
- The library supports a wide range of descriptor types, including:
87
- - Pay-to-Public-Key-Hash (P2PKH): `pkh(KEY)`
88
- - Pay-to-Witness-Public-Key-Hash (P2WPKH): `wpkh(KEY)`
89
- - Pay-to-Script-Hash (P2SH): `sh(SCRIPT)`
90
- - Pay-to-Witness-Script-Hash (P2WSH): `wsh(SCRIPT)`
91
- - Pay-to-Taproot (P2TR) with single key: `tr(KEY)`
92
- - Address-based descriptors: `addr(ADDRESS)`, including Taproot addresses
125
+ The library supports a wide range of descriptor types, including:
126
+ - Pay-to-Public-Key-Hash (P2PKH): `pkh(KEY)`
127
+ - Pay-to-Witness-Public-Key-Hash (P2WPKH): `wpkh(KEY)`
128
+ - Pay-to-Script-Hash (P2SH): `sh(SCRIPT)`
129
+ - Pay-to-Witness-Script-Hash (P2WSH): `wsh(SCRIPT)`
130
+ - Pay-to-Taproot (P2TR) with single key: `tr(KEY)`
131
+ - Address-based descriptors: `addr(ADDRESS)`, including Taproot addresses
93
132
 
94
133
  #### Working with PSBTs
95
134
 
96
135
  This library uses `Transaction` from `@scure/btc-signer` as the PSBT class:
97
136
 
98
- ```javascript
137
+ ```typescript
99
138
  import { Transaction } from '@scure/btc-signer';
139
+
100
140
  const psbt = new Transaction({ allowUnknownOutputs: true, disableScriptCheck: true });
101
141
  const inputFinalizer = output.updatePsbtAsInput({ psbt, txHex, vout });
102
142
  ```
103
143
 
104
- Here, `psbt` refers to an instance of the [`@scure/btc-signer` Transaction class](https://github.com/nicolo-ribaudo/scure-btc-signer). The parameter `txHex` denotes a hex string that serializes the previous transaction containing this output. Meanwhile, `vout` is an integer that marks the position of the output within that transaction.
144
+ Here, `psbt` refers to an instance of the [`@scure/btc-signer` Transaction class](https://github.com/paulmillr/scure-btc-signer). The parameter `txHex` denotes a hex string that serializes the previous transaction containing this output. Meanwhile, `vout` is an integer that marks the position of the output within that transaction.
105
145
 
106
146
  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. Complete all necessary signing operations before calling `inputFinalizer()`.
107
147
 
108
148
  To add an output:
109
149
 
110
- ```javascript
111
- const recipientOutput =
112
- new Output({ descriptor: `addr(bc1qgw6xanldsz959z45y4dszehx4xkuzf7nfhya8x)` });
150
+ ```typescript
151
+ const recipientOutput = new Output({
152
+ descriptor: 'addr(bc1qgw6xanldsz959z45y4dszehx4xkuzf7nfhya8x)'
153
+ });
113
154
  recipientOutput.updatePsbtAsOutput({ psbt, value: 10000 });
114
155
  ```
115
156
 
@@ -117,15 +158,15 @@ recipientOutput.updatePsbtAsOutput({ psbt, value: 10000 });
117
158
 
118
159
  The `expand()` function parses Bitcoin descriptors into their component parts:
119
160
 
120
- ```javascript
161
+ ```typescript
121
162
  const output = new Output({ descriptor: "your-descriptor-here" });
122
163
  const result = output.expand();
123
164
  ```
124
165
 
125
166
  Or through the factory:
126
167
 
127
- ```javascript
128
- const { expand } = descriptors.DescriptorsFactory({ ECPair, BIP32 });
168
+ ```typescript
169
+ const { expand } = DescriptorsFactory();
129
170
  const result = expand({
130
171
  descriptor: "sh(wsh(andor(pk(0252972572d465d016d4c501887b8df303eee3ed602c056b1eb09260dfa0da0ab2),older(8640),pk([d34db33f/49'/0'/0']tpubDCdxmvzJ5QBjTN8oCjjyT2V58AyZvA1fkmCeZRC75QMoaHcVP2m45Bv3hmnR7ttAwkb2UNYyoXdHVt4gwBqRrJqLUU2JrM43HippxiWpHra/1/2/3/4/*))))"
131
172
  });
@@ -135,7 +176,7 @@ const result = expand({
135
176
 
136
177
  This library includes two signers: ECPair (single-signature) and BIP32.
137
178
 
138
- ```javascript
179
+ ```typescript
139
180
  import { signers } from '@kukks/bitcoin-descriptors';
140
181
 
141
182
  // For BIP32
@@ -149,13 +190,13 @@ signers.signECPair({ psbt, ecpair });
149
190
 
150
191
  1. For each unspent output, call `updatePsbtAsInput`:
151
192
 
152
- ```javascript
193
+ ```typescript
153
194
  const inputFinalizer = output.updatePsbtAsInput({ psbt, txHex, vout });
154
195
  ```
155
196
 
156
197
  2. After signing, finalize each input:
157
198
 
158
- ```javascript
199
+ ```typescript
159
200
  inputFinalizer({ psbt });
160
201
  ```
161
202
 
@@ -163,7 +204,7 @@ signers.signECPair({ psbt, ecpair });
163
204
 
164
205
  Helper functions for generating descriptor strings:
165
206
 
166
- ```javascript
207
+ ```typescript
167
208
  import { scriptExpressions, keyExpressionBIP32 } from '@kukks/bitcoin-descriptors';
168
209
  ```
169
210
 
@@ -171,7 +212,7 @@ The `scriptExpressions` module includes functions like `pkhBIP32()`, `shWpkhBIP3
171
212
 
172
213
  The `keyExpressionBIP32` function generates BIP32 key expression strings:
173
214
 
174
- ```javascript
215
+ ```typescript
175
216
  keyExpressionBIP32({
176
217
  masterNode, // BIP32Interface
177
218
  originPath, // e.g. "/44'/0'/0'"
@@ -181,6 +222,36 @@ keyExpressionBIP32({
181
222
  });
182
223
  ```
183
224
 
225
+ ## API Reference
226
+
227
+ ### Exports
228
+
229
+ | Export | Type | Description |
230
+ |--------|------|-------------|
231
+ | `DescriptorsFactory` | function | Creates `Output`, `expand`, `parseKeyExpression` — params optional (defaults to built-in adapters) |
232
+ | `defaultFactory` | object | Pre-built factory using built-in adapters |
233
+ | `nobleECPair` | `ECPairAPI` | Built-in ECPair adapter using `@noble/curves` secp256k1 |
234
+ | `scureBIP32` | `BIP32API` | Built-in BIP32 adapter using `@scure/bip32` |
235
+ | `signers` | namespace | `signECPair`, `signBIP32` and related signing functions |
236
+ | `scriptExpressions` | namespace | `pkhBIP32`, `shWpkhBIP32`, `wpkhBIP32`, etc. |
237
+ | `keyExpressionBIP32` | function | Generate BIP32 key expression strings |
238
+ | `networks` | object | `bitcoin`, `testnet`, `regtest` network definitions |
239
+ | `checksum` | function | Compute/validate descriptor checksums |
240
+
241
+ ### Types
242
+
243
+ | Type | Description |
244
+ |------|-------------|
245
+ | `ECPairAPI` | Factory interface for creating key pairs |
246
+ | `ECPairInterface` | Key pair instance with `sign`, `verify`, `publicKey`, etc. |
247
+ | `BIP32API` | Factory interface for creating HD keys (`fromSeed`, `fromBase58`, etc.) |
248
+ | `BIP32Interface` | HD key instance with `derivePath`, `derive`, `sign`, etc. |
249
+ | `OutputInstance` | Instance returned by `new Output(...)` |
250
+ | `Network` | Network configuration (`bitcoin`, `testnet`, `regtest`) |
251
+ | `PsbtLike` | PSBT interface (compatible with `@scure/btc-signer` Transaction) |
252
+ | `KeyInfo` | Parsed key expression data |
253
+ | `Expansion` | Parsed descriptor expansion data |
254
+
184
255
  ## Building from source
185
256
 
186
257
  ```bash
@@ -0,0 +1,23 @@
1
+ import { HDKey } from '@scure/bip32';
2
+ import type { ECPairInterface, ECPairAPI, BIP32Interface, BIP32API } from './types.js';
3
+ import { type Network } from './networks.js';
4
+ /**
5
+ * Create an ECPairInterface from a private key and/or public key.
6
+ * Exported for advanced use cases where consumers need to construct
7
+ * key pairs directly.
8
+ */
9
+ export declare function createECPair(privKey: Uint8Array | undefined, pubKeyInput: Uint8Array | undefined, compressed: boolean, network: Network): ECPairInterface;
10
+ /**
11
+ * Built-in ECPairAPI implementation using @noble/curves secp256k1.
12
+ */
13
+ export declare const nobleECPair: ECPairAPI;
14
+ /**
15
+ * Wrap a @scure/bip32 HDKey in a BIP32Interface.
16
+ * Exported for advanced use cases where consumers need to wrap
17
+ * HDKey instances directly.
18
+ */
19
+ export declare function wrapHDKey(hdkey: HDKey, network: Network): BIP32Interface;
20
+ /**
21
+ * Built-in BIP32API implementation using @scure/bip32.
22
+ */
23
+ export declare const scureBIP32: BIP32API;
@@ -0,0 +1,321 @@
1
+ // Copyright (c) 2025 Jose-Luis Landabaso - https://bitcoinerlab.com
2
+ // Distributed under the MIT software license
3
+ // Built-in adapters for ECPairAPI and BIP32API using
4
+ // @noble/curves, @scure/bip32, @noble/hashes, and @scure/base.
5
+ // These conform to the interfaces defined in ./types.ts.
6
+ import { secp256k1 } from '@noble/curves/secp256k1.js';
7
+ import { HDKey } from '@scure/bip32';
8
+ import { sha256 } from '@noble/hashes/sha2.js';
9
+ import { base58check, hex } from '@scure/base';
10
+ import { concatBytes } from '@scure/btc-signer/utils.js';
11
+ import { networks } from './networks.js';
12
+ // ---------------------------------------------------------------------------
13
+ // Helpers
14
+ // ---------------------------------------------------------------------------
15
+ const bs58check = base58check(sha256);
16
+ const DEFAULT_NETWORK = networks.bitcoin;
17
+ /** Convert a bigint to a 32-byte big-endian Uint8Array. */
18
+ function bigintTo32Bytes(n) {
19
+ const hexStr = n.toString(16).padStart(64, '0');
20
+ return hex.decode(hexStr);
21
+ }
22
+ /** Write a 32-bit unsigned integer in big-endian format into buf at offset. */
23
+ function writeUInt32BE(buf, value, offset) {
24
+ buf[offset] = (value >>> 24) & 0xff;
25
+ buf[offset + 1] = (value >>> 16) & 0xff;
26
+ buf[offset + 2] = (value >>> 8) & 0xff;
27
+ buf[offset + 3] = value & 0xff;
28
+ }
29
+ // ---------------------------------------------------------------------------
30
+ // WIF encode / decode
31
+ // ---------------------------------------------------------------------------
32
+ function encodeWIF(privateKey, compressed, network) {
33
+ const prefix = Uint8Array.from([network.wif]);
34
+ const payload = compressed
35
+ ? concatBytes(prefix, privateKey, Uint8Array.from([0x01]))
36
+ : concatBytes(prefix, privateKey);
37
+ return bs58check.encode(payload);
38
+ }
39
+ function decodeWIF(wif, network) {
40
+ const decoded = bs58check.decode(wif);
41
+ const version = decoded[0];
42
+ // Determine matching network(s)
43
+ const candidateNetworks = network
44
+ ? Array.isArray(network)
45
+ ? network
46
+ : [network]
47
+ : [networks.bitcoin, networks.testnet, networks.regtest];
48
+ const matchedNetwork = candidateNetworks.find(n => n.wif === version);
49
+ if (!matchedNetwork) {
50
+ throw new Error(`Invalid network version`);
51
+ }
52
+ // Determine if compressed
53
+ if (decoded.length === 34 && decoded[33] === 0x01) {
54
+ return {
55
+ privateKey: decoded.subarray(1, 33),
56
+ compressed: true,
57
+ network: matchedNetwork
58
+ };
59
+ }
60
+ else if (decoded.length === 33) {
61
+ return {
62
+ privateKey: decoded.subarray(1, 33),
63
+ compressed: false,
64
+ network: matchedNetwork
65
+ };
66
+ }
67
+ else {
68
+ throw new Error('Invalid WIF payload length');
69
+ }
70
+ }
71
+ // ---------------------------------------------------------------------------
72
+ // ECPair implementation
73
+ // ---------------------------------------------------------------------------
74
+ /**
75
+ * Create an ECPairInterface from a private key and/or public key.
76
+ * Exported for advanced use cases where consumers need to construct
77
+ * key pairs directly.
78
+ */
79
+ export function createECPair(privKey, pubKeyInput, compressed, network) {
80
+ let pubKey;
81
+ if (privKey) {
82
+ pubKey = secp256k1.getPublicKey(privKey, compressed);
83
+ }
84
+ else if (pubKeyInput) {
85
+ // Normalise to the requested compression
86
+ const point = secp256k1.Point.fromHex(hex.encode(pubKeyInput));
87
+ pubKey = point.toBytes(compressed);
88
+ }
89
+ else {
90
+ throw new Error('Either privateKey or publicKey must be provided');
91
+ }
92
+ // Build the object conditionally so that `privateKey` is only present
93
+ // when defined (satisfies exactOptionalPropertyTypes).
94
+ const base = {
95
+ publicKey: pubKey,
96
+ compressed,
97
+ network,
98
+ sign(hash) {
99
+ if (!privKey)
100
+ throw new Error('Missing private key');
101
+ return secp256k1.sign(hash, privKey);
102
+ },
103
+ verify(hash, signature) {
104
+ return secp256k1.verify(signature, hash, pubKey);
105
+ },
106
+ toWIF() {
107
+ if (!privKey)
108
+ throw new Error('Missing private key');
109
+ return encodeWIF(privKey, compressed, network);
110
+ },
111
+ tweak(t) {
112
+ const tweakBigint = BigInt('0x' + hex.encode(t));
113
+ const n = secp256k1.Point.CURVE().n;
114
+ if (privKey) {
115
+ const privBigint = BigInt('0x' + hex.encode(privKey));
116
+ const newPrivBigint = (privBigint + tweakBigint) % n;
117
+ if (newPrivBigint === 0n) {
118
+ throw new Error('Tweaked private key is zero');
119
+ }
120
+ const newPrivKey = bigintTo32Bytes(newPrivBigint);
121
+ return createECPair(newPrivKey, undefined, compressed, network);
122
+ }
123
+ else {
124
+ // Public-key-only tweak: P' = P + t*G
125
+ const G = secp256k1.Point.BASE;
126
+ const tweakPoint = G.multiply(tweakBigint);
127
+ const pubPoint = secp256k1.Point.fromHex(hex.encode(pubKey));
128
+ const tweakedPoint = pubPoint.add(tweakPoint);
129
+ return createECPair(undefined, tweakedPoint.toBytes(compressed), compressed, network);
130
+ }
131
+ }
132
+ };
133
+ if (privKey) {
134
+ return Object.assign(base, { privateKey: privKey });
135
+ }
136
+ return base;
137
+ }
138
+ /**
139
+ * Built-in ECPairAPI implementation using @noble/curves secp256k1.
140
+ */
141
+ export const nobleECPair = {
142
+ fromPublicKey(publicKey, options) {
143
+ const compressed = options?.compressed !== false;
144
+ const network = options?.network ?? DEFAULT_NETWORK;
145
+ return createECPair(undefined, publicKey, compressed, network);
146
+ },
147
+ fromPrivateKey(privateKey, options) {
148
+ const compressed = options?.compressed !== false;
149
+ const network = options?.network ?? DEFAULT_NETWORK;
150
+ return createECPair(privateKey, undefined, compressed, network);
151
+ },
152
+ fromWIF(wif, network) {
153
+ const { privateKey, compressed, network: net } = decodeWIF(wif, network);
154
+ return createECPair(privateKey, undefined, compressed, net);
155
+ },
156
+ makeRandom(options) {
157
+ const privKey = secp256k1.utils.randomSecretKey();
158
+ const compressed = options?.compressed !== false;
159
+ const network = options?.network ?? DEFAULT_NETWORK;
160
+ return createECPair(privKey, undefined, compressed, network);
161
+ },
162
+ isPoint(p) {
163
+ try {
164
+ secp256k1.Point.fromHex(hex.encode(p));
165
+ return true;
166
+ }
167
+ catch {
168
+ return false;
169
+ }
170
+ }
171
+ };
172
+ // ---------------------------------------------------------------------------
173
+ // BIP32 implementation
174
+ // ---------------------------------------------------------------------------
175
+ /** Map a Network to the versions object expected by @scure/bip32's HDKey. */
176
+ function networkToVersions(network) {
177
+ return { public: network.bip32.public, private: network.bip32.private };
178
+ }
179
+ /**
180
+ * Wrap a @scure/bip32 HDKey in a BIP32Interface.
181
+ * Exported for advanced use cases where consumers need to wrap
182
+ * HDKey instances directly.
183
+ */
184
+ export function wrapHDKey(hdkey, network) {
185
+ const versions = networkToVersions(network);
186
+ // Build the base object without privateKey, then conditionally add it.
187
+ // This satisfies exactOptionalPropertyTypes.
188
+ const base = {
189
+ get publicKey() {
190
+ if (!hdkey.publicKey)
191
+ throw new Error('Missing public key');
192
+ return hdkey.publicKey;
193
+ },
194
+ get chainCode() {
195
+ if (!hdkey.chainCode)
196
+ throw new Error('Missing chain code');
197
+ return hdkey.chainCode;
198
+ },
199
+ get fingerprint() {
200
+ // HDKey.fingerprint is a number (4-byte big-endian unsigned int)
201
+ const buf = new Uint8Array(4);
202
+ writeUInt32BE(buf, hdkey.fingerprint >>> 0, 0);
203
+ return buf;
204
+ },
205
+ get depth() {
206
+ return hdkey.depth;
207
+ },
208
+ get index() {
209
+ return hdkey.index;
210
+ },
211
+ get parentFingerprint() {
212
+ return hdkey.parentFingerprint;
213
+ },
214
+ network,
215
+ derivePath(path) {
216
+ // @scure/bip32 requires paths to start with "m" or "M"
217
+ // Old bip32 package accepted relative paths like "0'/1'/0'"
218
+ const normalizedPath = path.startsWith('m') || path.startsWith('M') ? path : `m/${path}`;
219
+ return wrapHDKey(hdkey.derive(normalizedPath), network);
220
+ },
221
+ derive(index) {
222
+ return wrapHDKey(hdkey.deriveChild(index), network);
223
+ },
224
+ deriveHardened(index) {
225
+ return wrapHDKey(hdkey.deriveChild(index + 0x80000000), network);
226
+ },
227
+ neutered() {
228
+ // Create a public-only HDKey via the public extended key
229
+ const xpub = hdkey.publicExtendedKey;
230
+ const neuteredKey = HDKey.fromExtendedKey(xpub, versions);
231
+ return wrapHDKey(neuteredKey, network);
232
+ },
233
+ toBase58() {
234
+ if (hdkey.privateKey) {
235
+ return hdkey.privateExtendedKey;
236
+ }
237
+ return hdkey.publicExtendedKey;
238
+ },
239
+ sign(hash) {
240
+ if (!hdkey.privateKey)
241
+ throw new Error('Missing private key');
242
+ return hdkey.sign(hash);
243
+ },
244
+ verify(hash, signature) {
245
+ return hdkey.verify(hash, signature);
246
+ },
247
+ isNeutered() {
248
+ return !hdkey.privateKey;
249
+ },
250
+ toWIF() {
251
+ if (!hdkey.privateKey) {
252
+ throw new Error('Missing private key');
253
+ }
254
+ return encodeWIF(hdkey.privateKey, true, network);
255
+ }
256
+ };
257
+ if (hdkey.privateKey) {
258
+ return Object.defineProperty(base, 'privateKey', {
259
+ get() {
260
+ return hdkey.privateKey;
261
+ },
262
+ enumerable: true,
263
+ configurable: true
264
+ });
265
+ }
266
+ return base;
267
+ }
268
+ /**
269
+ * Built-in BIP32API implementation using @scure/bip32.
270
+ */
271
+ export const scureBIP32 = {
272
+ fromBase58(base58, network) {
273
+ const net = network ?? DEFAULT_NETWORK;
274
+ const versions = networkToVersions(net);
275
+ const hdkey = HDKey.fromExtendedKey(base58, versions);
276
+ return wrapHDKey(hdkey, net);
277
+ },
278
+ fromPublicKey(publicKey, chainCode, network) {
279
+ const net = network ?? DEFAULT_NETWORK;
280
+ const versions = networkToVersions(net);
281
+ // Manually serialise the public extended key in base58check format.
282
+ //
283
+ // xpub format (78 bytes):
284
+ // version (4) + depth (1) + parentFingerprint (4) + index (4) +
285
+ // chainCode (32) + publicKey (33)
286
+ const buf = new Uint8Array(78);
287
+ writeUInt32BE(buf, versions.public, 0); // version
288
+ buf[4] = 0; // depth
289
+ writeUInt32BE(buf, 0, 5); // parent fingerprint
290
+ writeUInt32BE(buf, 0, 9); // index
291
+ buf.set(chainCode, 13);
292
+ buf.set(publicKey, 45);
293
+ const xpub = bs58check.encode(buf);
294
+ const hdkey = HDKey.fromExtendedKey(xpub, versions);
295
+ return wrapHDKey(hdkey, net);
296
+ },
297
+ fromPrivateKey(privateKey, chainCode, network) {
298
+ const net = network ?? DEFAULT_NETWORK;
299
+ const versions = networkToVersions(net);
300
+ // xprv format (78 bytes):
301
+ // version (4) + depth (1) + parentFingerprint (4) + index (4) +
302
+ // chainCode (32) + 0x00 (1) + privateKey (32)
303
+ const buf = new Uint8Array(78);
304
+ writeUInt32BE(buf, versions.private, 0); // version
305
+ buf[4] = 0; // depth
306
+ writeUInt32BE(buf, 0, 5); // parent fingerprint
307
+ writeUInt32BE(buf, 0, 9); // index
308
+ buf.set(chainCode, 13);
309
+ buf[45] = 0x00; // padding byte before private key
310
+ buf.set(privateKey, 46);
311
+ const xprv = bs58check.encode(buf);
312
+ const hdkey = HDKey.fromExtendedKey(xprv, versions);
313
+ return wrapHDKey(hdkey, net);
314
+ },
315
+ fromSeed(seed, network) {
316
+ const net = network ?? DEFAULT_NETWORK;
317
+ const versions = networkToVersions(net);
318
+ const hdkey = HDKey.fromMasterSeed(seed, versions);
319
+ return wrapHDKey(hdkey, net);
320
+ }
321
+ };
@@ -16,11 +16,12 @@ import type { PsbtLike } from './psbt.js';
16
16
  * These are compatible interfaces for managing BIP32 keys and
17
17
  * public/private key pairs respectively.
18
18
  *
19
- * @param {Object} params - An object with `ECPair` and `BIP32` factories.
19
+ * @param {Object} params - An object with optional `ECPair` and `BIP32` factories.
20
+ * When omitted, built-in adapters using @noble/curves and @scure/bip32 are used.
20
21
  */
21
- export declare function DescriptorsFactory({ ECPair, BIP32 }: {
22
- ECPair: ECPairAPI;
23
- BIP32: BIP32API;
22
+ export declare function DescriptorsFactory({ ECPair, BIP32 }?: {
23
+ ECPair?: ECPairAPI;
24
+ BIP32?: BIP32API;
24
25
  }): {
25
26
  Output: {
26
27
  new ({ descriptor, index, checksumRequired, allowMiniscriptInP2SH, network, preimages, signersPubKeys }: {
@@ -30,6 +30,7 @@ import { varintEncodingLength, decompileScript } from './scriptUtils.js';
30
30
  import { hash160, compareBytes, equalBytes, concatBytes } from '@scure/btc-signer/utils.js';
31
31
  import { hex } from '@scure/base';
32
32
  import { sha256 } from '@noble/hashes/sha2.js';
33
+ import { nobleECPair, scureBIP32 } from './adapters.js';
33
34
  import { computeFinalScripts, updatePsbt } from './psbt.js';
34
35
  import { DescriptorChecksum } from './checksum.js';
35
36
  import { parseKeyExpression as globalParseKeyExpression } from './keyExpressions.js';
@@ -178,9 +179,10 @@ function parseSortedMulti(inner) {
178
179
  * These are compatible interfaces for managing BIP32 keys and
179
180
  * public/private key pairs respectively.
180
181
  *
181
- * @param {Object} params - An object with `ECPair` and `BIP32` factories.
182
+ * @param {Object} params - An object with optional `ECPair` and `BIP32` factories.
183
+ * When omitted, built-in adapters using @noble/curves and @scure/bip32 are used.
182
184
  */
183
- export function DescriptorsFactory({ ECPair, BIP32 }) {
185
+ export function DescriptorsFactory({ ECPair = nobleECPair, BIP32 = scureBIP32 } = {}) {
184
186
  var _Output_instances, _Output_payment, _Output_preimages, _Output_signersPubKeys, _Output_miniscript, _Output_witnessScript, _Output_redeemScript, _Output_isSegwit, _Output_isTaproot, _Output_expandedExpression, _Output_expandedMiniscript, _Output_expansionMap, _Output_network, _Output_getTimeConstraints, _Output_assertPsbtInput;
185
187
  /**
186
188
  * Takes a string key expression (xpub, xprv, pubkey or wif) and parses it
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export type { KeyInfo, Expansion } from './types.js';
2
+ export type { ECPairAPI, ECPairInterface, BIP32API, BIP32Interface } from './types.js';
2
3
  export type { OutputInstance } from './descriptors.js';
3
4
  export { DescriptorsFactory, OutputConstructor } from './descriptors.js';
4
5
  export { DescriptorChecksum as checksum } from './checksum.js';
@@ -10,3 +11,91 @@ export { scriptExpressions };
10
11
  export type { PsbtLike } from './psbt.js';
11
12
  export { networks } from './networks.js';
12
13
  export type { Network } from './networks.js';
14
+ export { nobleECPair, scureBIP32 } from './adapters.js';
15
+ export declare const defaultFactory: {
16
+ Output: {
17
+ new ({ descriptor, index, checksumRequired, allowMiniscriptInP2SH, network, preimages, signersPubKeys }: {
18
+ descriptor: string;
19
+ index?: number;
20
+ checksumRequired?: boolean;
21
+ allowMiniscriptInP2SH?: boolean;
22
+ network?: import("./networks.js").Network;
23
+ preimages?: import("./types.js").Preimage[];
24
+ signersPubKeys?: Uint8Array[];
25
+ }): {
26
+ readonly "__#private@#payment": import("@scure/btc-signer/payment.js").P2Ret;
27
+ readonly "__#private@#preimages": import("./types.js").Preimage[];
28
+ readonly "__#private@#signersPubKeys": Uint8Array[];
29
+ readonly "__#private@#miniscript"?: string;
30
+ readonly "__#private@#witnessScript"?: Uint8Array;
31
+ readonly "__#private@#redeemScript"?: Uint8Array;
32
+ readonly "__#private@#isSegwit"?: boolean;
33
+ readonly "__#private@#isTaproot"?: boolean;
34
+ readonly "__#private@#expandedExpression"?: string;
35
+ readonly "__#private@#expandedMiniscript"?: string;
36
+ readonly "__#private@#expansionMap"?: import("./types.js").ExpansionMap;
37
+ readonly "__#private@#network": import("./networks.js").Network;
38
+ "__#private@#getTimeConstraints"(): import("./types.js").TimeConstraints | undefined;
39
+ getPayment(): import("@scure/btc-signer/payment.js").P2Ret;
40
+ getAddress(): string;
41
+ getScriptPubKey(): Uint8Array;
42
+ getScriptSatisfaction(signatures: import("./types.js").PartialSig[] | "DANGEROUSLY_USE_FAKE_SIGNATURES"): Uint8Array;
43
+ getSequence(): number | undefined;
44
+ getLockTime(): number | undefined;
45
+ getWitnessScript(): Uint8Array | undefined;
46
+ getRedeemScript(): Uint8Array | undefined;
47
+ getNetwork(): import("./networks.js").Network;
48
+ isSegwit(): boolean | undefined;
49
+ isTaproot(): boolean | undefined;
50
+ guessOutput(): {
51
+ isPKH: boolean;
52
+ isWPKH: boolean;
53
+ isSH: boolean;
54
+ isWSH: boolean;
55
+ isTR: boolean;
56
+ };
57
+ inputWeight(isSegwitTx: boolean, signatures: import("./types.js").PartialSig[] | "DANGEROUSLY_USE_FAKE_SIGNATURES"): number;
58
+ outputWeight(): number;
59
+ updatePsbtAsInput({ psbt, txHex, txId, value, vout, rbf }: {
60
+ psbt: import("./psbt.js").PsbtLike;
61
+ txHex?: string;
62
+ txId?: string;
63
+ value?: number;
64
+ vout: number;
65
+ rbf?: boolean;
66
+ }): ({ psbt, validate }: {
67
+ psbt: import("./psbt.js").PsbtLike;
68
+ validate?: boolean | undefined;
69
+ }) => void;
70
+ updatePsbtAsOutput({ psbt, value }: {
71
+ psbt: import("./psbt.js").PsbtLike;
72
+ value: number | bigint;
73
+ }): void;
74
+ "__#private@#assertPsbtInput"({ psbt, index }: {
75
+ psbt: import("./psbt.js").PsbtLike;
76
+ index: number;
77
+ }): void;
78
+ finalizePsbtInput({ index, psbt, validate }: {
79
+ index: number;
80
+ psbt: import("./psbt.js").PsbtLike;
81
+ validate?: boolean | undefined;
82
+ }): void;
83
+ expand(): {
84
+ expansionMap?: import("./types.js").ExpansionMap;
85
+ expandedMiniscript?: string;
86
+ miniscript?: string;
87
+ expandedExpression?: string;
88
+ };
89
+ };
90
+ };
91
+ parseKeyExpression: import("./types.js").ParseKeyExpression;
92
+ expand: ({ descriptor, index, checksumRequired, network, allowMiniscriptInP2SH }: {
93
+ descriptor: string;
94
+ index?: number;
95
+ checksumRequired?: boolean;
96
+ network?: import("./networks.js").Network;
97
+ allowMiniscriptInP2SH?: boolean;
98
+ }) => import("./types.js").Expansion;
99
+ ECPair: import("./types.js").ECPairAPI;
100
+ BIP32: import("./types.js").BIP32API;
101
+ };
package/dist/index.js CHANGED
@@ -8,3 +8,8 @@ export { keyExpressionBIP32 } from './keyExpressions.js';
8
8
  import * as scriptExpressions from './scriptExpressions.js';
9
9
  export { scriptExpressions };
10
10
  export { networks } from './networks.js';
11
+ // Built-in adapters using @noble/curves and @scure/bip32
12
+ export { nobleECPair, scureBIP32 } from './adapters.js';
13
+ // Pre-built factory using built-in adapters (zero-config convenience)
14
+ import { DescriptorsFactory } from './descriptors.js';
15
+ export const defaultFactory = DescriptorsFactory();
@@ -6,7 +6,23 @@ import { hash160 } from '@scure/btc-signer/utils.js';
6
6
  import { hex } from '@scure/base';
7
7
  import { parseKeyExpression } from './keyExpressions.js';
8
8
  import * as RE from './re.js';
9
- import { compileMiniscript, satisfier } from '@bitcoinerlab/miniscript';
9
+ import { createRequire } from 'module';
10
+ // Lazy-load @bitcoinerlab/miniscript (optional peer dependency).
11
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
12
+ let _miniscriptLib;
13
+ function getMiniscriptLib() {
14
+ if (!_miniscriptLib) {
15
+ try {
16
+ const require = createRequire(import.meta.url);
17
+ _miniscriptLib = require('@bitcoinerlab/miniscript');
18
+ }
19
+ catch {
20
+ throw new Error('@bitcoinerlab/miniscript is required for miniscript descriptors. ' +
21
+ 'Install it: npm install @bitcoinerlab/miniscript');
22
+ }
23
+ }
24
+ return _miniscriptLib;
25
+ }
10
26
  /**
11
27
  * Expand a miniscript to a generalized form using variables instead of key
12
28
  * expressions. Variables will be of this form: @0, @1, ...
@@ -84,7 +100,7 @@ function substituteAsm({ expandedAsm, expansionMap }) {
84
100
  return asm;
85
101
  }
86
102
  export function miniscript2Script({ expandedMiniscript, expansionMap }) {
87
- const compiled = compileMiniscript(expandedMiniscript);
103
+ const compiled = getMiniscriptLib().compileMiniscript(expandedMiniscript);
88
104
  if (compiled.issane !== true) {
89
105
  throw new Error(`Error: Miniscript ${expandedMiniscript} is not sane`);
90
106
  }
@@ -128,7 +144,7 @@ export function satisfyMiniscript({ expandedMiniscript, expansionMap, signatures
128
144
  const expandedKnownsMap = { ...preimageMap, ...expandedSignatureMap };
129
145
  const knowns = Object.keys(expandedKnownsMap);
130
146
  //satisfier verifies again internally whether expandedKnownsMap with given knowns is sane
131
- const { nonMalleableSats } = satisfier(expandedMiniscript, { knowns });
147
+ const { nonMalleableSats } = getMiniscriptLib().satisfier(expandedMiniscript, { knowns });
132
148
  if (!Array.isArray(nonMalleableSats) || !nonMalleableSats[0])
133
149
  throw new Error(`Error: unresolvable miniscript ${expandedMiniscript}`);
134
150
  let sat;
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@kukks/bitcoin-descriptors",
3
3
  "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.",
4
4
  "homepage": "https://github.com/Kukks/descriptors",
5
- "version": "3.0.2",
5
+ "version": "3.2.1",
6
6
  "author": "Jose-Luis Landabaso",
7
7
  "license": "MIT",
8
8
  "repository": {
@@ -51,14 +51,22 @@
51
51
  ],
52
52
  "devDependencies": {
53
53
  "@bitcoinerlab/configs": "^2.0.0",
54
+ "@bitcoinerlab/miniscript": "^1.4.3",
54
55
  "bip39": "^3.0.4",
55
56
  "bip65": "^1.0.3",
56
57
  "bip68": "^1.0.4",
57
58
  "regtest-client": "^0.2.1",
58
59
  "yargs": "^17.7.2"
59
60
  },
61
+ "peerDependencies": {
62
+ "@bitcoinerlab/miniscript": "^1.4.3"
63
+ },
64
+ "peerDependenciesMeta": {
65
+ "@bitcoinerlab/miniscript": {
66
+ "optional": true
67
+ }
68
+ },
60
69
  "dependencies": {
61
- "@bitcoinerlab/miniscript": "^1.4.3",
62
70
  "@noble/curves": "^2.0.1",
63
71
  "@noble/hashes": "^2.0.1",
64
72
  "@scure/base": "^2.0.0",