@kukks/bitcoin-descriptors 3.0.2

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.
@@ -0,0 +1,129 @@
1
+ // Copyright (c) 2025 Jose-Luis Landabaso - https://bitcoinerlab.com
2
+ // Distributed under the MIT software license
3
+ import { readUInt32BE } from './scriptUtils.js';
4
+ /**
5
+ * Adapts a BIP32Interface node to the HDKey interface expected by @scure/btc-signer.
6
+ * scure's Transaction.sign/signIdx auto-handle Taproot tweaking when given an HDKey.
7
+ */
8
+ function toScureHDKey(node) {
9
+ if (!node.privateKey)
10
+ throw new Error('Cannot create HDKey signer from neutered node');
11
+ return {
12
+ publicKey: node.publicKey,
13
+ privateKey: node.privateKey,
14
+ fingerprint: readUInt32BE(node.fingerprint),
15
+ derive(path) {
16
+ return toScureHDKey(node.derivePath(path));
17
+ },
18
+ deriveChild(index) {
19
+ return toScureHDKey(node.derive(index));
20
+ },
21
+ sign(hash) {
22
+ return node.sign(hash);
23
+ }
24
+ };
25
+ }
26
+ /**
27
+ * Derives the private key from a BIP32 node using tapBip32Derivation info
28
+ * and signs the Taproot input directly with the raw private key.
29
+ * This is needed because @scure/btc-signer's signIdx HDKey path only checks
30
+ * bip32Derivation, not tapBip32Derivation.
31
+ */
32
+ function signTapBip32(psbt, index, masterNode) {
33
+ const input = psbt.getInput(index);
34
+ const tapBip32 = input.tapBip32Derivation;
35
+ if (!tapBip32 || !tapBip32.length)
36
+ return false;
37
+ const fp = readUInt32BE(masterNode.fingerprint);
38
+ let signed = false;
39
+ for (const [, { der }] of tapBip32) {
40
+ if (der.fingerprint !== fp)
41
+ continue;
42
+ let node = masterNode;
43
+ for (const childIdx of der.path) {
44
+ node = node.derive(childIdx);
45
+ }
46
+ if (!node.privateKey)
47
+ throw new Error('Cannot sign: derived node has no private key');
48
+ psbt.signIdx(node.privateKey, index);
49
+ signed = true;
50
+ }
51
+ return signed;
52
+ }
53
+ /**
54
+ * Signs a specific input of a PSBT with an ECPair.
55
+ *
56
+ * Uses @scure/btc-signer's signIdx which automatically handles
57
+ * Taproot key tweaking internally.
58
+ *
59
+ * @param {Object} params - The parameters object
60
+ * @param {PsbtLike} params.psbt - The PSBT to sign
61
+ * @param {number} params.index - The input index to sign
62
+ * @param {ECPairInterface} params.ecpair - The ECPair to sign with
63
+ */
64
+ export function signInputECPair({ psbt, index, ecpair }) {
65
+ if (!ecpair.privateKey)
66
+ throw new Error('Missing private key');
67
+ psbt.signIdx(ecpair.privateKey, index);
68
+ }
69
+ /**
70
+ * Signs all inputs of a PSBT with an ECPair.
71
+ *
72
+ * Uses @scure/btc-signer's sign which automatically handles
73
+ * Taproot key tweaking internally.
74
+ *
75
+ * @param {Object} params - The parameters object
76
+ * @param {PsbtLike} params.psbt - The PSBT to sign
77
+ * @param {ECPairInterface} params.ecpair - The ECPair to sign with
78
+ */
79
+ export function signECPair({ psbt, ecpair }) {
80
+ if (!ecpair.privateKey)
81
+ throw new Error('Missing private key');
82
+ const signed = psbt.sign(ecpair.privateKey);
83
+ if (signed === 0) {
84
+ throw new Error('No inputs were signed');
85
+ }
86
+ }
87
+ /**
88
+ * Signs a specific input of a PSBT with a BIP32 node.
89
+ *
90
+ * Handles Taproot inputs via tapBip32Derivation, and non-Taproot via
91
+ * @scure/btc-signer's signIdx with an HDKey adapter.
92
+ *
93
+ * @param {Object} params - The parameters object
94
+ * @param {PsbtLike} params.psbt - The PSBT to sign
95
+ * @param {number} params.index - The input index to sign
96
+ * @param {BIP32Interface} params.node - The BIP32 node to sign with
97
+ */
98
+ export function signInputBIP32({ psbt, index, node }) {
99
+ if (!signTapBip32(psbt, index, node)) {
100
+ psbt.signIdx(toScureHDKey(node), index);
101
+ }
102
+ }
103
+ /**
104
+ * Signs all inputs of a PSBT with a BIP32 master node.
105
+ *
106
+ * First signs any Taproot inputs via tapBip32Derivation, then uses
107
+ * @scure/btc-signer's sign for remaining non-Taproot inputs.
108
+ *
109
+ * @param {Object} params - The parameters object
110
+ * @param {PsbtLike} params.psbt - The PSBT to sign
111
+ * @param {BIP32Interface} params.masterNode - The BIP32 master node to sign with
112
+ */
113
+ export function signBIP32({ psbt, masterNode }) {
114
+ let tapSigned = 0;
115
+ for (let i = 0; i < psbt.inputsLength; i++) {
116
+ if (signTapBip32(psbt, i, masterNode))
117
+ tapSigned++;
118
+ }
119
+ let nonTapSigned = 0;
120
+ try {
121
+ nonTapSigned = psbt.sign(toScureHDKey(masterNode));
122
+ }
123
+ catch {
124
+ // sign() throws 'No inputs signed' when no bip32Derivation matches
125
+ }
126
+ if (tapSigned + nonTapSigned === 0) {
127
+ throw new Error('No inputs were signed');
128
+ }
129
+ }
@@ -0,0 +1,263 @@
1
+ import type { Network } from './networks.js';
2
+ import type { P2Ret } from '@scure/btc-signer/payment.js';
3
+ /**
4
+ * Interface for an EC key pair (replaces ecpair's ECPairInterface).
5
+ * Only the fields used by this library are included.
6
+ */
7
+ export interface ECPairInterface {
8
+ publicKey: Uint8Array;
9
+ privateKey?: Uint8Array;
10
+ compressed: boolean;
11
+ network?: Network;
12
+ sign(hash: Uint8Array): Uint8Array;
13
+ verify(hash: Uint8Array, signature: Uint8Array): boolean;
14
+ toWIF(): string;
15
+ tweak(t: Uint8Array): ECPairInterface;
16
+ }
17
+ /**
18
+ * API for creating EC key pairs (replaces ecpair's ECPairAPI).
19
+ */
20
+ export interface ECPairAPI {
21
+ fromPublicKey(publicKey: Uint8Array, options?: {
22
+ network?: Network;
23
+ compressed?: boolean;
24
+ }): ECPairInterface;
25
+ fromPrivateKey(privateKey: Uint8Array, options?: {
26
+ network?: Network;
27
+ compressed?: boolean;
28
+ }): ECPairInterface;
29
+ fromWIF(wif: string, network?: Network | Network[]): ECPairInterface;
30
+ makeRandom(options?: {
31
+ network?: Network;
32
+ compressed?: boolean;
33
+ }): ECPairInterface;
34
+ isPoint(p: Uint8Array): boolean;
35
+ }
36
+ /**
37
+ * Interface for a BIP32 HD key (replaces bip32's BIP32Interface).
38
+ * Only the fields used by this library are included.
39
+ */
40
+ export interface BIP32Interface {
41
+ publicKey: Uint8Array;
42
+ privateKey?: Uint8Array;
43
+ chainCode: Uint8Array;
44
+ fingerprint: Uint8Array;
45
+ depth: number;
46
+ index: number;
47
+ parentFingerprint: number;
48
+ network: Network;
49
+ derivePath(path: string): BIP32Interface;
50
+ derive(index: number): BIP32Interface;
51
+ deriveHardened(index: number): BIP32Interface;
52
+ neutered(): BIP32Interface;
53
+ toBase58(): string;
54
+ sign(hash: Uint8Array): Uint8Array;
55
+ verify(hash: Uint8Array, signature: Uint8Array): boolean;
56
+ isNeutered(): boolean;
57
+ toWIF(): string;
58
+ }
59
+ /**
60
+ * API for creating BIP32 keys (replaces bip32's BIP32API).
61
+ */
62
+ export interface BIP32API {
63
+ fromBase58(base58: string, network?: Network): BIP32Interface;
64
+ fromPublicKey(publicKey: Uint8Array, chainCode: Uint8Array, network?: Network): BIP32Interface;
65
+ fromPrivateKey(privateKey: Uint8Array, chainCode: Uint8Array, network?: Network): BIP32Interface;
66
+ fromSeed(seed: Uint8Array, network?: Network): BIP32Interface;
67
+ }
68
+ /**
69
+ * PartialSig (replaces bip174's PartialSig)
70
+ */
71
+ export interface PartialSig {
72
+ pubkey: Uint8Array;
73
+ signature: Uint8Array;
74
+ }
75
+ /**
76
+ * Preimage
77
+ * See {@link Output}
78
+ */
79
+ export type Preimage = {
80
+ /**
81
+ * Use same string expressions as in miniscript. For example: "sha256(cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204)" or "ripemd160(095ff41131e5946f3c85f79e44adbcf8e27e080e)"
82
+ *
83
+ * Accepted functions: sha256, hash256, ripemd160, hash160
84
+ *
85
+ * Digests must be: 64-character HEX for sha256, hash160 or 30-character HEX for ripemd160 or hash160.
86
+ */
87
+ digest: string;
88
+ /**
89
+ * Hex encoded preimage. Preimages are always 32 bytes (so, 64 character in hex).
90
+ */
91
+ preimage: string;
92
+ };
93
+ export type TimeConstraints = {
94
+ nLockTime: number | undefined;
95
+ nSequence: number | undefined;
96
+ };
97
+ /**
98
+ * See {@link _Internal_.ParseKeyExpression | ParseKeyExpression}.
99
+ */
100
+ export type KeyInfo = {
101
+ keyExpression: string;
102
+ pubkey?: Uint8Array;
103
+ ecpair?: ECPairInterface;
104
+ bip32?: BIP32Interface;
105
+ masterFingerprint?: Uint8Array;
106
+ originPath?: string;
107
+ keyPath?: string;
108
+ path?: string;
109
+ };
110
+ /**
111
+ * An `ExpansionMap` contains destructured information of a descritptor expression.
112
+ *
113
+ * For example, this descriptor `sh(wsh(andor(pk(0252972572d465d016d4c501887b8df303eee3ed602c056b1eb09260dfa0da0ab2),older(8640),pk([d34db33f/49'/0'/0']tpubDCdxmvzJ5QBjTN8oCjjyT2V58AyZvA1fkmCeZRC75QMoaHcVP2m45Bv3hmnR7ttAwkb2UNYyoXdHVt4gwBqRrJqLUU2JrM43HippxiWpHra/1/2/3/4/*))))` has the following
114
+ * `expandedExpression`: `sh(wsh(andor(pk(@0),older(8640),pk(@1))))`
115
+ *
116
+ * `key`'s are set using this format: `@i`, where `i` is an integer starting from `0` assigned by parsing and retrieving keys from the descriptor from left to right.
117
+ *
118
+ * For the given example, the `ExpansionMap` is:
119
+ *
120
+ * ```javascript
121
+ * {
122
+ * '@0': {
123
+ * keyExpression:
124
+ * '0252972572d465d016d4c501887b8df303eee3ed602c056b1eb09260dfa0da0ab2'
125
+ * },
126
+ * '@1': {
127
+ * keyExpression:
128
+ * "[d34db33f/49'/0'/0']tpubDCdxmvzJ5QBjTN8oCjjyT2V58AyZvA1fkmCeZRC75QMoaHcVP2m45Bv3hmnR7ttAwkb2UNYyoXdHVt4gwBqRrJqLUU2JrM43HippxiWpHra/1/2/3/4/*",
129
+ * keyPath: '/1/2/3/4/*',
130
+ * originPath: "/49'/0'/0'",
131
+ * path: "m/49'/0'/0'/1/2/3/4/*",
132
+ * // Other relevant properties of the type `KeyInfo`: `pubkey`, `ecpair` & `bip32` interfaces, `masterFingerprint`, etc.
133
+ * }
134
+ * }
135
+ *```
136
+ *
137
+ *
138
+ */
139
+ export type ExpansionMap = {
140
+ [key: string]: KeyInfo;
141
+ };
142
+ /** @ignore */
143
+ interface XOnlyPointAddTweakResult {
144
+ parity: 1 | 0;
145
+ xOnlyPubkey: Uint8Array;
146
+ }
147
+ /** @ignore */
148
+ export interface TinySecp256k1Interface {
149
+ isPoint(p: Uint8Array): boolean;
150
+ pointCompress(p: Uint8Array, compressed?: boolean): Uint8Array;
151
+ isPrivate(d: Uint8Array): boolean;
152
+ pointFromScalar(d: Uint8Array, compressed?: boolean): Uint8Array | null;
153
+ pointAddScalar(p: Uint8Array, tweak: Uint8Array, compressed?: boolean): Uint8Array | null;
154
+ privateAdd(d: Uint8Array, tweak: Uint8Array): Uint8Array | null;
155
+ sign(h: Uint8Array, d: Uint8Array, e?: Uint8Array): Uint8Array;
156
+ signSchnorr?(h: Uint8Array, d: Uint8Array, e?: Uint8Array): Uint8Array;
157
+ verify(h: Uint8Array, Q: Uint8Array, signature: Uint8Array, strict?: boolean): boolean;
158
+ verifySchnorr?(h: Uint8Array, Q: Uint8Array, signature: Uint8Array): boolean;
159
+ xOnlyPointAddTweak(p: Uint8Array, tweak: Uint8Array): XOnlyPointAddTweakResult | null;
160
+ isXOnlyPoint(p: Uint8Array): boolean;
161
+ privateNegate(d: Uint8Array): Uint8Array;
162
+ }
163
+ /**
164
+ * `DescriptorsFactory` creates and returns the {@link DescriptorsFactory | `expand()`}
165
+ * function that parses a descriptor expression and destructures it
166
+ * into its elemental parts. `Expansion` is the type that `expand()` returns.
167
+ */
168
+ export type Expansion = {
169
+ /**
170
+ * The corresponding Payment for the provided expression, if applicable.
171
+ */
172
+ payment?: P2Ret;
173
+ /**
174
+ * The expanded descriptor expression.
175
+ * See {@link ExpansionMap ExpansionMap} for a detailed explanation.
176
+ */
177
+ expandedExpression?: string;
178
+ /**
179
+ * The extracted miniscript from the expression, if any.
180
+ */
181
+ miniscript?: string;
182
+ /**
183
+ * A map of key expressions in the descriptor to their corresponding expanded keys.
184
+ * See {@link ExpansionMap ExpansionMap} for a detailed explanation.
185
+ */
186
+ expansionMap?: ExpansionMap;
187
+ /**
188
+ * A boolean indicating whether the descriptor uses SegWit.
189
+ */
190
+ isSegwit?: boolean;
191
+ /**
192
+ * A boolean indicating whether the descriptor uses Taproot.
193
+ */
194
+ isTaproot?: boolean;
195
+ /**
196
+ * The expanded miniscript, if any.
197
+ * It corresponds to the `expandedExpression` without the top-level script
198
+ * expression.
199
+ */
200
+ expandedMiniscript?: string;
201
+ /**
202
+ * The redeem script for the descriptor, if applicable.
203
+ */
204
+ redeemScript?: Uint8Array;
205
+ /**
206
+ * The witness script for the descriptor, if applicable.
207
+ */
208
+ witnessScript?: Uint8Array;
209
+ /**
210
+ * Whether the descriptor is a ranged-descriptor.
211
+ */
212
+ isRanged: boolean;
213
+ /**
214
+ * This is the preferred or authoritative representation of an output
215
+ * descriptor expression.
216
+ * It removes the checksum and, if it is a ranged-descriptor, it
217
+ * particularizes it to its index.
218
+ */
219
+ canonicalExpression: string;
220
+ };
221
+ /**
222
+ * The {@link DescriptorsFactory | `DescriptorsFactory`} function creates and
223
+ * returns the `parseKeyExpression` function, which is an implementation of this
224
+ * interface.
225
+ *
226
+ * It parses and destructures a key expression string (xpub, xprv, pubkey or
227
+ * wif) into {@link KeyInfo | `KeyInfo`}.
228
+ *
229
+ * For example, given this `keyExpression`: `[d34db33f/49'/0'/0']tpubDCdxmvzJ5QBjTN8oCjjyT2V58AyZvA1fkmCeZRC75QMoaHcVP2m45Bv3hmnR7ttAwkb2UNYyoXdHVt4gwBqRrJqLUU2JrM43HippxiWpHra/1/2/3/4/*`, this is the parsed result:
230
+ *
231
+ * ```javascript
232
+ * {
233
+ * keyExpression:
234
+ * "[d34db33f/49'/0'/0']tpubDCdxmvzJ5QBjTN8oCjjyT2V58AyZvA1fkmCeZRC75QMoaHcVP2m45Bv3hmnR7ttAwkb2UNYyoXdHVt4gwBqRrJqLUU2JrM43HippxiWpHra/1/2/3/4/*",
235
+ * keyPath: '/1/2/3/4/*',
236
+ * originPath: "/49'/0'/0'",
237
+ * path: "m/49'/0'/0'/1/2/3/4/*",
238
+ * // Other relevant properties of the type `KeyInfo`: `pubkey`, `ecpair` & `bip32` interfaces, `masterFingerprint`, etc.
239
+ * }
240
+ * ```
241
+ *
242
+ * See {@link KeyInfo} for the complete list of elements retrieved by this function.
243
+ */
244
+ export interface ParseKeyExpression {
245
+ (params: {
246
+ keyExpression: string;
247
+ /**
248
+ * Indicates if this key expression belongs to a a SegWit output. When set,
249
+ * further checks are done to ensure the public key (if present in the
250
+ * expression) is compressed (33 bytes).
251
+ */
252
+ isSegwit?: boolean;
253
+ /**
254
+ * Indicates if this key expression belongs to a Taproot output.
255
+ * For Taproot, the key must be represented as an x-only public key
256
+ * (32 bytes). If a 33-byte compressed pubkey is derived, it is converted to
257
+ * its x-only representation.
258
+ */
259
+ isTaproot?: boolean;
260
+ network?: Network;
261
+ }): KeyInfo;
262
+ }
263
+ export {};
package/dist/types.js ADDED
@@ -0,0 +1,3 @@
1
+ // Copyright (c) 2023 Jose-Luis Landabaso - https://bitcoinerlab.com
2
+ // Distributed under the MIT software license
3
+ export {};
package/package.json ADDED
@@ -0,0 +1,68 @@
1
+ {
2
+ "name": "@kukks/bitcoin-descriptors",
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
+ "homepage": "https://github.com/Kukks/descriptors",
5
+ "version": "3.0.2",
6
+ "author": "Jose-Luis Landabaso",
7
+ "license": "MIT",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/Kukks/descriptors.git"
11
+ },
12
+ "keywords": [
13
+ "bitcoin",
14
+ "descriptors",
15
+ "miniscript",
16
+ "scure",
17
+ "noble"
18
+ ],
19
+ "bugs": {
20
+ "url": "https://github.com/Kukks/descriptors/issues"
21
+ },
22
+ "type": "module",
23
+ "main": "dist/index.js",
24
+ "types": "dist/index.d.ts",
25
+ "prettier": "@bitcoinerlab/configs/prettierConfig.json",
26
+ "eslintConfig": {
27
+ "extends": "./node_modules/@bitcoinerlab/configs/eslintConfig"
28
+ },
29
+ "jest": {
30
+ "preset": "@bitcoinerlab/configs",
31
+ "extensionsToTreatAsEsm": [],
32
+ "transform": {}
33
+ },
34
+ "scripts": {
35
+ "webdocs": "typedoc --options ./node_modules/@bitcoinerlab/configs/webtypedoc.json",
36
+ "docs": "typedoc --options ./node_modules/@bitcoinerlab/configs/typedoc.json",
37
+ "build:src": "tsc --project tsconfig.src.json",
38
+ "build:fixtures": "node test/tools/generateBitcoinCoreFixtures.cjs -i test/fixtures/descriptor_tests.cpp | npx prettier --parser typescript > test/fixtures/bitcoinCore.ts",
39
+ "build:test": "npm run build:fixtures && tsc --project tsconfig.test.json --resolveJsonModule",
40
+ "build": "npm run build:src && npm run build:test",
41
+ "lint": "./node_modules/@bitcoinerlab/configs/scripts/lint.sh",
42
+ "ensureTester": "./node_modules/@bitcoinerlab/configs/scripts/ensureTester.sh",
43
+ "test:integration:soft": "npm run ensureTester && node test/integration/standardOutputs.js && echo \"\n\n\" && node test/integration/miniscript.js && echo \"\n\n\" && node test/integration/sortedmulti.js",
44
+
45
+ "test:unit": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
46
+ "test": "npm run lint && npm run build && npm run test:unit && npm run test:integration:soft",
47
+ "prepublishOnly": "npm run test"
48
+ },
49
+ "files": [
50
+ "dist"
51
+ ],
52
+ "devDependencies": {
53
+ "@bitcoinerlab/configs": "^2.0.0",
54
+ "bip39": "^3.0.4",
55
+ "bip65": "^1.0.3",
56
+ "bip68": "^1.0.4",
57
+ "regtest-client": "^0.2.1",
58
+ "yargs": "^17.7.2"
59
+ },
60
+ "dependencies": {
61
+ "@bitcoinerlab/miniscript": "^1.4.3",
62
+ "@noble/curves": "^2.0.1",
63
+ "@noble/hashes": "^2.0.1",
64
+ "@scure/base": "^2.0.0",
65
+ "@scure/bip32": "^2.0.1",
66
+ "@scure/btc-signer": "^2.0.1"
67
+ }
68
+ }