@bitcoinerlab/descriptors 2.2.0 → 2.3.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 +10 -0
- package/dist/applyPR2137.d.ts +2 -0
- package/dist/applyPR2137.js +123 -0
- package/dist/descriptors.d.ts +48 -6
- package/dist/descriptors.js +157 -30
- package/dist/keyExpressions.d.ts +8 -1
- package/dist/keyExpressions.js +19 -3
- package/dist/miniscript.d.ts +2 -1
- package/dist/miniscript.js +9 -2
- package/dist/psbt.d.ts +3 -1
- package/dist/psbt.js +41 -16
- package/dist/re.d.ts +7 -2
- package/dist/re.js +17 -8
- package/dist/scriptExpressions.d.ts +33 -0
- package/dist/scriptExpressions.js +3 -1
- package/dist/signers.d.ts +37 -0
- package/dist/signers.js +79 -3
- package/dist/types.d.ts +12 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -77,6 +77,16 @@ For miniscript-based descriptors, the `signersPubKeys` parameter in the constuct
|
|
|
77
77
|
|
|
78
78
|
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
79
|
|
|
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
|
|
87
|
+
|
|
88
|
+
These descriptors can be used with various key expressions, including raw public keys, BIP32 derivation paths, and more.
|
|
89
|
+
|
|
80
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.
|
|
81
91
|
|
|
82
92
|
To call `updatePsbtAsInput()`, use the following syntax:
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
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");
|
|
8
|
+
const toXOnly = (pubKey) => pubKey.length === 32 ? pubKey : pubKey.slice(1, 33);
|
|
9
|
+
function range(n) {
|
|
10
|
+
return [...Array(n).keys()];
|
|
11
|
+
}
|
|
12
|
+
function tapBranchHash(a, b) {
|
|
13
|
+
return (0, crypto_1.taggedHash)('TapBranch', Buffer.concat([a, b]));
|
|
14
|
+
}
|
|
15
|
+
function calculateScriptTreeMerkleRoot(leafHashes) {
|
|
16
|
+
if (!leafHashes || leafHashes.length === 0) {
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
// sort the leaf nodes
|
|
20
|
+
leafHashes.sort(Buffer.compare);
|
|
21
|
+
// create the initial hash node
|
|
22
|
+
let currentLevel = leafHashes;
|
|
23
|
+
// build Merkle Tree
|
|
24
|
+
while (currentLevel.length > 1) {
|
|
25
|
+
const nextLevel = [];
|
|
26
|
+
for (let i = 0; i < currentLevel.length; i += 2) {
|
|
27
|
+
const left = currentLevel[i];
|
|
28
|
+
const right = i + 1 < currentLevel.length ? currentLevel[i + 1] : left;
|
|
29
|
+
nextLevel.push(i + 1 < currentLevel.length ? tapBranchHash(left, right) : left);
|
|
30
|
+
}
|
|
31
|
+
currentLevel = nextLevel;
|
|
32
|
+
}
|
|
33
|
+
return currentLevel[0];
|
|
34
|
+
}
|
|
35
|
+
function getTweakSignersFromHD(inputIndex, inputs, hdKeyPair) {
|
|
36
|
+
const input = (0, utils_1.checkForInput)(inputs, inputIndex);
|
|
37
|
+
if (!input.tapBip32Derivation || input.tapBip32Derivation.length === 0) {
|
|
38
|
+
throw new Error('Need tapBip32Derivation to sign with HD');
|
|
39
|
+
}
|
|
40
|
+
const myDerivations = input.tapBip32Derivation
|
|
41
|
+
.map(bipDv => {
|
|
42
|
+
if (bipDv.masterFingerprint.equals(hdKeyPair.fingerprint)) {
|
|
43
|
+
return bipDv;
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
})
|
|
49
|
+
.filter(v => !!v);
|
|
50
|
+
if (myDerivations.length === 0) {
|
|
51
|
+
throw new Error('Need one tapBip32Derivation masterFingerprint to match the HDSigner fingerprint');
|
|
52
|
+
}
|
|
53
|
+
const signers = myDerivations.map(bipDv => {
|
|
54
|
+
const node = hdKeyPair.derivePath(bipDv.path);
|
|
55
|
+
if (!bipDv.pubkey.equals(toXOnly(node.publicKey))) {
|
|
56
|
+
throw new Error('pubkey did not match tapBip32Derivation');
|
|
57
|
+
}
|
|
58
|
+
const h = calculateScriptTreeMerkleRoot(bipDv.leafHashes);
|
|
59
|
+
const tweakValue = (0, bip341_1.tapTweakHash)(toXOnly(node.publicKey), h);
|
|
60
|
+
return node.tweak(tweakValue);
|
|
61
|
+
});
|
|
62
|
+
return signers;
|
|
63
|
+
}
|
|
64
|
+
function getSignersFromHD(inputIndex, inputs, hdKeyPair) {
|
|
65
|
+
const input = (0, utils_1.checkForInput)(inputs, inputIndex);
|
|
66
|
+
if ((0, bip371_1.isTaprootInput)(input)) {
|
|
67
|
+
return getTweakSignersFromHD(inputIndex, inputs, hdKeyPair);
|
|
68
|
+
}
|
|
69
|
+
if (!input.bip32Derivation || input.bip32Derivation.length === 0) {
|
|
70
|
+
throw new Error('Need bip32Derivation to sign with HD');
|
|
71
|
+
}
|
|
72
|
+
const myDerivations = input.bip32Derivation
|
|
73
|
+
.map(bipDv => {
|
|
74
|
+
if (bipDv.masterFingerprint.equals(hdKeyPair.fingerprint)) {
|
|
75
|
+
return bipDv;
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
})
|
|
81
|
+
.filter(v => !!v);
|
|
82
|
+
if (myDerivations.length === 0) {
|
|
83
|
+
throw new Error('Need one bip32Derivation masterFingerprint to match the HDSigner fingerprint');
|
|
84
|
+
}
|
|
85
|
+
const signers = myDerivations.map(bipDv => {
|
|
86
|
+
const node = hdKeyPair.derivePath(bipDv.path);
|
|
87
|
+
if (!bipDv.pubkey.equals(node.publicKey)) {
|
|
88
|
+
throw new Error('pubkey did not match bip32Derivation');
|
|
89
|
+
}
|
|
90
|
+
return node;
|
|
91
|
+
});
|
|
92
|
+
return signers;
|
|
93
|
+
}
|
|
94
|
+
const applyPR2137 = (psbt) => {
|
|
95
|
+
psbt.signInputHD = function signInputHD(inputIndex, hdKeyPair, sighashTypes) {
|
|
96
|
+
if (!hdKeyPair || !hdKeyPair.publicKey || !hdKeyPair.fingerprint) {
|
|
97
|
+
throw new Error('Need HDSigner to sign input');
|
|
98
|
+
}
|
|
99
|
+
const signers = getSignersFromHD(inputIndex, this.data.inputs, hdKeyPair);
|
|
100
|
+
signers.forEach(signer => this.signInput(inputIndex, signer, sighashTypes));
|
|
101
|
+
return this;
|
|
102
|
+
};
|
|
103
|
+
psbt.signAllInputsHD = function signAllInputsHD(hdKeyPair, sighashTypes) {
|
|
104
|
+
if (!hdKeyPair || !hdKeyPair.publicKey || !hdKeyPair.fingerprint) {
|
|
105
|
+
throw new Error('Need HDSigner to sign input');
|
|
106
|
+
}
|
|
107
|
+
const results = [];
|
|
108
|
+
for (const i of range(psbt.data.inputs.length)) {
|
|
109
|
+
try {
|
|
110
|
+
psbt.signInputHD(i, hdKeyPair, sighashTypes);
|
|
111
|
+
results.push(true);
|
|
112
|
+
}
|
|
113
|
+
catch (err) {
|
|
114
|
+
results.push(false);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if (results.every(v => v === false)) {
|
|
118
|
+
throw new Error('No inputs were signed');
|
|
119
|
+
}
|
|
120
|
+
return psbt;
|
|
121
|
+
};
|
|
122
|
+
};
|
|
123
|
+
exports.applyPR2137 = applyPR2137;
|
package/dist/descriptors.d.ts
CHANGED
|
@@ -47,6 +47,7 @@ export declare function DescriptorsFactory(ecc: TinySecp256k1Interface): {
|
|
|
47
47
|
readonly "__#1@#witnessScript"?: Buffer;
|
|
48
48
|
readonly "__#1@#redeemScript"?: Buffer;
|
|
49
49
|
readonly "__#1@#isSegwit"?: boolean;
|
|
50
|
+
readonly "__#1@#isTaproot"?: boolean;
|
|
50
51
|
readonly "__#1@#expandedExpression"?: string;
|
|
51
52
|
readonly "__#1@#expandedMiniscript"?: string;
|
|
52
53
|
readonly "__#1@#expansionMap"?: ExpansionMap;
|
|
@@ -125,13 +126,29 @@ export declare function DescriptorsFactory(ecc: TinySecp256k1Interface): {
|
|
|
125
126
|
*/
|
|
126
127
|
isSegwit(): boolean | undefined;
|
|
127
128
|
/**
|
|
128
|
-
*
|
|
129
|
-
|
|
129
|
+
* Whether this `Output` is Taproot.
|
|
130
|
+
*/
|
|
131
|
+
isTaproot(): boolean | undefined;
|
|
132
|
+
/**
|
|
133
|
+
* Attempts to determine the type of output script by testing it against
|
|
134
|
+
* various payment types.
|
|
135
|
+
*
|
|
136
|
+
* This method tries to identify if the output is one of the following types:
|
|
137
|
+
* - P2SH (Pay to Script Hash)
|
|
138
|
+
* - P2WSH (Pay to Witness Script Hash)
|
|
139
|
+
* - P2WPKH (Pay to Witness Public Key Hash)
|
|
140
|
+
* - P2PKH (Pay to Public Key Hash)
|
|
141
|
+
* - P2TR (Pay to Taproot)
|
|
142
|
+
*
|
|
143
|
+
* @returns An object { isPKH: boolean; isWPKH: boolean; isSH: boolean; isWSH: boolean; isTR: boolean;}
|
|
144
|
+
* with boolean properties indicating the detected output type
|
|
130
145
|
*/
|
|
131
146
|
guessOutput(): {
|
|
132
147
|
isPKH: boolean;
|
|
133
148
|
isWPKH: boolean;
|
|
134
149
|
isSH: boolean;
|
|
150
|
+
isWSH: boolean;
|
|
151
|
+
isTR: boolean;
|
|
135
152
|
};
|
|
136
153
|
/**
|
|
137
154
|
* Computes the Weight Unit contributions of this Output as if it were the
|
|
@@ -140,8 +157,12 @@ export declare function DescriptorsFactory(ecc: TinySecp256k1Interface): {
|
|
|
140
157
|
* *NOTE:* When the descriptor in an input is `addr(address)`, it is assumed
|
|
141
158
|
* that any `addr(SH_TYPE_ADDRESS)` is in fact a Segwit `SH_WPKH`
|
|
142
159
|
* (Script Hash-Witness Public Key Hash).
|
|
160
|
+
*, Also any `addr(SINGLE_KEY_ADDRESS)` * is assumed to be a single key Taproot
|
|
161
|
+
* address (like those defined in BIP86).
|
|
143
162
|
* For inputs using arbitrary scripts (not standard addresses),
|
|
144
|
-
* use a descriptor in the format `sh(MINISCRIPT)`.
|
|
163
|
+
* use a descriptor in the format `sh(MINISCRIPT)` or `tr(MINISCRIPT)`.
|
|
164
|
+
* Note however that tr(MINISCRIPT) is not yet supported for non-single-key
|
|
165
|
+
* expressions.
|
|
145
166
|
*/
|
|
146
167
|
inputWeight(isSegwitTx: boolean, signatures: PartialSig[] | 'DANGEROUSLY_USE_FAKE_SIGNATURES'): number;
|
|
147
168
|
/**
|
|
@@ -339,6 +360,7 @@ export declare function DescriptorsFactory(ecc: TinySecp256k1Interface): {
|
|
|
339
360
|
readonly "__#1@#witnessScript"?: Buffer;
|
|
340
361
|
readonly "__#1@#redeemScript"?: Buffer;
|
|
341
362
|
readonly "__#1@#isSegwit"?: boolean;
|
|
363
|
+
readonly "__#1@#isTaproot"?: boolean;
|
|
342
364
|
readonly "__#1@#expandedExpression"?: string;
|
|
343
365
|
readonly "__#1@#expandedMiniscript"?: string;
|
|
344
366
|
readonly "__#1@#expansionMap"?: ExpansionMap;
|
|
@@ -417,13 +439,29 @@ export declare function DescriptorsFactory(ecc: TinySecp256k1Interface): {
|
|
|
417
439
|
*/
|
|
418
440
|
isSegwit(): boolean | undefined;
|
|
419
441
|
/**
|
|
420
|
-
*
|
|
421
|
-
|
|
442
|
+
* Whether this `Output` is Taproot.
|
|
443
|
+
*/
|
|
444
|
+
isTaproot(): boolean | undefined;
|
|
445
|
+
/**
|
|
446
|
+
* Attempts to determine the type of output script by testing it against
|
|
447
|
+
* various payment types.
|
|
448
|
+
*
|
|
449
|
+
* This method tries to identify if the output is one of the following types:
|
|
450
|
+
* - P2SH (Pay to Script Hash)
|
|
451
|
+
* - P2WSH (Pay to Witness Script Hash)
|
|
452
|
+
* - P2WPKH (Pay to Witness Public Key Hash)
|
|
453
|
+
* - P2PKH (Pay to Public Key Hash)
|
|
454
|
+
* - P2TR (Pay to Taproot)
|
|
455
|
+
*
|
|
456
|
+
* @returns An object { isPKH: boolean; isWPKH: boolean; isSH: boolean; isWSH: boolean; isTR: boolean;}
|
|
457
|
+
* with boolean properties indicating the detected output type
|
|
422
458
|
*/
|
|
423
459
|
guessOutput(): {
|
|
424
460
|
isPKH: boolean;
|
|
425
461
|
isWPKH: boolean;
|
|
426
462
|
isSH: boolean;
|
|
463
|
+
isWSH: boolean;
|
|
464
|
+
isTR: boolean;
|
|
427
465
|
};
|
|
428
466
|
/**
|
|
429
467
|
* Computes the Weight Unit contributions of this Output as if it were the
|
|
@@ -432,8 +470,12 @@ export declare function DescriptorsFactory(ecc: TinySecp256k1Interface): {
|
|
|
432
470
|
* *NOTE:* When the descriptor in an input is `addr(address)`, it is assumed
|
|
433
471
|
* that any `addr(SH_TYPE_ADDRESS)` is in fact a Segwit `SH_WPKH`
|
|
434
472
|
* (Script Hash-Witness Public Key Hash).
|
|
473
|
+
*, Also any `addr(SINGLE_KEY_ADDRESS)` * is assumed to be a single key Taproot
|
|
474
|
+
* address (like those defined in BIP86).
|
|
435
475
|
* For inputs using arbitrary scripts (not standard addresses),
|
|
436
|
-
* use a descriptor in the format `sh(MINISCRIPT)`.
|
|
476
|
+
* use a descriptor in the format `sh(MINISCRIPT)` or `tr(MINISCRIPT)`.
|
|
477
|
+
* Note however that tr(MINISCRIPT) is not yet supported for non-single-key
|
|
478
|
+
* expressions.
|
|
437
479
|
*/
|
|
438
480
|
inputWeight(isSegwitTx: boolean, signatures: PartialSig[] | 'DANGEROUSLY_USE_FAKE_SIGNATURES'): number;
|
|
439
481
|
/**
|
package/dist/descriptors.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
// Copyright (c)
|
|
2
|
+
// Copyright (c) 2025 Jose-Luis Landabaso - https://bitcoinerlab.com
|
|
3
3
|
// Distributed under the MIT software license
|
|
4
4
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
5
5
|
if (k2 === undefined) k2 = k;
|
|
@@ -148,18 +148,31 @@ function evaluate({ descriptor, checksumRequired, index }) {
|
|
|
148
148
|
* [@bitcoinerlab/secp256k1](https://github.com/bitcoinerlab/secp256k1).
|
|
149
149
|
*/
|
|
150
150
|
function DescriptorsFactory(ecc) {
|
|
151
|
-
var _Output_instances, _Output_payment, _Output_preimages, _Output_signersPubKeys, _Output_miniscript, _Output_witnessScript, _Output_redeemScript, _Output_isSegwit, _Output_expandedExpression, _Output_expandedMiniscript, _Output_expansionMap, _Output_network, _Output_getTimeConstraints, _Output_assertPsbtInput;
|
|
151
|
+
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;
|
|
152
|
+
(0, bitcoinjs_lib_1.initEccLib)(ecc); //Taproot requires initEccLib
|
|
152
153
|
const BIP32 = (0, bip32_1.BIP32Factory)(ecc);
|
|
153
154
|
const ECPair = (0, ecpair_1.ECPairFactory)(ecc);
|
|
154
|
-
const signatureValidator = (pubkey, msghash, signature) =>
|
|
155
|
+
const signatureValidator = (pubkey, msghash, signature) => {
|
|
156
|
+
if (pubkey.length === 32) {
|
|
157
|
+
//x-only
|
|
158
|
+
if (!ecc.verifySchnorr) {
|
|
159
|
+
throw new Error('TinySecp256k1Interface is not initialized properly: verifySchnorr is missing.');
|
|
160
|
+
}
|
|
161
|
+
return ecc.verifySchnorr(msghash, pubkey, signature);
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
return ECPair.fromPublicKey(pubkey).verify(msghash, signature);
|
|
165
|
+
}
|
|
166
|
+
};
|
|
155
167
|
/**
|
|
156
168
|
* Takes a string key expression (xpub, xprv, pubkey or wif) and parses it
|
|
157
169
|
*/
|
|
158
|
-
const parseKeyExpression = ({ keyExpression, isSegwit, network = bitcoinjs_lib_1.networks.bitcoin }) => {
|
|
170
|
+
const parseKeyExpression = ({ keyExpression, isSegwit, isTaproot, network = bitcoinjs_lib_1.networks.bitcoin }) => {
|
|
159
171
|
return (0, keyExpressions_1.parseKeyExpression)({
|
|
160
172
|
keyExpression,
|
|
161
173
|
network,
|
|
162
174
|
...(typeof isSegwit === 'boolean' ? { isSegwit } : {}),
|
|
175
|
+
...(typeof isTaproot === 'boolean' ? { isTaproot } : {}),
|
|
163
176
|
ECPair,
|
|
164
177
|
BIP32
|
|
165
178
|
});
|
|
@@ -179,6 +192,7 @@ function DescriptorsFactory(ecc) {
|
|
|
179
192
|
let miniscript;
|
|
180
193
|
let expansionMap;
|
|
181
194
|
let isSegwit;
|
|
195
|
+
let isTaproot;
|
|
182
196
|
let expandedMiniscript;
|
|
183
197
|
let payment;
|
|
184
198
|
let witnessScript;
|
|
@@ -212,27 +226,32 @@ function DescriptorsFactory(ecc) {
|
|
|
212
226
|
try {
|
|
213
227
|
payment = p2pkh({ output, network });
|
|
214
228
|
isSegwit = false;
|
|
229
|
+
isTaproot = false;
|
|
215
230
|
}
|
|
216
231
|
catch (e) { }
|
|
217
232
|
try {
|
|
218
233
|
payment = p2sh({ output, network });
|
|
219
234
|
// It assumes that an addr(SH_ADDRESS) is always a add(SH_WPKH) address
|
|
220
235
|
isSegwit = true;
|
|
236
|
+
isTaproot = false;
|
|
221
237
|
}
|
|
222
238
|
catch (e) { }
|
|
223
239
|
try {
|
|
224
240
|
payment = p2wpkh({ output, network });
|
|
225
241
|
isSegwit = true;
|
|
242
|
+
isTaproot = false;
|
|
226
243
|
}
|
|
227
244
|
catch (e) { }
|
|
228
245
|
try {
|
|
229
246
|
payment = p2wsh({ output, network });
|
|
230
247
|
isSegwit = true;
|
|
248
|
+
isTaproot = false;
|
|
231
249
|
}
|
|
232
250
|
catch (e) { }
|
|
233
251
|
try {
|
|
234
252
|
payment = p2tr({ output, network });
|
|
235
253
|
isSegwit = true;
|
|
254
|
+
isTaproot = true;
|
|
236
255
|
}
|
|
237
256
|
catch (e) { }
|
|
238
257
|
if (!payment) {
|
|
@@ -242,7 +261,8 @@ function DescriptorsFactory(ecc) {
|
|
|
242
261
|
//pk(KEY)
|
|
243
262
|
else if (canonicalExpression.match(RE.rePkAnchored)) {
|
|
244
263
|
isSegwit = false;
|
|
245
|
-
|
|
264
|
+
isTaproot = false;
|
|
265
|
+
const keyExpression = canonicalExpression.match(RE.reNonSegwitKeyExp)?.[0];
|
|
246
266
|
if (!keyExpression)
|
|
247
267
|
throw new Error(`Error: keyExpression could not me extracted`);
|
|
248
268
|
if (canonicalExpression !== `pk(${keyExpression})`)
|
|
@@ -261,7 +281,8 @@ function DescriptorsFactory(ecc) {
|
|
|
261
281
|
//pkh(KEY) - legacy
|
|
262
282
|
else if (canonicalExpression.match(RE.rePkhAnchored)) {
|
|
263
283
|
isSegwit = false;
|
|
264
|
-
|
|
284
|
+
isTaproot = false;
|
|
285
|
+
const keyExpression = canonicalExpression.match(RE.reNonSegwitKeyExp)?.[0];
|
|
265
286
|
if (!keyExpression)
|
|
266
287
|
throw new Error(`Error: keyExpression could not me extracted`);
|
|
267
288
|
if (canonicalExpression !== `pkh(${keyExpression})`)
|
|
@@ -279,7 +300,8 @@ function DescriptorsFactory(ecc) {
|
|
|
279
300
|
//sh(wpkh(KEY)) - nested segwit
|
|
280
301
|
else if (canonicalExpression.match(RE.reShWpkhAnchored)) {
|
|
281
302
|
isSegwit = true;
|
|
282
|
-
|
|
303
|
+
isTaproot = false;
|
|
304
|
+
const keyExpression = canonicalExpression.match(RE.reSegwitKeyExp)?.[0];
|
|
283
305
|
if (!keyExpression)
|
|
284
306
|
throw new Error(`Error: keyExpression could not me extracted`);
|
|
285
307
|
if (canonicalExpression !== `sh(wpkh(${keyExpression}))`)
|
|
@@ -300,7 +322,8 @@ function DescriptorsFactory(ecc) {
|
|
|
300
322
|
//wpkh(KEY) - native segwit
|
|
301
323
|
else if (canonicalExpression.match(RE.reWpkhAnchored)) {
|
|
302
324
|
isSegwit = true;
|
|
303
|
-
|
|
325
|
+
isTaproot = false;
|
|
326
|
+
const keyExpression = canonicalExpression.match(RE.reSegwitKeyExp)?.[0];
|
|
304
327
|
if (!keyExpression)
|
|
305
328
|
throw new Error(`Error: keyExpression could not me extracted`);
|
|
306
329
|
if (canonicalExpression !== `wpkh(${keyExpression})`)
|
|
@@ -318,6 +341,7 @@ function DescriptorsFactory(ecc) {
|
|
|
318
341
|
//sh(wsh(miniscript))
|
|
319
342
|
else if (canonicalExpression.match(RE.reShWshMiniscriptAnchored)) {
|
|
320
343
|
isSegwit = true;
|
|
344
|
+
isTaproot = false;
|
|
321
345
|
miniscript = canonicalExpression.match(RE.reShWshMiniscriptAnchored)?.[1]; //[1]-> whatever is found sh(wsh(->HERE<-))
|
|
322
346
|
if (!miniscript)
|
|
323
347
|
throw new Error(`Error: could not get miniscript in ${descriptor}`);
|
|
@@ -351,6 +375,7 @@ function DescriptorsFactory(ecc) {
|
|
|
351
375
|
//isSegwit false because we know it's a P2SH of a miniscript and not a
|
|
352
376
|
//P2SH that embeds a witness payment.
|
|
353
377
|
isSegwit = false;
|
|
378
|
+
isTaproot = false;
|
|
354
379
|
miniscript = canonicalExpression.match(RE.reShMiniscriptAnchored)?.[1]; //[1]-> whatever is found sh(->HERE<-)
|
|
355
380
|
if (!miniscript)
|
|
356
381
|
throw new Error(`Error: could not get miniscript in ${descriptor}`);
|
|
@@ -383,6 +408,7 @@ function DescriptorsFactory(ecc) {
|
|
|
383
408
|
//wsh(miniscript)
|
|
384
409
|
else if (canonicalExpression.match(RE.reWshMiniscriptAnchored)) {
|
|
385
410
|
isSegwit = true;
|
|
411
|
+
isTaproot = false;
|
|
386
412
|
miniscript = canonicalExpression.match(RE.reWshMiniscriptAnchored)?.[1]; //[1]-> whatever is found wsh(->HERE<-)
|
|
387
413
|
if (!miniscript)
|
|
388
414
|
throw new Error(`Error: could not get miniscript in ${descriptor}`);
|
|
@@ -405,6 +431,36 @@ function DescriptorsFactory(ecc) {
|
|
|
405
431
|
payment = p2wsh({ redeem: { output: script, network }, network });
|
|
406
432
|
}
|
|
407
433
|
}
|
|
434
|
+
//tr(KEY) - taproot - TODO: tr(KEY,TREE) not yet supported
|
|
435
|
+
else if (canonicalExpression.match(RE.reTrSingleKeyAnchored)) {
|
|
436
|
+
isSegwit = true;
|
|
437
|
+
isTaproot = true;
|
|
438
|
+
const keyExpression = canonicalExpression.match(RE.reTaprootKeyExp)?.[0];
|
|
439
|
+
if (!keyExpression)
|
|
440
|
+
throw new Error(`Error: keyExpression could not me extracted`);
|
|
441
|
+
if (canonicalExpression !== `tr(${keyExpression})`)
|
|
442
|
+
throw new Error(`Error: invalid expression ${expression}`);
|
|
443
|
+
expandedExpression = 'tr(@0)';
|
|
444
|
+
const pKE = parseKeyExpression({
|
|
445
|
+
keyExpression,
|
|
446
|
+
network,
|
|
447
|
+
isSegwit,
|
|
448
|
+
isTaproot
|
|
449
|
+
});
|
|
450
|
+
expansionMap = { '@0': pKE };
|
|
451
|
+
if (!isCanonicalRanged) {
|
|
452
|
+
const pubkey = pKE.pubkey;
|
|
453
|
+
if (!pubkey)
|
|
454
|
+
throw new Error(`Error: could not extract a pubkey from ${expression}`);
|
|
455
|
+
payment = p2tr({ internalPubkey: pubkey, network });
|
|
456
|
+
//console.log('TRACE', {
|
|
457
|
+
// pKE,
|
|
458
|
+
// pubkey: pubkey?.toString('hex'),
|
|
459
|
+
// payment,
|
|
460
|
+
// paymentPubKey: payment.pubkey
|
|
461
|
+
//});
|
|
462
|
+
}
|
|
463
|
+
}
|
|
408
464
|
else {
|
|
409
465
|
throw new Error(`Error: Could not parse descriptor ${descriptor}`);
|
|
410
466
|
}
|
|
@@ -414,6 +470,7 @@ function DescriptorsFactory(ecc) {
|
|
|
414
470
|
...(miniscript !== undefined ? { miniscript } : {}),
|
|
415
471
|
...(expansionMap !== undefined ? { expansionMap } : {}),
|
|
416
472
|
...(isSegwit !== undefined ? { isSegwit } : {}),
|
|
473
|
+
...(isTaproot !== undefined ? { isTaproot } : {}),
|
|
417
474
|
...(expandedMiniscript !== undefined ? { expandedMiniscript } : {}),
|
|
418
475
|
...(redeemScript !== undefined ? { redeemScript } : {}),
|
|
419
476
|
...(witnessScript !== undefined ? { witnessScript } : {}),
|
|
@@ -432,6 +489,7 @@ function DescriptorsFactory(ecc) {
|
|
|
432
489
|
return (0, miniscript_1.expandMiniscript)({
|
|
433
490
|
miniscript,
|
|
434
491
|
isSegwit,
|
|
492
|
+
isTaproot: false,
|
|
435
493
|
network,
|
|
436
494
|
BIP32,
|
|
437
495
|
ECPair
|
|
@@ -459,6 +517,7 @@ function DescriptorsFactory(ecc) {
|
|
|
459
517
|
//isSegwit true if witnesses are needed to the spend coins sent to this descriptor.
|
|
460
518
|
//may be unset because we may get addr(P2SH) which we don't know if they have segwit.
|
|
461
519
|
_Output_isSegwit.set(this, void 0);
|
|
520
|
+
_Output_isTaproot.set(this, void 0);
|
|
462
521
|
_Output_expandedExpression.set(this, void 0);
|
|
463
522
|
_Output_expandedMiniscript.set(this, void 0);
|
|
464
523
|
_Output_expansionMap.set(this, void 0);
|
|
@@ -487,6 +546,8 @@ function DescriptorsFactory(ecc) {
|
|
|
487
546
|
__classPrivateFieldSet(this, _Output_expansionMap, expandedResult.expansionMap, "f");
|
|
488
547
|
if (expandedResult.isSegwit !== undefined)
|
|
489
548
|
__classPrivateFieldSet(this, _Output_isSegwit, expandedResult.isSegwit, "f");
|
|
549
|
+
if (expandedResult.isTaproot !== undefined)
|
|
550
|
+
__classPrivateFieldSet(this, _Output_isTaproot, expandedResult.isTaproot, "f");
|
|
490
551
|
if (expandedResult.expandedMiniscript !== undefined)
|
|
491
552
|
__classPrivateFieldSet(this, _Output_expandedMiniscript, expandedResult.expandedMiniscript, "f");
|
|
492
553
|
if (expandedResult.redeemScript !== undefined)
|
|
@@ -671,8 +732,24 @@ function DescriptorsFactory(ecc) {
|
|
|
671
732
|
return __classPrivateFieldGet(this, _Output_isSegwit, "f");
|
|
672
733
|
}
|
|
673
734
|
/**
|
|
674
|
-
*
|
|
675
|
-
|
|
735
|
+
* Whether this `Output` is Taproot.
|
|
736
|
+
*/
|
|
737
|
+
isTaproot() {
|
|
738
|
+
return __classPrivateFieldGet(this, _Output_isTaproot, "f");
|
|
739
|
+
}
|
|
740
|
+
/**
|
|
741
|
+
* Attempts to determine the type of output script by testing it against
|
|
742
|
+
* various payment types.
|
|
743
|
+
*
|
|
744
|
+
* This method tries to identify if the output is one of the following types:
|
|
745
|
+
* - P2SH (Pay to Script Hash)
|
|
746
|
+
* - P2WSH (Pay to Witness Script Hash)
|
|
747
|
+
* - P2WPKH (Pay to Witness Public Key Hash)
|
|
748
|
+
* - P2PKH (Pay to Public Key Hash)
|
|
749
|
+
* - P2TR (Pay to Taproot)
|
|
750
|
+
*
|
|
751
|
+
* @returns An object { isPKH: boolean; isWPKH: boolean; isSH: boolean; isWSH: boolean; isTR: boolean;}
|
|
752
|
+
* with boolean properties indicating the detected output type
|
|
676
753
|
*/
|
|
677
754
|
guessOutput() {
|
|
678
755
|
function guessSH(output) {
|
|
@@ -684,6 +761,15 @@ function DescriptorsFactory(ecc) {
|
|
|
684
761
|
return false;
|
|
685
762
|
}
|
|
686
763
|
}
|
|
764
|
+
function guessWSH(output) {
|
|
765
|
+
try {
|
|
766
|
+
bitcoinjs_lib_1.payments.p2wsh({ output });
|
|
767
|
+
return true;
|
|
768
|
+
}
|
|
769
|
+
catch (err) {
|
|
770
|
+
return false;
|
|
771
|
+
}
|
|
772
|
+
}
|
|
687
773
|
function guessWPKH(output) {
|
|
688
774
|
try {
|
|
689
775
|
bitcoinjs_lib_1.payments.p2wpkh({ output });
|
|
@@ -702,18 +788,30 @@ function DescriptorsFactory(ecc) {
|
|
|
702
788
|
return false;
|
|
703
789
|
}
|
|
704
790
|
}
|
|
791
|
+
function guessTR(output) {
|
|
792
|
+
try {
|
|
793
|
+
bitcoinjs_lib_1.payments.p2tr({ output });
|
|
794
|
+
return true;
|
|
795
|
+
}
|
|
796
|
+
catch (err) {
|
|
797
|
+
return false;
|
|
798
|
+
}
|
|
799
|
+
}
|
|
705
800
|
const isPKH = guessPKH(this.getScriptPubKey());
|
|
706
801
|
const isWPKH = guessWPKH(this.getScriptPubKey());
|
|
707
802
|
const isSH = guessSH(this.getScriptPubKey());
|
|
708
|
-
|
|
803
|
+
const isWSH = guessWSH(this.getScriptPubKey());
|
|
804
|
+
const isTR = guessTR(this.getScriptPubKey());
|
|
805
|
+
if ([isPKH, isWPKH, isSH, isWSH, isTR].filter(Boolean).length > 1)
|
|
709
806
|
throw new Error('Cannot have multiple output types.');
|
|
710
|
-
return { isPKH, isWPKH, isSH };
|
|
807
|
+
return { isPKH, isWPKH, isSH, isWSH, isTR };
|
|
711
808
|
}
|
|
712
809
|
// References for inputWeight & outputWeight:
|
|
713
810
|
// https://gist.github.com/junderw/b43af3253ea5865ed52cb51c200ac19c
|
|
714
811
|
// https://bitcoinops.org/en/tools/calc-size/
|
|
715
812
|
// Look for byteLength: https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/ts_src/transaction.ts
|
|
716
813
|
// https://github.com/bitcoinjs/coinselect/blob/master/utils.js
|
|
814
|
+
// https://bitcoin.stackexchange.com/questions/111395/what-is-the-weight-of-a-p2tr-input
|
|
717
815
|
/**
|
|
718
816
|
* Computes the Weight Unit contributions of this Output as if it were the
|
|
719
817
|
* input in a tx.
|
|
@@ -721,8 +819,12 @@ function DescriptorsFactory(ecc) {
|
|
|
721
819
|
* *NOTE:* When the descriptor in an input is `addr(address)`, it is assumed
|
|
722
820
|
* that any `addr(SH_TYPE_ADDRESS)` is in fact a Segwit `SH_WPKH`
|
|
723
821
|
* (Script Hash-Witness Public Key Hash).
|
|
822
|
+
*, Also any `addr(SINGLE_KEY_ADDRESS)` * is assumed to be a single key Taproot
|
|
823
|
+
* address (like those defined in BIP86).
|
|
724
824
|
* For inputs using arbitrary scripts (not standard addresses),
|
|
725
|
-
* use a descriptor in the format `sh(MINISCRIPT)`.
|
|
825
|
+
* use a descriptor in the format `sh(MINISCRIPT)` or `tr(MINISCRIPT)`.
|
|
826
|
+
* Note however that tr(MINISCRIPT) is not yet supported for non-single-key
|
|
827
|
+
* expressions.
|
|
726
828
|
*/
|
|
727
829
|
inputWeight(
|
|
728
830
|
/**
|
|
@@ -737,21 +839,23 @@ function DescriptorsFactory(ecc) {
|
|
|
737
839
|
/*
|
|
738
840
|
* Array of `PartialSig`. Each `PartialSig` includes
|
|
739
841
|
* a public key and its corresponding signature. This parameter
|
|
740
|
-
* enables the accurate calculation of signature sizes.
|
|
741
|
-
* Pass 'DANGEROUSLY_USE_FAKE_SIGNATURES' to assume 72 bytes in length
|
|
842
|
+
* enables the accurate calculation of signature sizes for ECDSA.
|
|
843
|
+
* Pass 'DANGEROUSLY_USE_FAKE_SIGNATURES' to assume 72 bytes in length
|
|
844
|
+
* for ECDSA.
|
|
845
|
+
* Schnorr signatures are always 64 bytes.
|
|
742
846
|
* Mainly used for testing.
|
|
743
847
|
*/
|
|
744
848
|
signatures) {
|
|
745
849
|
if (this.isSegwit() && !isSegwitTx)
|
|
746
850
|
throw new Error(`a tx is segwit if at least one input is segwit`);
|
|
747
|
-
const errorMsg = 'Input type not implemented. Currently supported: pkh(KEY), wpkh(KEY), \
|
|
748
|
-
|
|
749
|
-
|
|
851
|
+
const errorMsg = 'Input type not implemented. Currently supported: pkh(KEY), wpkh(KEY), tr(KEY), \
|
|
852
|
+
sh(wpkh(KEY)), sh(wsh(MINISCRIPT)), sh(MINISCRIPT), wsh(MINISCRIPT), \
|
|
853
|
+
addr(PKH_ADDRESS), addr(WPKH_ADDRESS), addr(SH_WPKH_ADDRESS), addr(SINGLE_KEY_ADDRESS).';
|
|
750
854
|
//expand any miniscript-based descriptor. If not miniscript-based, then it's
|
|
751
855
|
//an addr() descriptor. For those, we can only guess their type.
|
|
752
856
|
const expansion = this.expand().expandedExpression;
|
|
753
|
-
const { isPKH, isWPKH, isSH } = this.guessOutput();
|
|
754
|
-
if (!expansion && !isPKH && !isWPKH && !isSH)
|
|
857
|
+
const { isPKH, isWPKH, isSH, isTR } = this.guessOutput();
|
|
858
|
+
if (!expansion && !isPKH && !isWPKH && !isSH && !isTR)
|
|
755
859
|
throw new Error(errorMsg);
|
|
756
860
|
const firstSignature = signatures && typeof signatures[0] === 'object'
|
|
757
861
|
? signatures[0]
|
|
@@ -842,6 +946,17 @@ function DescriptorsFactory(ecc) {
|
|
|
842
946
|
4 * (40 + varSliceSize(payment.input)) +
|
|
843
947
|
//Segwit
|
|
844
948
|
vectorSize(payment.witness));
|
|
949
|
+
//when addr(SINGLE_KEY_ADDRESS) or tr(KEY) (single key):
|
|
950
|
+
//TODO: only single-key taproot outputs currently supported
|
|
951
|
+
}
|
|
952
|
+
else if (isTR && (!expansion || expansion === 'tr(@0)')) {
|
|
953
|
+
if (!isSegwitTx)
|
|
954
|
+
throw new Error('Should be SegwitTx');
|
|
955
|
+
return (
|
|
956
|
+
// Non-segwit: (txid:32) + (vout:4) + (sequence:4) + (script_len:1)
|
|
957
|
+
41 * 4 +
|
|
958
|
+
// Segwit: (push_count:1) + (sig_length(1) + schnorr_sig(64): 65)
|
|
959
|
+
(1 + 65));
|
|
845
960
|
}
|
|
846
961
|
else {
|
|
847
962
|
throw new Error(errorMsg);
|
|
@@ -852,14 +967,14 @@ function DescriptorsFactory(ecc) {
|
|
|
852
967
|
* output in a tx.
|
|
853
968
|
*/
|
|
854
969
|
outputWeight() {
|
|
855
|
-
const errorMsg = 'Output type not implemented. Currently supported: pkh(KEY), wpkh(KEY), \
|
|
856
|
-
|
|
857
|
-
|
|
970
|
+
const errorMsg = 'Output type not implemented. Currently supported: pkh(KEY), wpkh(KEY), tr(ANYTHING), \
|
|
971
|
+
sh(ANYTHING), wsh(ANYTHING), addr(PKH_ADDRESS), addr(WPKH_ADDRESS), \
|
|
972
|
+
addr(SH_WPKH_ADDRESS), addr(TR_ADDRESS)';
|
|
858
973
|
//expand any miniscript-based descriptor. If not miniscript-based, then it's
|
|
859
974
|
//an addr() descriptor. For those, we can only guess their type.
|
|
860
975
|
const expansion = this.expand().expandedExpression;
|
|
861
|
-
const { isPKH, isWPKH, isSH } = this.guessOutput();
|
|
862
|
-
if (!expansion && !isPKH && !isWPKH && !isSH)
|
|
976
|
+
const { isPKH, isWPKH, isSH, isWSH, isTR } = this.guessOutput();
|
|
977
|
+
if (!expansion && !isPKH && !isWPKH && !isSH && !isTR)
|
|
863
978
|
throw new Error(errorMsg);
|
|
864
979
|
if (expansion ? expansion.startsWith('pkh(') : isPKH) {
|
|
865
980
|
// (p2pkh:26) + (amount:8)
|
|
@@ -873,10 +988,14 @@ function DescriptorsFactory(ecc) {
|
|
|
873
988
|
// (p2sh:24) + (amount:8)
|
|
874
989
|
return 32 * 4;
|
|
875
990
|
}
|
|
876
|
-
else if (expansion
|
|
991
|
+
else if (expansion ? expansion.startsWith('wsh(') : isWSH) {
|
|
877
992
|
// (p2wsh:35) + (amount:8)
|
|
878
993
|
return 43 * 4;
|
|
879
994
|
}
|
|
995
|
+
else if (expansion ? expansion.startsWith('tr(') : isTR) {
|
|
996
|
+
// (script_pubKey_length:1) + (p2t2(OP_1 OP_PUSH32 <schnorr_public_key>):34) + (amount:8)
|
|
997
|
+
return 43 * 4;
|
|
998
|
+
}
|
|
880
999
|
else {
|
|
881
1000
|
throw new Error(errorMsg);
|
|
882
1001
|
}
|
|
@@ -930,12 +1049,20 @@ function DescriptorsFactory(ecc) {
|
|
|
930
1049
|
//This should only happen when using addr() expressions
|
|
931
1050
|
throw new Error(`Error: could not determine whether this is a segwit descriptor`);
|
|
932
1051
|
}
|
|
1052
|
+
const isTaproot = this.isTaproot();
|
|
1053
|
+
if (isTaproot === undefined) {
|
|
1054
|
+
//This should only happen when using addr() expressions
|
|
1055
|
+
throw new Error(`Error: could not determine whether this is a taproot descriptor`);
|
|
1056
|
+
}
|
|
933
1057
|
const index = (0, psbt_1.updatePsbt)({
|
|
934
1058
|
psbt,
|
|
935
1059
|
vout,
|
|
936
1060
|
...(txHex !== undefined ? { txHex } : {}),
|
|
937
1061
|
...(txId !== undefined ? { txId } : {}),
|
|
938
1062
|
...(value !== undefined ? { value } : {}),
|
|
1063
|
+
...(isTaproot
|
|
1064
|
+
? { tapInternalKey: this.getPayment().internalPubkey }
|
|
1065
|
+
: {}),
|
|
939
1066
|
sequence: this.getSequence(),
|
|
940
1067
|
locktime: this.getLockTime(),
|
|
941
1068
|
keysInfo: __classPrivateFieldGet(this, _Output_expansionMap, "f") ? Object.values(__classPrivateFieldGet(this, _Output_expansionMap, "f")) : [],
|
|
@@ -1000,15 +1127,15 @@ function DescriptorsFactory(ecc) {
|
|
|
1000
1127
|
//same sequences, ... The descriptor does not store the hash of the previous
|
|
1001
1128
|
//transaction since it is a general Descriptor object. Indices must be kept
|
|
1002
1129
|
//out of the scope of this class and then passed.
|
|
1003
|
-
const signatures = psbt.data.inputs[index]?.partialSig;
|
|
1004
|
-
if (!signatures)
|
|
1005
|
-
throw new Error(`Error: cannot finalize without signatures`);
|
|
1006
1130
|
__classPrivateFieldGet(this, _Output_instances, "m", _Output_assertPsbtInput).call(this, { index, psbt });
|
|
1007
1131
|
if (!__classPrivateFieldGet(this, _Output_miniscript, "f")) {
|
|
1008
1132
|
//Use standard finalizers
|
|
1009
1133
|
psbt.finalizeInput(index);
|
|
1010
1134
|
}
|
|
1011
1135
|
else {
|
|
1136
|
+
const signatures = psbt.data.inputs[index]?.partialSig;
|
|
1137
|
+
if (!signatures)
|
|
1138
|
+
throw new Error(`Error: cannot finalize without signatures`);
|
|
1012
1139
|
const scriptSatisfaction = this.getScriptSatisfaction(signatures);
|
|
1013
1140
|
psbt.finalizeInput(index, (0, psbt_1.finalScriptsFuncFactory)(scriptSatisfaction, __classPrivateFieldGet(this, _Output_network, "f")));
|
|
1014
1141
|
}
|
|
@@ -1034,7 +1161,7 @@ function DescriptorsFactory(ecc) {
|
|
|
1034
1161
|
};
|
|
1035
1162
|
}
|
|
1036
1163
|
}
|
|
1037
|
-
_Output_payment = new WeakMap(), _Output_preimages = new WeakMap(), _Output_signersPubKeys = new WeakMap(), _Output_miniscript = new WeakMap(), _Output_witnessScript = new WeakMap(), _Output_redeemScript = new WeakMap(), _Output_isSegwit = new WeakMap(), _Output_expandedExpression = new WeakMap(), _Output_expandedMiniscript = new WeakMap(), _Output_expansionMap = new WeakMap(), _Output_network = new WeakMap(), _Output_instances = new WeakSet(), _Output_getTimeConstraints = function _Output_getTimeConstraints() {
|
|
1164
|
+
_Output_payment = new WeakMap(), _Output_preimages = new WeakMap(), _Output_signersPubKeys = new WeakMap(), _Output_miniscript = new WeakMap(), _Output_witnessScript = new WeakMap(), _Output_redeemScript = new WeakMap(), _Output_isSegwit = new WeakMap(), _Output_isTaproot = new WeakMap(), _Output_expandedExpression = new WeakMap(), _Output_expandedMiniscript = new WeakMap(), _Output_expansionMap = new WeakMap(), _Output_network = new WeakMap(), _Output_instances = new WeakSet(), _Output_getTimeConstraints = function _Output_getTimeConstraints() {
|
|
1038
1165
|
const miniscript = __classPrivateFieldGet(this, _Output_miniscript, "f");
|
|
1039
1166
|
const preimages = __classPrivateFieldGet(this, _Output_preimages, "f");
|
|
1040
1167
|
const expandedMiniscript = __classPrivateFieldGet(this, _Output_expandedMiniscript, "f");
|
package/dist/keyExpressions.d.ts
CHANGED
|
@@ -19,7 +19,7 @@ import { LedgerState, LedgerManager } from './ledger';
|
|
|
19
19
|
* }
|
|
20
20
|
* ```
|
|
21
21
|
*/
|
|
22
|
-
export declare function parseKeyExpression({ keyExpression, isSegwit, ECPair, BIP32, network }: {
|
|
22
|
+
export declare function parseKeyExpression({ keyExpression, isSegwit, isTaproot, ECPair, BIP32, network }: {
|
|
23
23
|
keyExpression: string;
|
|
24
24
|
/** @default networks.bitcoin */
|
|
25
25
|
network?: Network;
|
|
@@ -29,6 +29,13 @@ export declare function parseKeyExpression({ keyExpression, isSegwit, ECPair, BI
|
|
|
29
29
|
* expression) is compressed (33 bytes).
|
|
30
30
|
*/
|
|
31
31
|
isSegwit?: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Indicates if this key expression belongs to a Taproot output. For Taproot,
|
|
34
|
+
* the key must be represented as an x-only public key (32 bytes).
|
|
35
|
+
* If a 33-byte compressed pubkey is derived, it is converted to its x-only
|
|
36
|
+
* representation.
|
|
37
|
+
*/
|
|
38
|
+
isTaproot?: boolean;
|
|
32
39
|
ECPair: ECPairAPI;
|
|
33
40
|
BIP32: BIP32API;
|
|
34
41
|
}): KeyInfo;
|
package/dist/keyExpressions.js
CHANGED
|
@@ -59,7 +59,7 @@ const derivePath = (node, path) => {
|
|
|
59
59
|
* }
|
|
60
60
|
* ```
|
|
61
61
|
*/
|
|
62
|
-
function parseKeyExpression({ keyExpression, isSegwit, ECPair, BIP32, network = bitcoinjs_lib_1.networks.bitcoin }) {
|
|
62
|
+
function parseKeyExpression({ keyExpression, isSegwit, isTaproot, ECPair, BIP32, network = bitcoinjs_lib_1.networks.bitcoin }) {
|
|
63
63
|
let pubkey; //won't be computed for ranged keyExpressions
|
|
64
64
|
let ecpair;
|
|
65
65
|
let bip32;
|
|
@@ -68,8 +68,18 @@ function parseKeyExpression({ keyExpression, isSegwit, ECPair, BIP32, network =
|
|
|
68
68
|
let keyPath;
|
|
69
69
|
let path;
|
|
70
70
|
const isRanged = keyExpression.indexOf('*') !== -1;
|
|
71
|
+
const reKeyExp = isTaproot
|
|
72
|
+
? RE.reTaprootKeyExp
|
|
73
|
+
: isSegwit
|
|
74
|
+
? RE.reSegwitKeyExp
|
|
75
|
+
: RE.reNonSegwitKeyExp;
|
|
76
|
+
const rePubKey = isTaproot
|
|
77
|
+
? RE.reTaprootPubKey
|
|
78
|
+
: isSegwit
|
|
79
|
+
? RE.reSegwitPubKey
|
|
80
|
+
: RE.reNonSegwitPubKey;
|
|
71
81
|
//Validate the keyExpression:
|
|
72
|
-
const keyExpressions = keyExpression.match(
|
|
82
|
+
const keyExpressions = keyExpression.match(reKeyExp);
|
|
73
83
|
if (keyExpressions === null || keyExpressions[0] !== keyExpression) {
|
|
74
84
|
throw new Error(`Error: expected a keyExpression but got ${keyExpression}`);
|
|
75
85
|
}
|
|
@@ -93,8 +103,11 @@ function parseKeyExpression({ keyExpression, isSegwit, ECPair, BIP32, network =
|
|
|
93
103
|
const actualKey = keyExpression.replace(reOriginAnchoredStart, '');
|
|
94
104
|
let mPubKey, mWIF, mXpubKey, mXprvKey;
|
|
95
105
|
//match pubkey:
|
|
96
|
-
if ((mPubKey = actualKey.match(RE.anchorStartAndEnd(
|
|
106
|
+
if ((mPubKey = actualKey.match(RE.anchorStartAndEnd(rePubKey))) !== null) {
|
|
97
107
|
pubkey = Buffer.from(mPubKey[0], 'hex');
|
|
108
|
+
if (isTaproot && pubkey.length === 32)
|
|
109
|
+
//convert the xonly point to a compressed point assuming even parity
|
|
110
|
+
pubkey = Buffer.concat([Buffer.from([0x02]), pubkey]);
|
|
98
111
|
ecpair = ECPair.fromPublicKey(pubkey, { network });
|
|
99
112
|
//Validate the pubkey (compressed or uncompressed)
|
|
100
113
|
if (!ECPair.isPoint(pubkey) ||
|
|
@@ -161,6 +174,9 @@ function parseKeyExpression({ keyExpression, isSegwit, ECPair, BIP32, network =
|
|
|
161
174
|
if (originPath || keyPath) {
|
|
162
175
|
path = `m${originPath ?? ''}${keyPath ?? ''}`;
|
|
163
176
|
}
|
|
177
|
+
if (pubkey !== undefined && isTaproot && pubkey.length === 33)
|
|
178
|
+
// If we get a 33-byte compressed key, drop the first byte.
|
|
179
|
+
pubkey = pubkey.slice(1, 33);
|
|
164
180
|
return {
|
|
165
181
|
keyExpression,
|
|
166
182
|
...(pubkey !== undefined ? { pubkey } : {}),
|
package/dist/miniscript.d.ts
CHANGED
|
@@ -11,9 +11,10 @@ import type { Preimage, TimeConstraints, ExpansionMap } from './types';
|
|
|
11
11
|
* satisfied with satisfier.
|
|
12
12
|
* Also compute pubkeys from descriptors to use them later.
|
|
13
13
|
*/
|
|
14
|
-
export declare function expandMiniscript({ miniscript, isSegwit, network, ECPair, BIP32 }: {
|
|
14
|
+
export declare function expandMiniscript({ miniscript, isSegwit, isTaproot, network, ECPair, BIP32 }: {
|
|
15
15
|
miniscript: string;
|
|
16
16
|
isSegwit: boolean;
|
|
17
|
+
isTaproot: boolean;
|
|
17
18
|
network?: Network;
|
|
18
19
|
ECPair: ECPairAPI;
|
|
19
20
|
BIP32: BIP32API;
|
package/dist/miniscript.js
CHANGED
|
@@ -37,9 +37,16 @@ const miniscript_1 = require("@bitcoinerlab/miniscript");
|
|
|
37
37
|
* satisfied with satisfier.
|
|
38
38
|
* Also compute pubkeys from descriptors to use them later.
|
|
39
39
|
*/
|
|
40
|
-
function expandMiniscript({ miniscript, isSegwit, network = bitcoinjs_lib_1.networks.bitcoin, ECPair, BIP32 }) {
|
|
40
|
+
function expandMiniscript({ miniscript, isSegwit, isTaproot, network = bitcoinjs_lib_1.networks.bitcoin, ECPair, BIP32 }) {
|
|
41
|
+
if (isTaproot)
|
|
42
|
+
throw new Error('Taproot miniscript not yet supported.');
|
|
43
|
+
const reKeyExp = isTaproot
|
|
44
|
+
? RE.reTaprootKeyExp
|
|
45
|
+
: isSegwit
|
|
46
|
+
? RE.reSegwitKeyExp
|
|
47
|
+
: RE.reNonSegwitKeyExp;
|
|
41
48
|
const expansionMap = {};
|
|
42
|
-
const expandedMiniscript = miniscript.replace(RegExp(
|
|
49
|
+
const expandedMiniscript = miniscript.replace(RegExp(reKeyExp, 'g'), (keyExpression) => {
|
|
43
50
|
const key = '@' + Object.keys(expansionMap).length;
|
|
44
51
|
expansionMap[key] = (0, keyExpressions_1.parseKeyExpression)({
|
|
45
52
|
keyExpression,
|
package/dist/psbt.d.ts
CHANGED
|
@@ -21,7 +21,7 @@ export declare function finalScriptsFuncFactory(scriptSatisfaction: Buffer, netw
|
|
|
21
21
|
/**
|
|
22
22
|
* Important: Read comments on descriptor.updatePsbt regarding not passing txHex
|
|
23
23
|
*/
|
|
24
|
-
export declare function updatePsbt({ psbt, vout, txHex, txId, value, sequence, locktime, keysInfo, scriptPubKey, isSegwit, witnessScript, redeemScript, rbf }: {
|
|
24
|
+
export declare function updatePsbt({ psbt, vout, txHex, txId, value, sequence, locktime, keysInfo, scriptPubKey, isSegwit, tapInternalKey, witnessScript, redeemScript, rbf }: {
|
|
25
25
|
psbt: Psbt;
|
|
26
26
|
vout: number;
|
|
27
27
|
txHex?: string;
|
|
@@ -32,6 +32,8 @@ export declare function updatePsbt({ psbt, vout, txHex, txId, value, sequence, l
|
|
|
32
32
|
keysInfo: KeyInfo[];
|
|
33
33
|
scriptPubKey: Buffer;
|
|
34
34
|
isSegwit: boolean;
|
|
35
|
+
/** for taproot **/
|
|
36
|
+
tapInternalKey?: Buffer | undefined;
|
|
35
37
|
witnessScript: Buffer | undefined;
|
|
36
38
|
redeemScript: Buffer | undefined;
|
|
37
39
|
rbf: boolean;
|
package/dist/psbt.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
// Copyright (c)
|
|
2
|
+
// Copyright (c) 2025 Jose-Luis Landabaso - https://bitcoinerlab.com
|
|
3
3
|
// Distributed under the MIT software license
|
|
4
4
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
5
|
exports.updatePsbt = exports.finalScriptsFuncFactory = void 0;
|
|
@@ -86,7 +86,7 @@ exports.finalScriptsFuncFactory = finalScriptsFuncFactory;
|
|
|
86
86
|
/**
|
|
87
87
|
* Important: Read comments on descriptor.updatePsbt regarding not passing txHex
|
|
88
88
|
*/
|
|
89
|
-
function updatePsbt({ psbt, vout, txHex, txId, value, sequence, locktime, keysInfo, scriptPubKey, isSegwit, witnessScript, redeemScript, rbf }) {
|
|
89
|
+
function updatePsbt({ psbt, vout, txHex, txId, value, sequence, locktime, keysInfo, scriptPubKey, isSegwit, tapInternalKey, witnessScript, redeemScript, rbf }) {
|
|
90
90
|
//Some data-sanity checks:
|
|
91
91
|
if (sequence !== undefined && rbf && sequence > 0xfffffffd)
|
|
92
92
|
throw new Error(`Error: incompatible sequence and rbf settings`);
|
|
@@ -155,20 +155,45 @@ function updatePsbt({ psbt, vout, txHex, txId, value, sequence, locktime, keysIn
|
|
|
155
155
|
if (txHex !== undefined) {
|
|
156
156
|
input.nonWitnessUtxo = bitcoinjs_lib_1.Transaction.fromHex(txHex).toBuffer();
|
|
157
157
|
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
158
|
+
if (tapInternalKey) {
|
|
159
|
+
//Taproot
|
|
160
|
+
const tapBip32Derivation = keysInfo
|
|
161
|
+
.filter((keyInfo) => keyInfo.pubkey && keyInfo.masterFingerprint && keyInfo.path)
|
|
162
|
+
.map((keyInfo) => {
|
|
163
|
+
const pubkey = keyInfo.pubkey;
|
|
164
|
+
if (!pubkey)
|
|
165
|
+
throw new Error(`key ${keyInfo.keyExpression} missing pubkey`);
|
|
166
|
+
return {
|
|
167
|
+
masterFingerprint: keyInfo.masterFingerprint,
|
|
168
|
+
pubkey,
|
|
169
|
+
path: keyInfo.path,
|
|
170
|
+
leafHashes: [] // TODO: Empty array for tr(KEY) taproot key spend - this is the only type currently supported
|
|
171
|
+
};
|
|
172
|
+
});
|
|
173
|
+
if (tapBip32Derivation.length)
|
|
174
|
+
input.tapBip32Derivation = tapBip32Derivation;
|
|
175
|
+
input.tapInternalKey = tapInternalKey;
|
|
176
|
+
//TODO: currently only single-key taproot supported.
|
|
177
|
+
//https://github.com/bitcoinjs/bitcoinjs-lib/blob/6ba8bb3ce20ba533eeaba6939cfc2891576d9969/test/integration/taproot.spec.ts#L243
|
|
178
|
+
if (tapBip32Derivation.length > 1)
|
|
179
|
+
throw new Error('Only single key taproot inputs are currently supported');
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
const bip32Derivation = keysInfo
|
|
183
|
+
.filter((keyInfo) => keyInfo.pubkey && keyInfo.masterFingerprint && keyInfo.path)
|
|
184
|
+
.map((keyInfo) => {
|
|
185
|
+
const pubkey = keyInfo.pubkey;
|
|
186
|
+
if (!pubkey)
|
|
187
|
+
throw new Error(`key ${keyInfo.keyExpression} missing pubkey`);
|
|
188
|
+
return {
|
|
189
|
+
masterFingerprint: keyInfo.masterFingerprint,
|
|
190
|
+
pubkey,
|
|
191
|
+
path: keyInfo.path
|
|
192
|
+
};
|
|
193
|
+
});
|
|
194
|
+
if (bip32Derivation.length)
|
|
195
|
+
input.bip32Derivation = bip32Derivation;
|
|
196
|
+
}
|
|
172
197
|
if (isSegwit && txHex !== undefined) {
|
|
173
198
|
//There's no need to put both witnessUtxo and nonWitnessUtxo
|
|
174
199
|
input.witnessUtxo = { script: scriptPubKey, value };
|
package/dist/re.d.ts
CHANGED
|
@@ -2,20 +2,25 @@ export declare const reOriginPath: string;
|
|
|
2
2
|
export declare const reMasterFingerprint: string;
|
|
3
3
|
export declare const reOrigin: string;
|
|
4
4
|
export declare const reChecksum: string;
|
|
5
|
-
export declare const
|
|
5
|
+
export declare const reNonSegwitPubKey: string;
|
|
6
|
+
export declare const reSegwitPubKey: string;
|
|
7
|
+
export declare const reTaprootPubKey: string;
|
|
6
8
|
export declare const reWIF: string;
|
|
7
9
|
export declare const reXpub: string;
|
|
8
10
|
export declare const reXprv: string;
|
|
9
11
|
export declare const rePath: string;
|
|
10
12
|
export declare const reXpubKey: string;
|
|
11
13
|
export declare const reXprvKey: string;
|
|
12
|
-
export declare const
|
|
14
|
+
export declare const reNonSegwitKeyExp: string;
|
|
15
|
+
export declare const reSegwitKeyExp: string;
|
|
16
|
+
export declare const reTaprootKeyExp: string;
|
|
13
17
|
export declare const anchorStartAndEnd: (re: string) => string;
|
|
14
18
|
export declare const rePkAnchored: string;
|
|
15
19
|
export declare const reAddrAnchored: string;
|
|
16
20
|
export declare const rePkhAnchored: string;
|
|
17
21
|
export declare const reWpkhAnchored: string;
|
|
18
22
|
export declare const reShWpkhAnchored: string;
|
|
23
|
+
export declare const reTrSingleKeyAnchored: string;
|
|
19
24
|
export declare const reShMiniscriptAnchored: string;
|
|
20
25
|
export declare const reShWshMiniscriptAnchored: string;
|
|
21
26
|
export declare const reWshMiniscriptAnchored: string;
|
package/dist/re.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
// Copyright (c)
|
|
2
|
+
// Copyright (c) 2025 Jose-Luis Landabaso - https://bitcoinerlab.com
|
|
3
3
|
// Distributed under the MIT software license
|
|
4
4
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
-
exports.reWshMiniscriptAnchored = exports.reShWshMiniscriptAnchored = exports.reShMiniscriptAnchored = exports.reShWpkhAnchored = exports.reWpkhAnchored = exports.rePkhAnchored = exports.reAddrAnchored = exports.rePkAnchored = exports.anchorStartAndEnd = exports.
|
|
5
|
+
exports.reWshMiniscriptAnchored = exports.reShWshMiniscriptAnchored = exports.reShMiniscriptAnchored = exports.reTrSingleKeyAnchored = exports.reShWpkhAnchored = exports.reWpkhAnchored = exports.rePkhAnchored = exports.reAddrAnchored = exports.rePkAnchored = exports.anchorStartAndEnd = exports.reTaprootKeyExp = exports.reSegwitKeyExp = exports.reNonSegwitKeyExp = exports.reXprvKey = exports.reXpubKey = exports.rePath = exports.reXprv = exports.reXpub = exports.reWIF = exports.reTaprootPubKey = exports.reSegwitPubKey = exports.reNonSegwitPubKey = exports.reChecksum = exports.reOrigin = exports.reMasterFingerprint = exports.reOriginPath = void 0;
|
|
6
6
|
const checksum_1 = require("./checksum");
|
|
7
7
|
//Regular expressions cheat sheet:
|
|
8
8
|
//https://www.keycdn.com/support/regex-cheat-sheet
|
|
@@ -22,7 +22,10 @@ exports.reChecksum = String.raw `(#[${checksum_1.CHECKSUM_CHARSET}]{8})`;
|
|
|
22
22
|
//as explained here: github.com/bitcoin/bitcoin/blob/master/doc/descriptors.md#reference
|
|
23
23
|
const reCompressedPubKey = String.raw `((02|03)[0-9a-fA-F]{64})`;
|
|
24
24
|
const reUncompressedPubKey = String.raw `(04[0-9a-fA-F]{128})`;
|
|
25
|
-
|
|
25
|
+
const reXOnlyPubKey = String.raw `([0-9a-fA-F]{64})`;
|
|
26
|
+
exports.reNonSegwitPubKey = String.raw `(${reCompressedPubKey}|${reUncompressedPubKey})`;
|
|
27
|
+
exports.reSegwitPubKey = String.raw `(${reCompressedPubKey})`;
|
|
28
|
+
exports.reTaprootPubKey = String.raw `(${reCompressedPubKey}|${reXOnlyPubKey})`;
|
|
26
29
|
//https://learnmeabitcoin.com/technical/wif
|
|
27
30
|
//5, K, L for mainnet, 5: uncompressed, {K, L}: compressed
|
|
28
31
|
//c, 9, testnet, c: compressed, 9: uncompressed
|
|
@@ -38,15 +41,20 @@ exports.rePath = String.raw `(\/(${rePathComponent})*(${reRangeLevel}|${reLevel}
|
|
|
38
41
|
exports.reXpubKey = String.raw `(${exports.reXpub})(${exports.rePath})?`;
|
|
39
42
|
exports.reXprvKey = String.raw `(${exports.reXprv})(${exports.rePath})?`;
|
|
40
43
|
//actualKey is the keyExpression without optional origin
|
|
41
|
-
const
|
|
44
|
+
const reNonSegwitActualKey = String.raw `(${exports.reXpubKey}|${exports.reXprvKey}|${exports.reNonSegwitPubKey}|${exports.reWIF})`;
|
|
45
|
+
const reSegwitActualKey = String.raw `(${exports.reXpubKey}|${exports.reXprvKey}|${exports.reSegwitPubKey}|${exports.reWIF})`;
|
|
46
|
+
const reTaprootActualKey = String.raw `(${exports.reXpubKey}|${exports.reXprvKey}|${exports.reTaprootPubKey}|${exports.reWIF})`;
|
|
42
47
|
//reOrigin is optional: Optionally, key origin information, consisting of:
|
|
43
48
|
//Matches a key expression: wif, xpub, xprv or pubkey:
|
|
44
|
-
exports.
|
|
49
|
+
exports.reNonSegwitKeyExp = String.raw `(${exports.reOrigin})?(${reNonSegwitActualKey})`;
|
|
50
|
+
exports.reSegwitKeyExp = String.raw `(${exports.reOrigin})?(${reSegwitActualKey})`;
|
|
51
|
+
exports.reTaprootKeyExp = String.raw `(${exports.reOrigin})?(${reTaprootActualKey})`;
|
|
45
52
|
const rePk = String.raw `pk\((.*?)\)`; //Matches anything. We assert later in the code that the pubkey is valid.
|
|
46
53
|
const reAddr = String.raw `addr\((.*?)\)`; //Matches anything. We assert later in the code that the address is valid.
|
|
47
|
-
const rePkh = String.raw `pkh\(${exports.
|
|
48
|
-
const reWpkh = String.raw `wpkh\(${exports.
|
|
49
|
-
const reShWpkh = String.raw `sh\(wpkh\(${exports.
|
|
54
|
+
const rePkh = String.raw `pkh\(${exports.reNonSegwitKeyExp}\)`;
|
|
55
|
+
const reWpkh = String.raw `wpkh\(${exports.reSegwitKeyExp}\)`;
|
|
56
|
+
const reShWpkh = String.raw `sh\(wpkh\(${exports.reSegwitKeyExp}\)\)`;
|
|
57
|
+
const reTrSingleKey = String.raw `tr\(${exports.reTaprootKeyExp}\)`; // TODO: tr(KEY,TREE) not yet supported. TrSingleKey used for tr(KEY)
|
|
50
58
|
const reMiniscript = String.raw `(.*?)`; //Matches anything. We assert later in the code that miniscripts are valid and sane.
|
|
51
59
|
//RegExp makers:
|
|
52
60
|
const makeReSh = (re) => String.raw `sh\(${re}\)`;
|
|
@@ -60,6 +68,7 @@ exports.reAddrAnchored = (0, exports.anchorStartAndEnd)(composeChecksum(reAddr))
|
|
|
60
68
|
exports.rePkhAnchored = (0, exports.anchorStartAndEnd)(composeChecksum(rePkh));
|
|
61
69
|
exports.reWpkhAnchored = (0, exports.anchorStartAndEnd)(composeChecksum(reWpkh));
|
|
62
70
|
exports.reShWpkhAnchored = (0, exports.anchorStartAndEnd)(composeChecksum(reShWpkh));
|
|
71
|
+
exports.reTrSingleKeyAnchored = (0, exports.anchorStartAndEnd)(composeChecksum(reTrSingleKey));
|
|
63
72
|
exports.reShMiniscriptAnchored = (0, exports.anchorStartAndEnd)(composeChecksum(makeReSh(reMiniscript)));
|
|
64
73
|
exports.reShWshMiniscriptAnchored = (0, exports.anchorStartAndEnd)(composeChecksum(makeReShWsh(reMiniscript)));
|
|
65
74
|
exports.reWshMiniscriptAnchored = (0, exports.anchorStartAndEnd)(composeChecksum(makeReWsh(reMiniscript)));
|
|
@@ -43,6 +43,20 @@ export declare const wpkhBIP32: ({ masterNode, network, keyPath, account, change
|
|
|
43
43
|
*/
|
|
44
44
|
isPublic?: boolean;
|
|
45
45
|
}) => string;
|
|
46
|
+
export declare const trBIP32: ({ masterNode, network, keyPath, account, change, index, isPublic }: {
|
|
47
|
+
masterNode: BIP32Interface;
|
|
48
|
+
/** @default networks.bitcoin */
|
|
49
|
+
network?: Network;
|
|
50
|
+
account: number;
|
|
51
|
+
change?: number | undefined;
|
|
52
|
+
index?: number | undefined | '*';
|
|
53
|
+
keyPath?: string;
|
|
54
|
+
/**
|
|
55
|
+
* Compute an xpub or xprv
|
|
56
|
+
* @default true
|
|
57
|
+
*/
|
|
58
|
+
isPublic?: boolean;
|
|
59
|
+
}) => string;
|
|
46
60
|
export declare const pkhLedger: {
|
|
47
61
|
({ ledgerManager, account, keyPath, change, index }: {
|
|
48
62
|
ledgerManager: LedgerManager;
|
|
@@ -100,3 +114,22 @@ export declare const wpkhLedger: {
|
|
|
100
114
|
index?: number | undefined | '*';
|
|
101
115
|
}): Promise<string>;
|
|
102
116
|
};
|
|
117
|
+
export declare const trLedger: {
|
|
118
|
+
({ ledgerManager, account, keyPath, change, index }: {
|
|
119
|
+
ledgerManager: LedgerManager;
|
|
120
|
+
account: number;
|
|
121
|
+
keyPath?: string;
|
|
122
|
+
change?: number | undefined;
|
|
123
|
+
index?: number | undefined | '*';
|
|
124
|
+
}): Promise<string>;
|
|
125
|
+
({ ledgerClient, ledgerState, network, account, keyPath, change, index }: {
|
|
126
|
+
ledgerClient: unknown;
|
|
127
|
+
ledgerState: LedgerState;
|
|
128
|
+
/** @default networks.bitcoin */
|
|
129
|
+
network?: Network;
|
|
130
|
+
account: number;
|
|
131
|
+
keyPath?: string;
|
|
132
|
+
change?: number | undefined;
|
|
133
|
+
index?: number | undefined | '*';
|
|
134
|
+
}): Promise<string>;
|
|
135
|
+
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.wpkhLedger = exports.shWpkhLedger = exports.pkhLedger = exports.wpkhBIP32 = exports.shWpkhBIP32 = exports.pkhBIP32 = void 0;
|
|
3
|
+
exports.trLedger = exports.wpkhLedger = exports.shWpkhLedger = exports.pkhLedger = exports.trBIP32 = exports.wpkhBIP32 = exports.shWpkhBIP32 = exports.pkhBIP32 = void 0;
|
|
4
4
|
const bitcoinjs_lib_1 = require("bitcoinjs-lib");
|
|
5
5
|
const keyExpressions_1 = require("./keyExpressions");
|
|
6
6
|
function assertStandardKeyPath(keyPath) {
|
|
@@ -43,6 +43,7 @@ function standardExpressionsBIP32Maker(purpose, scriptTemplate) {
|
|
|
43
43
|
exports.pkhBIP32 = standardExpressionsBIP32Maker(44, 'pkh(KEYEXPRESSION)');
|
|
44
44
|
exports.shWpkhBIP32 = standardExpressionsBIP32Maker(49, 'sh(wpkh(KEYEXPRESSION))');
|
|
45
45
|
exports.wpkhBIP32 = standardExpressionsBIP32Maker(84, 'wpkh(KEYEXPRESSION)');
|
|
46
|
+
exports.trBIP32 = standardExpressionsBIP32Maker(86, 'tr(KEYEXPRESSION)');
|
|
46
47
|
function standardExpressionsLedgerMaker(purpose, scriptTemplate) {
|
|
47
48
|
/** @hidden */
|
|
48
49
|
async function standardScriptExpressionLedger({ ledgerClient, ledgerState, ledgerManager, network, account, keyPath, change, index }) {
|
|
@@ -74,3 +75,4 @@ function standardExpressionsLedgerMaker(purpose, scriptTemplate) {
|
|
|
74
75
|
exports.pkhLedger = standardExpressionsLedgerMaker(44, 'pkh(KEYEXPRESSION)');
|
|
75
76
|
exports.shWpkhLedger = standardExpressionsLedgerMaker(49, 'sh(wpkh(KEYEXPRESSION))');
|
|
76
77
|
exports.wpkhLedger = standardExpressionsLedgerMaker(84, 'wpkh(KEYEXPRESSION)');
|
|
78
|
+
exports.trLedger = standardExpressionsLedgerMaker(86, 'tr(KEYEXPRESSION)');
|
package/dist/signers.d.ts
CHANGED
|
@@ -3,11 +3,48 @@ import type { ECPairInterface } from 'ecpair';
|
|
|
3
3
|
import type { BIP32Interface } from 'bip32';
|
|
4
4
|
import type { DescriptorInstance } from './descriptors';
|
|
5
5
|
import { LedgerState, LedgerManager } from './ledger';
|
|
6
|
+
/**
|
|
7
|
+
* Signs a specific input of a PSBT with an ECPair.
|
|
8
|
+
*
|
|
9
|
+
* Unlike bitcoinjs-lib's native `psbt.signInput()`, this function automatically detects
|
|
10
|
+
* if the input is a Taproot input and internally tweaks the key if needed.
|
|
11
|
+
*
|
|
12
|
+
* This behavior matches how `signInputBIP32` works, where the BIP32 node is automatically
|
|
13
|
+
* tweaked for Taproot inputs. In contrast, bitcoinjs-lib's native implementation requires
|
|
14
|
+
* manual pre-tweaking of ECPair signers for Taproot inputs.
|
|
15
|
+
*
|
|
16
|
+
* @see https://github.com/bitcoinjs/bitcoinjs-lib/pull/2137#issuecomment-2713264848
|
|
17
|
+
*
|
|
18
|
+
* @param {Object} params - The parameters object
|
|
19
|
+
* @param {Psbt} params.psbt - The PSBT to sign
|
|
20
|
+
* @param {number} params.index - The input index to sign
|
|
21
|
+
* @param {ECPairInterface} params.ecpair - The ECPair to sign with
|
|
22
|
+
*/
|
|
6
23
|
export declare function signInputECPair({ psbt, index, ecpair }: {
|
|
7
24
|
psbt: Psbt;
|
|
8
25
|
index: number;
|
|
9
26
|
ecpair: ECPairInterface;
|
|
10
27
|
}): void;
|
|
28
|
+
/**
|
|
29
|
+
* Signs all inputs of a PSBT with an ECPair.
|
|
30
|
+
*
|
|
31
|
+
* This function improves upon bitcoinjs-lib's native `psbt.signAllInputs()` by automatically
|
|
32
|
+
* handling Taproot inputs. For each input, it detects if it's a Taproot input and internally
|
|
33
|
+
* tweaks the key if needed.
|
|
34
|
+
*
|
|
35
|
+
* This creates consistency with the BIP32 signing methods (`signBIP32`/`signInputBIP32`),
|
|
36
|
+
* which also automatically handle key tweaking for Taproot inputs. In contrast, bitcoinjs-lib's
|
|
37
|
+
* native implementation requires users to manually pre-tweak ECPair signers for Taproot inputs.
|
|
38
|
+
*
|
|
39
|
+
* With this implementation, you can use a single ECPair to sign both Taproot and non-Taproot
|
|
40
|
+
* inputs in the same PSBT, similar to how `signBIP32` allows using a common node for both types.
|
|
41
|
+
*
|
|
42
|
+
* @see https://github.com/bitcoinjs/bitcoinjs-lib/pull/2137#issuecomment-2713264848
|
|
43
|
+
*
|
|
44
|
+
* @param {Object} params - The parameters object
|
|
45
|
+
* @param {Psbt} params.psbt - The PSBT to sign
|
|
46
|
+
* @param {ECPairInterface} params.ecpair - The ECPair to sign with
|
|
47
|
+
*/
|
|
11
48
|
export declare function signECPair({ psbt, ecpair }: {
|
|
12
49
|
psbt: Psbt;
|
|
13
50
|
ecpair: ECPairInterface;
|
package/dist/signers.js
CHANGED
|
@@ -1,22 +1,94 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
// Copyright (c)
|
|
2
|
+
// Copyright (c) 2025 Jose-Luis Landabaso - https://bitcoinerlab.com
|
|
3
3
|
// Distributed under the MIT software license
|
|
4
4
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
5
|
exports.signLedger = exports.signInputLedger = exports.signBIP32 = exports.signInputBIP32 = exports.signECPair = exports.signInputECPair = void 0;
|
|
6
|
+
const bip371_1 = require("bitcoinjs-lib/src/psbt/bip371");
|
|
7
|
+
const bip341_1 = require("bitcoinjs-lib/src/payments/bip341");
|
|
6
8
|
const ledger_1 = require("./ledger");
|
|
9
|
+
const applyPR2137_1 = require("./applyPR2137");
|
|
10
|
+
function range(n) {
|
|
11
|
+
return [...Array(n).keys()];
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Signs a specific input of a PSBT with an ECPair.
|
|
15
|
+
*
|
|
16
|
+
* Unlike bitcoinjs-lib's native `psbt.signInput()`, this function automatically detects
|
|
17
|
+
* if the input is a Taproot input and internally tweaks the key if needed.
|
|
18
|
+
*
|
|
19
|
+
* This behavior matches how `signInputBIP32` works, where the BIP32 node is automatically
|
|
20
|
+
* tweaked for Taproot inputs. In contrast, bitcoinjs-lib's native implementation requires
|
|
21
|
+
* manual pre-tweaking of ECPair signers for Taproot inputs.
|
|
22
|
+
*
|
|
23
|
+
* @see https://github.com/bitcoinjs/bitcoinjs-lib/pull/2137#issuecomment-2713264848
|
|
24
|
+
*
|
|
25
|
+
* @param {Object} params - The parameters object
|
|
26
|
+
* @param {Psbt} params.psbt - The PSBT to sign
|
|
27
|
+
* @param {number} params.index - The input index to sign
|
|
28
|
+
* @param {ECPairInterface} params.ecpair - The ECPair to sign with
|
|
29
|
+
*/
|
|
7
30
|
function signInputECPair({ psbt, index, ecpair }) {
|
|
8
|
-
psbt.signInput(index, ecpair);
|
|
31
|
+
//psbt.signInput(index, ecpair); <- Replaced for the code below
|
|
32
|
+
//that can handle taroot inputs automatically.
|
|
33
|
+
//See https://github.com/bitcoinjs/bitcoinjs-lib/pull/2137#issuecomment-2713264848
|
|
34
|
+
const input = psbt.data.inputs[index];
|
|
35
|
+
if (!input)
|
|
36
|
+
throw new Error('Invalid index');
|
|
37
|
+
if ((0, bip371_1.isTaprootInput)(input)) {
|
|
38
|
+
const hash = (0, bip341_1.tapTweakHash)(Buffer.from(ecpair.publicKey.slice(1, 33)), undefined);
|
|
39
|
+
const tweakedEcpair = ecpair.tweak(hash);
|
|
40
|
+
psbt.signInput(index, tweakedEcpair);
|
|
41
|
+
}
|
|
42
|
+
else
|
|
43
|
+
psbt.signInput(index, ecpair);
|
|
9
44
|
}
|
|
10
45
|
exports.signInputECPair = signInputECPair;
|
|
46
|
+
/**
|
|
47
|
+
* Signs all inputs of a PSBT with an ECPair.
|
|
48
|
+
*
|
|
49
|
+
* This function improves upon bitcoinjs-lib's native `psbt.signAllInputs()` by automatically
|
|
50
|
+
* handling Taproot inputs. For each input, it detects if it's a Taproot input and internally
|
|
51
|
+
* tweaks the key if needed.
|
|
52
|
+
*
|
|
53
|
+
* This creates consistency with the BIP32 signing methods (`signBIP32`/`signInputBIP32`),
|
|
54
|
+
* which also automatically handle key tweaking for Taproot inputs. In contrast, bitcoinjs-lib's
|
|
55
|
+
* native implementation requires users to manually pre-tweak ECPair signers for Taproot inputs.
|
|
56
|
+
*
|
|
57
|
+
* With this implementation, you can use a single ECPair to sign both Taproot and non-Taproot
|
|
58
|
+
* inputs in the same PSBT, similar to how `signBIP32` allows using a common node for both types.
|
|
59
|
+
*
|
|
60
|
+
* @see https://github.com/bitcoinjs/bitcoinjs-lib/pull/2137#issuecomment-2713264848
|
|
61
|
+
*
|
|
62
|
+
* @param {Object} params - The parameters object
|
|
63
|
+
* @param {Psbt} params.psbt - The PSBT to sign
|
|
64
|
+
* @param {ECPairInterface} params.ecpair - The ECPair to sign with
|
|
65
|
+
*/
|
|
11
66
|
function signECPair({ psbt, ecpair }) {
|
|
12
|
-
psbt.signAllInputs(ecpair);
|
|
67
|
+
//psbt.signAllInputs(ecpair); <- replaced for the code below that handles
|
|
68
|
+
//taptoot automatically.
|
|
69
|
+
//See https://github.com/bitcoinjs/bitcoinjs-lib/pull/2137#issuecomment-2713264848
|
|
70
|
+
const results = [];
|
|
71
|
+
for (const index of range(psbt.data.inputs.length)) {
|
|
72
|
+
try {
|
|
73
|
+
signInputECPair({ psbt, index, ecpair });
|
|
74
|
+
results.push(true);
|
|
75
|
+
}
|
|
76
|
+
catch (err) {
|
|
77
|
+
results.push(false);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (results.every(v => v === false)) {
|
|
81
|
+
throw new Error('No inputs were signed');
|
|
82
|
+
}
|
|
13
83
|
}
|
|
14
84
|
exports.signECPair = signECPair;
|
|
15
85
|
function signInputBIP32({ psbt, index, node }) {
|
|
86
|
+
(0, applyPR2137_1.applyPR2137)(psbt);
|
|
16
87
|
psbt.signInputHD(index, node);
|
|
17
88
|
}
|
|
18
89
|
exports.signInputBIP32 = signInputBIP32;
|
|
19
90
|
function signBIP32({ psbt, masterNode }) {
|
|
91
|
+
(0, applyPR2137_1.applyPR2137)(psbt);
|
|
20
92
|
psbt.signAllInputsHD(masterNode);
|
|
21
93
|
}
|
|
22
94
|
exports.signBIP32 = signBIP32;
|
|
@@ -50,6 +122,8 @@ async function signInputLedger({ psbt, index, descriptor, ledgerClient, ledgerSt
|
|
|
50
122
|
throw new Error(`Error: pass a valid ledgerClient`);
|
|
51
123
|
let ledgerSignatures;
|
|
52
124
|
if (ledgerManager) {
|
|
125
|
+
if (psbt.data.inputs[index]?.tapInternalKey)
|
|
126
|
+
throw new Error('Taproot inputs not yet supported for the Ledger device');
|
|
53
127
|
const policy = await (0, ledger_1.ledgerPolicyFromPsbtInput)({
|
|
54
128
|
psbt,
|
|
55
129
|
index,
|
|
@@ -129,6 +203,8 @@ async function signLedger({ psbt, descriptors, ledgerClient, ledgerState, ledger
|
|
|
129
203
|
const ledgerPolicies = [];
|
|
130
204
|
if (ledgerManager)
|
|
131
205
|
for (let index = 0; index < psbt.data.inputs.length; index++) {
|
|
206
|
+
if (psbt.data.inputs[index]?.tapInternalKey)
|
|
207
|
+
throw new Error('Taproot inputs not yet supported for the Ledger device');
|
|
132
208
|
const policy = await (0, ledger_1.ledgerPolicyFromPsbtInput)({
|
|
133
209
|
psbt,
|
|
134
210
|
index,
|
package/dist/types.d.ts
CHANGED
|
@@ -88,6 +88,7 @@ export interface TinySecp256k1Interface {
|
|
|
88
88
|
verify(h: Uint8Array, Q: Uint8Array, signature: Uint8Array, strict?: boolean): boolean;
|
|
89
89
|
verifySchnorr?(h: Uint8Array, Q: Uint8Array, signature: Uint8Array): boolean;
|
|
90
90
|
xOnlyPointAddTweak(p: Uint8Array, tweak: Uint8Array): XOnlyPointAddTweakResult | null;
|
|
91
|
+
isXOnlyPoint(p: Uint8Array): boolean;
|
|
91
92
|
privateNegate(d: Uint8Array): Uint8Array;
|
|
92
93
|
}
|
|
93
94
|
/**
|
|
@@ -118,6 +119,10 @@ export type Expansion = {
|
|
|
118
119
|
* A boolean indicating whether the descriptor uses SegWit.
|
|
119
120
|
*/
|
|
120
121
|
isSegwit?: boolean;
|
|
122
|
+
/**
|
|
123
|
+
* A boolean indicating whether the descriptor uses Taproot.
|
|
124
|
+
*/
|
|
125
|
+
isTaproot?: boolean;
|
|
121
126
|
/**
|
|
122
127
|
* The expanded miniscript, if any.
|
|
123
128
|
* It corresponds to the `expandedExpression` without the top-level script
|
|
@@ -176,6 +181,13 @@ export interface ParseKeyExpression {
|
|
|
176
181
|
* expression) is compressed (33 bytes).
|
|
177
182
|
*/
|
|
178
183
|
isSegwit?: boolean;
|
|
184
|
+
/**
|
|
185
|
+
* Indicates if this key expression belongs to a Taproot output.
|
|
186
|
+
* For Taproot, the key must be represented as an x-only public key
|
|
187
|
+
* (32 bytes). If a 33-byte compressed pubkey is derived, it is converted to
|
|
188
|
+
* its x-only representation.
|
|
189
|
+
*/
|
|
190
|
+
isTaproot?: boolean;
|
|
179
191
|
network?: Network;
|
|
180
192
|
}): KeyInfo;
|
|
181
193
|
}
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@bitcoinerlab/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/bitcoinerlab/descriptors",
|
|
5
|
-
"version": "2.
|
|
5
|
+
"version": "2.3.0",
|
|
6
6
|
"author": "Jose-Luis Landabaso",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"repository": {
|
|
@@ -68,7 +68,7 @@
|
|
|
68
68
|
},
|
|
69
69
|
"dependencies": {
|
|
70
70
|
"@bitcoinerlab/miniscript": "^1.4.0",
|
|
71
|
-
"@bitcoinerlab/secp256k1": "^1.
|
|
71
|
+
"@bitcoinerlab/secp256k1": "^1.2.0",
|
|
72
72
|
"bip32": "^4.0.0",
|
|
73
73
|
"bitcoinjs-lib": "^6.1.3",
|
|
74
74
|
"ecpair": "^2.1.0",
|