@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.
- package/README.md +214 -0
- package/dist/checksum.d.ts +6 -0
- package/dist/checksum.js +54 -0
- package/dist/descriptors.d.ts +113 -0
- package/dist/descriptors.js +1033 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +10 -0
- package/dist/keyExpressions.d.ts +59 -0
- package/dist/keyExpressions.js +197 -0
- package/dist/miniscript.d.ts +55 -0
- package/dist/miniscript.js +163 -0
- package/dist/networks.d.ts +23 -0
- package/dist/networks.js +37 -0
- package/dist/psbt.d.ts +38 -0
- package/dist/psbt.js +200 -0
- package/dist/re.d.ts +31 -0
- package/dist/re.js +75 -0
- package/dist/scriptExpressions.d.ts +62 -0
- package/dist/scriptExpressions.js +47 -0
- package/dist/scriptUtils.d.ts +19 -0
- package/dist/scriptUtils.js +124 -0
- package/dist/signers.d.ts +62 -0
- package/dist/signers.js +129 -0
- package/dist/types.d.ts +263 -0
- package/dist/types.js +3 -0
- package/package.json +68 -0
|
@@ -0,0 +1,1033 @@
|
|
|
1
|
+
// Copyright (c) 2025 Jose-Luis Landabaso - https://bitcoinerlab.com
|
|
2
|
+
// Distributed under the MIT software license
|
|
3
|
+
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
4
|
+
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
5
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
6
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
|
7
|
+
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
8
|
+
};
|
|
9
|
+
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
10
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
11
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
12
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
13
|
+
};
|
|
14
|
+
/** Simple memoize: caches results keyed by an optional resolver. */
|
|
15
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
16
|
+
function memoize(fn, resolver) {
|
|
17
|
+
const cache = new Map();
|
|
18
|
+
return function (...args) {
|
|
19
|
+
const key = resolver ? resolver(...args) : String(args[0]);
|
|
20
|
+
if (cache.has(key))
|
|
21
|
+
return cache.get(key);
|
|
22
|
+
const result = fn.apply(this, args);
|
|
23
|
+
cache.set(key, result);
|
|
24
|
+
return result;
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
import * as btc from '@scure/btc-signer';
|
|
28
|
+
import { networks, toBtcSignerNetwork } from './networks.js';
|
|
29
|
+
import { varintEncodingLength, decompileScript } from './scriptUtils.js';
|
|
30
|
+
import { hash160, compareBytes, equalBytes, concatBytes } from '@scure/btc-signer/utils.js';
|
|
31
|
+
import { hex } from '@scure/base';
|
|
32
|
+
import { sha256 } from '@noble/hashes/sha2.js';
|
|
33
|
+
import { computeFinalScripts, updatePsbt } from './psbt.js';
|
|
34
|
+
import { DescriptorChecksum } from './checksum.js';
|
|
35
|
+
import { parseKeyExpression as globalParseKeyExpression } from './keyExpressions.js';
|
|
36
|
+
import * as RE from './re.js';
|
|
37
|
+
import { expandMiniscript as globalExpandMiniscript, miniscript2Script, satisfyMiniscript } from './miniscript.js';
|
|
38
|
+
//See "Resource limitations" https://bitcoin.sipa.be/miniscript/
|
|
39
|
+
//https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2019-September/017306.html
|
|
40
|
+
const MAX_SCRIPT_ELEMENT_SIZE = 520;
|
|
41
|
+
const MAX_STANDARD_P2WSH_SCRIPT_SIZE = 3600;
|
|
42
|
+
const MAX_OPS_PER_SCRIPT = 201;
|
|
43
|
+
function countNonPushOnlyOPs(script) {
|
|
44
|
+
const decompiled = decompileScript(script);
|
|
45
|
+
if (!decompiled)
|
|
46
|
+
throw new Error(`Error: cound not decompile ${script}`);
|
|
47
|
+
return decompiled.filter(op => typeof op === 'number' && op > btc.OP.OP_16)
|
|
48
|
+
.length;
|
|
49
|
+
}
|
|
50
|
+
function vectorSize(someVector) {
|
|
51
|
+
const length = someVector.length;
|
|
52
|
+
return (varintEncodingLength(length) +
|
|
53
|
+
someVector.reduce((sum, witness) => {
|
|
54
|
+
return sum + varSliceSize(witness);
|
|
55
|
+
}, 0));
|
|
56
|
+
}
|
|
57
|
+
function varSliceSize(someScript) {
|
|
58
|
+
const length = someScript.length;
|
|
59
|
+
return varintEncodingLength(length) + length;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Safe p2wsh wrapper: tries btc.p2wsh first, falls back to manual computation
|
|
63
|
+
* when btc-signer rejects the inner script (e.g. complex miniscript).
|
|
64
|
+
*/
|
|
65
|
+
function safeP2wsh(innerScript, net) {
|
|
66
|
+
try {
|
|
67
|
+
return btc.p2wsh({ type: 'unknown', script: innerScript }, net);
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
// Manual computation: OP_0 <SHA256(script)>
|
|
71
|
+
const scriptHash = sha256(innerScript);
|
|
72
|
+
const outputScript = btc.OutScript.encode({
|
|
73
|
+
type: 'wsh',
|
|
74
|
+
hash: scriptHash
|
|
75
|
+
});
|
|
76
|
+
const address = net
|
|
77
|
+
? btc.Address(net).encode({ type: 'wsh', hash: scriptHash })
|
|
78
|
+
: undefined;
|
|
79
|
+
return {
|
|
80
|
+
type: 'wsh',
|
|
81
|
+
script: outputScript,
|
|
82
|
+
witnessScript: innerScript,
|
|
83
|
+
...(address ? { address } : {})
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Safe p2sh wrapper: tries btc.p2sh first, falls back to manual computation
|
|
89
|
+
* when btc-signer rejects the inner payment.
|
|
90
|
+
*/
|
|
91
|
+
function safeP2sh(innerScript, net) {
|
|
92
|
+
try {
|
|
93
|
+
return btc.p2sh({ type: 'unknown', script: innerScript }, net);
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
// Manual computation: OP_HASH160 <HASH160(script)> OP_EQUAL
|
|
97
|
+
const scriptHash = hash160(innerScript);
|
|
98
|
+
const outputScript = btc.OutScript.encode({ type: 'sh', hash: scriptHash });
|
|
99
|
+
const address = net
|
|
100
|
+
? btc.Address(net).encode({ type: 'sh', hash: scriptHash })
|
|
101
|
+
: undefined;
|
|
102
|
+
return {
|
|
103
|
+
type: 'sh',
|
|
104
|
+
script: outputScript,
|
|
105
|
+
redeemScript: innerScript,
|
|
106
|
+
...(address ? { address } : {})
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* This function will typically return 73; since it assumes a signature size of
|
|
112
|
+
* 72 bytes (this is the max size of a DER encoded signature) and it adds 1
|
|
113
|
+
* extra byte for encoding its length
|
|
114
|
+
*/
|
|
115
|
+
function signatureSize(signature) {
|
|
116
|
+
const length = signature === 'DANGEROUSLY_USE_FAKE_SIGNATURES'
|
|
117
|
+
? 72
|
|
118
|
+
: signature.signature.length;
|
|
119
|
+
return varintEncodingLength(length) + length;
|
|
120
|
+
}
|
|
121
|
+
/*
|
|
122
|
+
* Returns a bare descriptor without checksum and particularized for a certain
|
|
123
|
+
* index (if desc was a range descriptor)
|
|
124
|
+
* @hidden
|
|
125
|
+
*/
|
|
126
|
+
function evaluate({ descriptor, checksumRequired, index }) {
|
|
127
|
+
if (!descriptor)
|
|
128
|
+
throw new Error('You must provide a descriptor.');
|
|
129
|
+
const mChecksum = descriptor.match(String.raw `(${RE.reChecksum})$`);
|
|
130
|
+
if (mChecksum === null && checksumRequired === true)
|
|
131
|
+
throw new Error(`Error: descriptor ${descriptor} has not checksum`);
|
|
132
|
+
//evaluatedDescriptor: a bare desc without checksum and particularized for a certain
|
|
133
|
+
//index (if desc was a range descriptor)
|
|
134
|
+
let evaluatedDescriptor = descriptor;
|
|
135
|
+
if (mChecksum !== null) {
|
|
136
|
+
const checksum = mChecksum[0].substring(1); //remove the leading #
|
|
137
|
+
evaluatedDescriptor = descriptor.substring(0, descriptor.length - mChecksum[0].length);
|
|
138
|
+
if (checksum !== DescriptorChecksum(evaluatedDescriptor)) {
|
|
139
|
+
throw new Error(`Error: invalid descriptor checksum for ${descriptor}`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
if (index !== undefined) {
|
|
143
|
+
const mWildcard = evaluatedDescriptor.match(/\*/g);
|
|
144
|
+
if (mWildcard && mWildcard.length > 0) {
|
|
145
|
+
evaluatedDescriptor = evaluatedDescriptor.replaceAll('*', index.toString());
|
|
146
|
+
}
|
|
147
|
+
else
|
|
148
|
+
throw new Error(`Error: index passed for non-ranged descriptor: ${descriptor}`);
|
|
149
|
+
}
|
|
150
|
+
return evaluatedDescriptor;
|
|
151
|
+
}
|
|
152
|
+
// Helper: parse sortedmulti(M, k1, k2,...)
|
|
153
|
+
function parseSortedMulti(inner) {
|
|
154
|
+
const parts = inner.split(',').map(p => p.trim());
|
|
155
|
+
if (parts.length < 2)
|
|
156
|
+
throw new Error(`sortedmulti(): must contain M and at least one key: ${inner}`);
|
|
157
|
+
const m = Number(parts[0]);
|
|
158
|
+
if (!Number.isInteger(m) || m < 1 || m > 20)
|
|
159
|
+
throw new Error(`sortedmulti(): invalid M=${parts[0]}`);
|
|
160
|
+
const keyExpressions = parts.slice(1);
|
|
161
|
+
if (keyExpressions.length < m)
|
|
162
|
+
throw new Error(`sortedmulti(): M cannot exceed number of keys: ${inner}`);
|
|
163
|
+
if (keyExpressions.length > 20)
|
|
164
|
+
throw new Error(`sortedmulti(): descriptors support up to 20 keys (per BIP 380/383).`);
|
|
165
|
+
return { m, keyExpressions };
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Constructs the necessary functions and classes for working with descriptors.
|
|
169
|
+
*
|
|
170
|
+
* Notably, it returns the {@link _Internal_.Output | `Output`} class, which
|
|
171
|
+
* provides methods to create, sign, and finalize PSBTs based on descriptor
|
|
172
|
+
* expressions.
|
|
173
|
+
*
|
|
174
|
+
* The Factory also returns utility methods like `expand` (detailed below)
|
|
175
|
+
* and `parseKeyExpression` (see {@link ParseKeyExpression}).
|
|
176
|
+
*
|
|
177
|
+
* Additionally, for convenience, the function returns `BIP32` and `ECPair`.
|
|
178
|
+
* These are compatible interfaces for managing BIP32 keys and
|
|
179
|
+
* public/private key pairs respectively.
|
|
180
|
+
*
|
|
181
|
+
* @param {Object} params - An object with `ECPair` and `BIP32` factories.
|
|
182
|
+
*/
|
|
183
|
+
export function DescriptorsFactory({ ECPair, BIP32 }) {
|
|
184
|
+
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
|
+
/**
|
|
186
|
+
* Takes a string key expression (xpub, xprv, pubkey or wif) and parses it
|
|
187
|
+
*/
|
|
188
|
+
const parseKeyExpression = ({ keyExpression, isSegwit, isTaproot, network = networks.bitcoin }) => {
|
|
189
|
+
return globalParseKeyExpression({
|
|
190
|
+
keyExpression,
|
|
191
|
+
network,
|
|
192
|
+
...(typeof isSegwit === 'boolean' ? { isSegwit } : {}),
|
|
193
|
+
...(typeof isTaproot === 'boolean' ? { isTaproot } : {}),
|
|
194
|
+
ECPair,
|
|
195
|
+
BIP32
|
|
196
|
+
});
|
|
197
|
+
};
|
|
198
|
+
/**
|
|
199
|
+
* Parses and analyzies a descriptor expression and destructures it into
|
|
200
|
+
* {@link Expansion |its elemental parts}.
|
|
201
|
+
*
|
|
202
|
+
* @throws {Error} Throws an error if the descriptor cannot be parsed or does
|
|
203
|
+
* not conform to the expected format.
|
|
204
|
+
*/
|
|
205
|
+
function expand({ descriptor, index, checksumRequired = false, network = networks.bitcoin, allowMiniscriptInP2SH = false }) {
|
|
206
|
+
let expandedExpression;
|
|
207
|
+
let miniscript;
|
|
208
|
+
let expansionMap;
|
|
209
|
+
let isSegwit;
|
|
210
|
+
let isTaproot;
|
|
211
|
+
let expandedMiniscript;
|
|
212
|
+
let payment;
|
|
213
|
+
let witnessScript;
|
|
214
|
+
let redeemScript;
|
|
215
|
+
const isRanged = descriptor.indexOf('*') !== -1;
|
|
216
|
+
const net = toBtcSignerNetwork(network);
|
|
217
|
+
if (index !== undefined)
|
|
218
|
+
if (!Number.isInteger(index) || index < 0)
|
|
219
|
+
throw new Error(`Error: invalid index ${index}`);
|
|
220
|
+
const canonicalExpression = evaluate({
|
|
221
|
+
descriptor,
|
|
222
|
+
...(index !== undefined ? { index } : {}),
|
|
223
|
+
checksumRequired
|
|
224
|
+
});
|
|
225
|
+
const isCanonicalRanged = canonicalExpression.indexOf('*') !== -1;
|
|
226
|
+
//addr(ADDR)
|
|
227
|
+
if (canonicalExpression.match(RE.reAddrAnchored)) {
|
|
228
|
+
if (isRanged)
|
|
229
|
+
throw new Error(`Error: addr() cannot be ranged`);
|
|
230
|
+
const matchedAddress = canonicalExpression.match(RE.reAddrAnchored)?.[1];
|
|
231
|
+
if (!matchedAddress)
|
|
232
|
+
throw new Error(`Error: could not get an address in ${descriptor}`);
|
|
233
|
+
let output;
|
|
234
|
+
try {
|
|
235
|
+
const decoded = btc
|
|
236
|
+
.Address(toBtcSignerNetwork(network))
|
|
237
|
+
.decode(matchedAddress);
|
|
238
|
+
output = btc.OutScript.encode(decoded);
|
|
239
|
+
}
|
|
240
|
+
catch (e) {
|
|
241
|
+
void e;
|
|
242
|
+
throw new Error(`Error: invalid address ${matchedAddress}`);
|
|
243
|
+
}
|
|
244
|
+
// Detect output type using OutScript.decode
|
|
245
|
+
let scriptType;
|
|
246
|
+
try {
|
|
247
|
+
const decoded = btc.OutScript.decode(output);
|
|
248
|
+
scriptType = decoded.type;
|
|
249
|
+
}
|
|
250
|
+
catch {
|
|
251
|
+
throw new Error(`Error: invalid address ${matchedAddress}`);
|
|
252
|
+
}
|
|
253
|
+
// For addr() we already have the output script.
|
|
254
|
+
// We build a minimal P2Ret with the script and address.
|
|
255
|
+
payment = {
|
|
256
|
+
type: scriptType,
|
|
257
|
+
address: matchedAddress,
|
|
258
|
+
script: output
|
|
259
|
+
};
|
|
260
|
+
if (scriptType === 'pkh') {
|
|
261
|
+
isSegwit = false;
|
|
262
|
+
isTaproot = false;
|
|
263
|
+
}
|
|
264
|
+
else if (scriptType === 'sh') {
|
|
265
|
+
isSegwit = true; // Assume SH is SH_WPKH
|
|
266
|
+
isTaproot = false;
|
|
267
|
+
}
|
|
268
|
+
else if (scriptType === 'wpkh') {
|
|
269
|
+
isSegwit = true;
|
|
270
|
+
isTaproot = false;
|
|
271
|
+
}
|
|
272
|
+
else if (scriptType === 'wsh') {
|
|
273
|
+
isSegwit = true;
|
|
274
|
+
isTaproot = false;
|
|
275
|
+
}
|
|
276
|
+
else if (scriptType === 'tr') {
|
|
277
|
+
isSegwit = true;
|
|
278
|
+
isTaproot = true;
|
|
279
|
+
}
|
|
280
|
+
else {
|
|
281
|
+
throw new Error(`Error: invalid address ${matchedAddress}`);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
//pk(KEY)
|
|
285
|
+
else if (canonicalExpression.match(RE.rePkAnchored)) {
|
|
286
|
+
isSegwit = false;
|
|
287
|
+
isTaproot = false;
|
|
288
|
+
const keyExpression = canonicalExpression.match(RE.reNonSegwitKeyExp)?.[0];
|
|
289
|
+
if (!keyExpression)
|
|
290
|
+
throw new Error(`Error: keyExpression could not me extracted`);
|
|
291
|
+
if (canonicalExpression !== `pk(${keyExpression})`)
|
|
292
|
+
throw new Error(`Error: invalid expression ${descriptor}`);
|
|
293
|
+
expandedExpression = 'pk(@0)';
|
|
294
|
+
const pKE = parseKeyExpression({ keyExpression, network, isSegwit });
|
|
295
|
+
expansionMap = { '@0': pKE };
|
|
296
|
+
if (!isCanonicalRanged) {
|
|
297
|
+
const pubkey = pKE.pubkey;
|
|
298
|
+
if (!pubkey)
|
|
299
|
+
throw new Error(`Error: could not extract a pubkey from ${descriptor}`);
|
|
300
|
+
payment = btc.p2pk(pubkey, net);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
//pkh(KEY) - legacy
|
|
304
|
+
else if (canonicalExpression.match(RE.rePkhAnchored)) {
|
|
305
|
+
isSegwit = false;
|
|
306
|
+
isTaproot = false;
|
|
307
|
+
const keyExpression = canonicalExpression.match(RE.reNonSegwitKeyExp)?.[0];
|
|
308
|
+
if (!keyExpression)
|
|
309
|
+
throw new Error(`Error: keyExpression could not me extracted`);
|
|
310
|
+
if (canonicalExpression !== `pkh(${keyExpression})`)
|
|
311
|
+
throw new Error(`Error: invalid expression ${descriptor}`);
|
|
312
|
+
expandedExpression = 'pkh(@0)';
|
|
313
|
+
const pKE = parseKeyExpression({ keyExpression, network, isSegwit });
|
|
314
|
+
expansionMap = { '@0': pKE };
|
|
315
|
+
if (!isCanonicalRanged) {
|
|
316
|
+
const pubkey = pKE.pubkey;
|
|
317
|
+
if (!pubkey)
|
|
318
|
+
throw new Error(`Error: could not extract a pubkey from ${descriptor}`);
|
|
319
|
+
payment = btc.p2pkh(pubkey, net);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
//sh(wpkh(KEY)) - nested segwit
|
|
323
|
+
else if (canonicalExpression.match(RE.reShWpkhAnchored)) {
|
|
324
|
+
isSegwit = true;
|
|
325
|
+
isTaproot = false;
|
|
326
|
+
const keyExpression = canonicalExpression.match(RE.reSegwitKeyExp)?.[0];
|
|
327
|
+
if (!keyExpression)
|
|
328
|
+
throw new Error(`Error: keyExpression could not me extracted`);
|
|
329
|
+
if (canonicalExpression !== `sh(wpkh(${keyExpression}))`)
|
|
330
|
+
throw new Error(`Error: invalid expression ${descriptor}`);
|
|
331
|
+
expandedExpression = 'sh(wpkh(@0))';
|
|
332
|
+
const pKE = parseKeyExpression({ keyExpression, network, isSegwit });
|
|
333
|
+
expansionMap = { '@0': pKE };
|
|
334
|
+
if (!isCanonicalRanged) {
|
|
335
|
+
const pubkey = pKE.pubkey;
|
|
336
|
+
if (!pubkey)
|
|
337
|
+
throw new Error(`Error: could not extract a pubkey from ${descriptor}`);
|
|
338
|
+
const wpkhPayment = btc.p2wpkh(pubkey, net);
|
|
339
|
+
payment = btc.p2sh(wpkhPayment, net);
|
|
340
|
+
redeemScript = payment.redeemScript ?? wpkhPayment.script;
|
|
341
|
+
if (!redeemScript)
|
|
342
|
+
throw new Error(`Error: could not calculate redeemScript for ${descriptor}`);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
//wpkh(KEY) - native segwit
|
|
346
|
+
else if (canonicalExpression.match(RE.reWpkhAnchored)) {
|
|
347
|
+
isSegwit = true;
|
|
348
|
+
isTaproot = false;
|
|
349
|
+
const keyExpression = canonicalExpression.match(RE.reSegwitKeyExp)?.[0];
|
|
350
|
+
if (!keyExpression)
|
|
351
|
+
throw new Error(`Error: keyExpression could not me extracted`);
|
|
352
|
+
if (canonicalExpression !== `wpkh(${keyExpression})`)
|
|
353
|
+
throw new Error(`Error: invalid expression ${descriptor}`);
|
|
354
|
+
expandedExpression = 'wpkh(@0)';
|
|
355
|
+
const pKE = parseKeyExpression({ keyExpression, network, isSegwit });
|
|
356
|
+
expansionMap = { '@0': pKE };
|
|
357
|
+
if (!isCanonicalRanged) {
|
|
358
|
+
const pubkey = pKE.pubkey;
|
|
359
|
+
if (!pubkey)
|
|
360
|
+
throw new Error(`Error: could not extract a pubkey from ${descriptor}`);
|
|
361
|
+
payment = btc.p2wpkh(pubkey, net);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
// sortedmulti script expressions
|
|
365
|
+
// sh(sortedmulti())
|
|
366
|
+
else if (canonicalExpression.match(RE.reShSortedMultiAnchored)) {
|
|
367
|
+
isSegwit = false;
|
|
368
|
+
isTaproot = false;
|
|
369
|
+
const inner = canonicalExpression.match(RE.reShSortedMultiAnchored)?.[1];
|
|
370
|
+
if (!inner)
|
|
371
|
+
throw new Error(`Error extracting sortedmulti() in ${descriptor}`);
|
|
372
|
+
const { m, keyExpressions } = parseSortedMulti(inner);
|
|
373
|
+
const pKEs = keyExpressions.map(k => parseKeyExpression({ keyExpression: k, network, isSegwit: false }));
|
|
374
|
+
const map = {};
|
|
375
|
+
pKEs.forEach((pke, i) => (map['@' + i] = pke));
|
|
376
|
+
expansionMap = map;
|
|
377
|
+
expandedExpression =
|
|
378
|
+
'sh(sortedmulti(' +
|
|
379
|
+
[m, ...Object.keys(expansionMap).map(k => k)].join(',') +
|
|
380
|
+
'))';
|
|
381
|
+
if (!isCanonicalRanged) {
|
|
382
|
+
const pubkeys = pKEs.map(p => {
|
|
383
|
+
if (!p.pubkey)
|
|
384
|
+
throw new Error(`Error: key has no pubkey`);
|
|
385
|
+
return p.pubkey;
|
|
386
|
+
});
|
|
387
|
+
pubkeys.sort((a, b) => compareBytes(a, b));
|
|
388
|
+
const redeem = btc.p2ms(m, pubkeys, undefined);
|
|
389
|
+
redeemScript = redeem.script;
|
|
390
|
+
payment = btc.p2sh(redeem, net);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
// wsh(sortedmulti())
|
|
394
|
+
else if (canonicalExpression.match(RE.reWshSortedMultiAnchored)) {
|
|
395
|
+
isSegwit = true;
|
|
396
|
+
isTaproot = false;
|
|
397
|
+
const inner = canonicalExpression.match(RE.reWshSortedMultiAnchored)?.[1];
|
|
398
|
+
if (!inner)
|
|
399
|
+
throw new Error(`Error extracting sortedmulti() in ${descriptor}`);
|
|
400
|
+
const { m, keyExpressions } = parseSortedMulti(inner);
|
|
401
|
+
const pKEs = keyExpressions.map(k => parseKeyExpression({ keyExpression: k, network, isSegwit: true }));
|
|
402
|
+
const map = {};
|
|
403
|
+
pKEs.forEach((pke, i) => (map['@' + i] = pke));
|
|
404
|
+
expansionMap = map;
|
|
405
|
+
expandedExpression =
|
|
406
|
+
'wsh(sortedmulti(' +
|
|
407
|
+
[m, ...Object.keys(expansionMap).map(k => k)].join(',') +
|
|
408
|
+
'))';
|
|
409
|
+
if (!isCanonicalRanged) {
|
|
410
|
+
const pubkeys = pKEs.map(p => {
|
|
411
|
+
if (!p.pubkey)
|
|
412
|
+
throw new Error(`Error: key has no pubkey`);
|
|
413
|
+
return p.pubkey;
|
|
414
|
+
});
|
|
415
|
+
pubkeys.sort((a, b) => compareBytes(a, b));
|
|
416
|
+
const redeem = btc.p2ms(m, pubkeys, undefined);
|
|
417
|
+
witnessScript = redeem.script;
|
|
418
|
+
payment = btc.p2wsh(redeem, net);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
// sh(wsh(sortedmulti()))
|
|
422
|
+
else if (canonicalExpression.match(RE.reShWshSortedMultiAnchored)) {
|
|
423
|
+
isSegwit = true;
|
|
424
|
+
isTaproot = false;
|
|
425
|
+
const inner = canonicalExpression.match(RE.reShWshSortedMultiAnchored)?.[1];
|
|
426
|
+
if (!inner)
|
|
427
|
+
throw new Error(`Error extracting sortedmulti() in ${descriptor}`);
|
|
428
|
+
const { m, keyExpressions } = parseSortedMulti(inner);
|
|
429
|
+
const pKEs = keyExpressions.map(k => parseKeyExpression({ keyExpression: k, network, isSegwit: true }));
|
|
430
|
+
const map = {};
|
|
431
|
+
pKEs.forEach((pke, i) => (map['@' + i] = pke));
|
|
432
|
+
expansionMap = map;
|
|
433
|
+
expandedExpression =
|
|
434
|
+
'sh(wsh(sortedmulti(' +
|
|
435
|
+
[m, ...Object.keys(expansionMap).map(k => k)].join(',') +
|
|
436
|
+
')))';
|
|
437
|
+
if (!isCanonicalRanged) {
|
|
438
|
+
const pubkeys = pKEs.map(p => {
|
|
439
|
+
if (!p.pubkey)
|
|
440
|
+
throw new Error(`Error: key has no pubkey`);
|
|
441
|
+
return p.pubkey;
|
|
442
|
+
});
|
|
443
|
+
pubkeys.sort((a, b) => compareBytes(a, b));
|
|
444
|
+
const redeem = btc.p2ms(m, pubkeys, undefined);
|
|
445
|
+
const wsh = btc.p2wsh(redeem, net);
|
|
446
|
+
witnessScript = redeem.script;
|
|
447
|
+
redeemScript = wsh.script;
|
|
448
|
+
payment = btc.p2sh(wsh, net);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
//sh(wsh(miniscript))
|
|
452
|
+
else if (canonicalExpression.match(RE.reShWshMiniscriptAnchored)) {
|
|
453
|
+
isSegwit = true;
|
|
454
|
+
isTaproot = false;
|
|
455
|
+
miniscript = canonicalExpression.match(RE.reShWshMiniscriptAnchored)?.[1];
|
|
456
|
+
if (!miniscript)
|
|
457
|
+
throw new Error(`Error: could not get miniscript in ${descriptor}`);
|
|
458
|
+
({ expandedMiniscript, expansionMap } = expandMiniscript({
|
|
459
|
+
miniscript,
|
|
460
|
+
isSegwit,
|
|
461
|
+
network
|
|
462
|
+
}));
|
|
463
|
+
expandedExpression = `sh(wsh(${expandedMiniscript}))`;
|
|
464
|
+
if (!isCanonicalRanged) {
|
|
465
|
+
const script = miniscript2Script({ expandedMiniscript, expansionMap });
|
|
466
|
+
witnessScript = script;
|
|
467
|
+
if (script.byteLength > MAX_STANDARD_P2WSH_SCRIPT_SIZE) {
|
|
468
|
+
throw new Error(`Error: script is too large, ${script.byteLength} bytes is larger than ${MAX_STANDARD_P2WSH_SCRIPT_SIZE} bytes`);
|
|
469
|
+
}
|
|
470
|
+
const nonPushOnlyOps = countNonPushOnlyOPs(script);
|
|
471
|
+
if (nonPushOnlyOps > MAX_OPS_PER_SCRIPT) {
|
|
472
|
+
throw new Error(`Error: too many non-push ops, ${nonPushOnlyOps} non-push ops is larger than ${MAX_OPS_PER_SCRIPT}`);
|
|
473
|
+
}
|
|
474
|
+
const wshPayment = safeP2wsh(script, net);
|
|
475
|
+
const shPayment = safeP2sh(wshPayment.script, net);
|
|
476
|
+
payment = shPayment;
|
|
477
|
+
redeemScript = wshPayment.script;
|
|
478
|
+
if (!redeemScript)
|
|
479
|
+
throw new Error(`Error: could not calculate redeemScript for ${descriptor}`);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
//sh(miniscript)
|
|
483
|
+
else if (canonicalExpression.match(RE.reShMiniscriptAnchored)) {
|
|
484
|
+
isSegwit = false;
|
|
485
|
+
isTaproot = false;
|
|
486
|
+
miniscript = canonicalExpression.match(RE.reShMiniscriptAnchored)?.[1];
|
|
487
|
+
if (!miniscript)
|
|
488
|
+
throw new Error(`Error: could not get miniscript in ${descriptor}`);
|
|
489
|
+
if (allowMiniscriptInP2SH === false &&
|
|
490
|
+
miniscript.search(/^(pk\(|pkh\(|wpkh\(|combo\(|multi\(|sortedmulti\(|multi_a\(|sortedmulti_a\()/) !== 0) {
|
|
491
|
+
throw new Error(`Error: Miniscript expressions can only be used in wsh`);
|
|
492
|
+
}
|
|
493
|
+
({ expandedMiniscript, expansionMap } = expandMiniscript({
|
|
494
|
+
miniscript,
|
|
495
|
+
isSegwit,
|
|
496
|
+
network
|
|
497
|
+
}));
|
|
498
|
+
expandedExpression = `sh(${expandedMiniscript})`;
|
|
499
|
+
if (!isCanonicalRanged) {
|
|
500
|
+
const script = miniscript2Script({ expandedMiniscript, expansionMap });
|
|
501
|
+
redeemScript = script;
|
|
502
|
+
if (script.byteLength > MAX_SCRIPT_ELEMENT_SIZE) {
|
|
503
|
+
throw new Error(`Error: P2SH script is too large, ${script.byteLength} bytes is larger than ${MAX_SCRIPT_ELEMENT_SIZE} bytes`);
|
|
504
|
+
}
|
|
505
|
+
const nonPushOnlyOps = countNonPushOnlyOPs(script);
|
|
506
|
+
if (nonPushOnlyOps > MAX_OPS_PER_SCRIPT) {
|
|
507
|
+
throw new Error(`Error: too many non-push ops, ${nonPushOnlyOps} non-push ops is larger than ${MAX_OPS_PER_SCRIPT}`);
|
|
508
|
+
}
|
|
509
|
+
payment = safeP2sh(script, net);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
//wsh(miniscript)
|
|
513
|
+
else if (canonicalExpression.match(RE.reWshMiniscriptAnchored)) {
|
|
514
|
+
isSegwit = true;
|
|
515
|
+
isTaproot = false;
|
|
516
|
+
miniscript = canonicalExpression.match(RE.reWshMiniscriptAnchored)?.[1];
|
|
517
|
+
if (!miniscript)
|
|
518
|
+
throw new Error(`Error: could not get miniscript in ${descriptor}`);
|
|
519
|
+
({ expandedMiniscript, expansionMap } = expandMiniscript({
|
|
520
|
+
miniscript,
|
|
521
|
+
isSegwit,
|
|
522
|
+
network
|
|
523
|
+
}));
|
|
524
|
+
expandedExpression = `wsh(${expandedMiniscript})`;
|
|
525
|
+
if (!isCanonicalRanged) {
|
|
526
|
+
const script = miniscript2Script({ expandedMiniscript, expansionMap });
|
|
527
|
+
witnessScript = script;
|
|
528
|
+
if (script.byteLength > MAX_STANDARD_P2WSH_SCRIPT_SIZE) {
|
|
529
|
+
throw new Error(`Error: script is too large, ${script.byteLength} bytes is larger than ${MAX_STANDARD_P2WSH_SCRIPT_SIZE} bytes`);
|
|
530
|
+
}
|
|
531
|
+
const nonPushOnlyOps = countNonPushOnlyOPs(script);
|
|
532
|
+
if (nonPushOnlyOps > MAX_OPS_PER_SCRIPT) {
|
|
533
|
+
throw new Error(`Error: too many non-push ops, ${nonPushOnlyOps} non-push ops is larger than ${MAX_OPS_PER_SCRIPT}`);
|
|
534
|
+
}
|
|
535
|
+
payment = safeP2wsh(script, net);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
//tr(KEY) - taproot
|
|
539
|
+
else if (canonicalExpression.match(RE.reTrSingleKeyAnchored)) {
|
|
540
|
+
isSegwit = true;
|
|
541
|
+
isTaproot = true;
|
|
542
|
+
const keyExpression = canonicalExpression.match(RE.reTaprootKeyExp)?.[0];
|
|
543
|
+
if (!keyExpression)
|
|
544
|
+
throw new Error(`Error: keyExpression could not me extracted`);
|
|
545
|
+
if (canonicalExpression !== `tr(${keyExpression})`)
|
|
546
|
+
throw new Error(`Error: invalid expression ${descriptor}`);
|
|
547
|
+
expandedExpression = 'tr(@0)';
|
|
548
|
+
const pKE = parseKeyExpression({
|
|
549
|
+
keyExpression,
|
|
550
|
+
network,
|
|
551
|
+
isSegwit,
|
|
552
|
+
isTaproot
|
|
553
|
+
});
|
|
554
|
+
expansionMap = { '@0': pKE };
|
|
555
|
+
if (!isCanonicalRanged) {
|
|
556
|
+
const pubkey = pKE.pubkey;
|
|
557
|
+
if (!pubkey)
|
|
558
|
+
throw new Error(`Error: could not extract a pubkey from ${descriptor}`);
|
|
559
|
+
payment = btc.p2tr(pubkey, undefined, net);
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
else {
|
|
563
|
+
throw new Error(`Error: Could not parse descriptor ${descriptor}`);
|
|
564
|
+
}
|
|
565
|
+
return {
|
|
566
|
+
...(payment !== undefined ? { payment } : {}),
|
|
567
|
+
...(expandedExpression !== undefined ? { expandedExpression } : {}),
|
|
568
|
+
...(miniscript !== undefined ? { miniscript } : {}),
|
|
569
|
+
...(expansionMap !== undefined ? { expansionMap } : {}),
|
|
570
|
+
...(isSegwit !== undefined ? { isSegwit } : {}),
|
|
571
|
+
...(isTaproot !== undefined ? { isTaproot } : {}),
|
|
572
|
+
...(expandedMiniscript !== undefined ? { expandedMiniscript } : {}),
|
|
573
|
+
...(redeemScript !== undefined ? { redeemScript } : {}),
|
|
574
|
+
...(witnessScript !== undefined ? { witnessScript } : {}),
|
|
575
|
+
isRanged,
|
|
576
|
+
canonicalExpression
|
|
577
|
+
};
|
|
578
|
+
}
|
|
579
|
+
/**
|
|
580
|
+
* Expand a miniscript to a generalized form using variables instead of key
|
|
581
|
+
* expressions.
|
|
582
|
+
*/
|
|
583
|
+
function expandMiniscript({ miniscript, isSegwit, network = networks.bitcoin }) {
|
|
584
|
+
return globalExpandMiniscript({
|
|
585
|
+
miniscript,
|
|
586
|
+
isSegwit,
|
|
587
|
+
isTaproot: false,
|
|
588
|
+
network,
|
|
589
|
+
BIP32,
|
|
590
|
+
ECPair
|
|
591
|
+
});
|
|
592
|
+
}
|
|
593
|
+
/**
|
|
594
|
+
* The `Output` class is the central component for managing descriptors.
|
|
595
|
+
*/
|
|
596
|
+
class Output {
|
|
597
|
+
constructor({ descriptor, index, checksumRequired = false, allowMiniscriptInP2SH = false, network = networks.bitcoin, preimages = [], signersPubKeys }) {
|
|
598
|
+
_Output_instances.add(this);
|
|
599
|
+
_Output_payment.set(this, void 0);
|
|
600
|
+
_Output_preimages.set(this, []);
|
|
601
|
+
_Output_signersPubKeys.set(this, void 0);
|
|
602
|
+
_Output_miniscript.set(this, void 0);
|
|
603
|
+
_Output_witnessScript.set(this, void 0);
|
|
604
|
+
_Output_redeemScript.set(this, void 0);
|
|
605
|
+
_Output_isSegwit.set(this, void 0);
|
|
606
|
+
_Output_isTaproot.set(this, void 0);
|
|
607
|
+
_Output_expandedExpression.set(this, void 0);
|
|
608
|
+
_Output_expandedMiniscript.set(this, void 0);
|
|
609
|
+
_Output_expansionMap.set(this, void 0);
|
|
610
|
+
_Output_network.set(this, void 0);
|
|
611
|
+
__classPrivateFieldSet(this, _Output_network, network, "f");
|
|
612
|
+
__classPrivateFieldSet(this, _Output_preimages, preimages, "f");
|
|
613
|
+
if (typeof descriptor !== 'string')
|
|
614
|
+
throw new Error(`Error: invalid descriptor type`);
|
|
615
|
+
const expandedResult = expand({
|
|
616
|
+
descriptor,
|
|
617
|
+
...(index !== undefined ? { index } : {}),
|
|
618
|
+
checksumRequired,
|
|
619
|
+
network,
|
|
620
|
+
allowMiniscriptInP2SH
|
|
621
|
+
});
|
|
622
|
+
if (expandedResult.isRanged && index === undefined)
|
|
623
|
+
throw new Error(`Error: index was not provided for ranged descriptor`);
|
|
624
|
+
if (!expandedResult.payment)
|
|
625
|
+
throw new Error(`Error: could not extract a payment from ${descriptor}`);
|
|
626
|
+
__classPrivateFieldSet(this, _Output_payment, expandedResult.payment, "f");
|
|
627
|
+
if (expandedResult.expandedExpression !== undefined)
|
|
628
|
+
__classPrivateFieldSet(this, _Output_expandedExpression, expandedResult.expandedExpression, "f");
|
|
629
|
+
if (expandedResult.miniscript !== undefined)
|
|
630
|
+
__classPrivateFieldSet(this, _Output_miniscript, expandedResult.miniscript, "f");
|
|
631
|
+
if (expandedResult.expansionMap !== undefined)
|
|
632
|
+
__classPrivateFieldSet(this, _Output_expansionMap, expandedResult.expansionMap, "f");
|
|
633
|
+
if (expandedResult.isSegwit !== undefined)
|
|
634
|
+
__classPrivateFieldSet(this, _Output_isSegwit, expandedResult.isSegwit, "f");
|
|
635
|
+
if (expandedResult.isTaproot !== undefined)
|
|
636
|
+
__classPrivateFieldSet(this, _Output_isTaproot, expandedResult.isTaproot, "f");
|
|
637
|
+
if (expandedResult.expandedMiniscript !== undefined)
|
|
638
|
+
__classPrivateFieldSet(this, _Output_expandedMiniscript, expandedResult.expandedMiniscript, "f");
|
|
639
|
+
if (expandedResult.redeemScript !== undefined)
|
|
640
|
+
__classPrivateFieldSet(this, _Output_redeemScript, expandedResult.redeemScript, "f");
|
|
641
|
+
if (expandedResult.witnessScript !== undefined)
|
|
642
|
+
__classPrivateFieldSet(this, _Output_witnessScript, expandedResult.witnessScript, "f");
|
|
643
|
+
if (signersPubKeys) {
|
|
644
|
+
__classPrivateFieldSet(this, _Output_signersPubKeys, signersPubKeys, "f");
|
|
645
|
+
}
|
|
646
|
+
else {
|
|
647
|
+
if (__classPrivateFieldGet(this, _Output_expansionMap, "f")) {
|
|
648
|
+
__classPrivateFieldSet(this, _Output_signersPubKeys, Object.values(__classPrivateFieldGet(this, _Output_expansionMap, "f")).map(keyInfo => {
|
|
649
|
+
const pubkey = keyInfo.pubkey;
|
|
650
|
+
if (!pubkey)
|
|
651
|
+
throw new Error(`Error: could not extract a pubkey from ${descriptor}`);
|
|
652
|
+
return pubkey;
|
|
653
|
+
}), "f");
|
|
654
|
+
}
|
|
655
|
+
else {
|
|
656
|
+
if (!expandedResult.canonicalExpression.match(RE.reAddrAnchored)) {
|
|
657
|
+
throw new Error(`Error: expansionMap not available for expression ${descriptor} that is not an address`);
|
|
658
|
+
}
|
|
659
|
+
__classPrivateFieldSet(this, _Output_signersPubKeys, [this.getScriptPubKey()], "f");
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
this.getSequence = memoize(this.getSequence);
|
|
663
|
+
this.getLockTime = memoize(this.getLockTime);
|
|
664
|
+
const getSignaturesKey = (signatures) => signatures === 'DANGEROUSLY_USE_FAKE_SIGNATURES'
|
|
665
|
+
? signatures
|
|
666
|
+
: signatures
|
|
667
|
+
.map(s => `${hex.encode(s.pubkey)}-${hex.encode(s.signature)}`)
|
|
668
|
+
.join('|');
|
|
669
|
+
this.getScriptSatisfaction = memoize(this.getScriptSatisfaction, getSignaturesKey);
|
|
670
|
+
this.guessOutput = memoize(this.guessOutput);
|
|
671
|
+
this.inputWeight = memoize(this.inputWeight, (isSegwitTx, signatures) => {
|
|
672
|
+
const segwitKey = isSegwitTx ? 'segwit' : 'non-segwit';
|
|
673
|
+
const signaturesKey = getSignaturesKey(signatures);
|
|
674
|
+
return `${segwitKey}-${signaturesKey}`;
|
|
675
|
+
});
|
|
676
|
+
this.outputWeight = memoize(this.outputWeight);
|
|
677
|
+
}
|
|
678
|
+
getPayment() {
|
|
679
|
+
return __classPrivateFieldGet(this, _Output_payment, "f");
|
|
680
|
+
}
|
|
681
|
+
getAddress() {
|
|
682
|
+
if (!__classPrivateFieldGet(this, _Output_payment, "f").address)
|
|
683
|
+
throw new Error(`Error: could extract an address from the payment`);
|
|
684
|
+
return __classPrivateFieldGet(this, _Output_payment, "f").address;
|
|
685
|
+
}
|
|
686
|
+
getScriptPubKey() {
|
|
687
|
+
if (!__classPrivateFieldGet(this, _Output_payment, "f").script)
|
|
688
|
+
throw new Error(`Error: could not extract script from the payment`);
|
|
689
|
+
return __classPrivateFieldGet(this, _Output_payment, "f").script;
|
|
690
|
+
}
|
|
691
|
+
getScriptSatisfaction(signatures) {
|
|
692
|
+
if (signatures === 'DANGEROUSLY_USE_FAKE_SIGNATURES')
|
|
693
|
+
signatures = __classPrivateFieldGet(this, _Output_signersPubKeys, "f").map(pubkey => ({
|
|
694
|
+
pubkey,
|
|
695
|
+
signature: new Uint8Array(72)
|
|
696
|
+
}));
|
|
697
|
+
const miniscript = __classPrivateFieldGet(this, _Output_miniscript, "f");
|
|
698
|
+
const expandedMiniscript = __classPrivateFieldGet(this, _Output_expandedMiniscript, "f");
|
|
699
|
+
const expansionMap = __classPrivateFieldGet(this, _Output_expansionMap, "f");
|
|
700
|
+
if (miniscript === undefined ||
|
|
701
|
+
expandedMiniscript === undefined ||
|
|
702
|
+
expansionMap === undefined)
|
|
703
|
+
throw new Error(`Error: cannot get satisfaction from not expanded miniscript ${miniscript}`);
|
|
704
|
+
const scriptSatisfaction = satisfyMiniscript({
|
|
705
|
+
expandedMiniscript,
|
|
706
|
+
expansionMap,
|
|
707
|
+
signatures,
|
|
708
|
+
preimages: __classPrivateFieldGet(this, _Output_preimages, "f"),
|
|
709
|
+
timeConstraints: {
|
|
710
|
+
nLockTime: this.getLockTime(),
|
|
711
|
+
nSequence: this.getSequence()
|
|
712
|
+
}
|
|
713
|
+
}).scriptSatisfaction;
|
|
714
|
+
if (!scriptSatisfaction)
|
|
715
|
+
throw new Error(`Error: could not produce a valid satisfaction`);
|
|
716
|
+
return scriptSatisfaction;
|
|
717
|
+
}
|
|
718
|
+
getSequence() {
|
|
719
|
+
return __classPrivateFieldGet(this, _Output_instances, "m", _Output_getTimeConstraints).call(this)?.nSequence;
|
|
720
|
+
}
|
|
721
|
+
getLockTime() {
|
|
722
|
+
return __classPrivateFieldGet(this, _Output_instances, "m", _Output_getTimeConstraints).call(this)?.nLockTime;
|
|
723
|
+
}
|
|
724
|
+
getWitnessScript() {
|
|
725
|
+
return __classPrivateFieldGet(this, _Output_witnessScript, "f");
|
|
726
|
+
}
|
|
727
|
+
getRedeemScript() {
|
|
728
|
+
return __classPrivateFieldGet(this, _Output_redeemScript, "f");
|
|
729
|
+
}
|
|
730
|
+
getNetwork() {
|
|
731
|
+
return __classPrivateFieldGet(this, _Output_network, "f");
|
|
732
|
+
}
|
|
733
|
+
isSegwit() {
|
|
734
|
+
return __classPrivateFieldGet(this, _Output_isSegwit, "f");
|
|
735
|
+
}
|
|
736
|
+
isTaproot() {
|
|
737
|
+
return __classPrivateFieldGet(this, _Output_isTaproot, "f");
|
|
738
|
+
}
|
|
739
|
+
guessOutput() {
|
|
740
|
+
const scriptPubKey = this.getScriptPubKey();
|
|
741
|
+
let scriptType;
|
|
742
|
+
try {
|
|
743
|
+
const decoded = btc.OutScript.decode(scriptPubKey);
|
|
744
|
+
scriptType = decoded.type;
|
|
745
|
+
}
|
|
746
|
+
catch {
|
|
747
|
+
scriptType = undefined;
|
|
748
|
+
}
|
|
749
|
+
const isPKH = scriptType === 'pkh';
|
|
750
|
+
const isWPKH = scriptType === 'wpkh';
|
|
751
|
+
const isSH = scriptType === 'sh';
|
|
752
|
+
const isWSH = scriptType === 'wsh';
|
|
753
|
+
const isTR = scriptType === 'tr';
|
|
754
|
+
if ([isPKH, isWPKH, isSH, isWSH, isTR].filter(Boolean).length > 1)
|
|
755
|
+
throw new Error('Cannot have multiple output types.');
|
|
756
|
+
return { isPKH, isWPKH, isSH, isWSH, isTR };
|
|
757
|
+
}
|
|
758
|
+
inputWeight(isSegwitTx, signatures) {
|
|
759
|
+
if (this.isSegwit() && !isSegwitTx)
|
|
760
|
+
throw new Error(`a tx is segwit if at least one input is segwit`);
|
|
761
|
+
const expansion = this.expand().expandedExpression;
|
|
762
|
+
const { isPKH, isWPKH, isSH, isTR } = this.guessOutput();
|
|
763
|
+
const errorMsg = `Input type not implemented. Currently supported: pkh(KEY), wpkh(KEY), tr(KEY), \
|
|
764
|
+
sh(wpkh(KEY)), sh(wsh(MINISCRIPT)), sh(MINISCRIPT), wsh(MINISCRIPT), \
|
|
765
|
+
addr(PKH_ADDRESS), addr(WPKH_ADDRESS), addr(SH_WPKH_ADDRESS), addr(SINGLE_KEY_ADDRESS). \
|
|
766
|
+
expansion=${expansion}, isPKH=${isPKH}, isWPKH=${isWPKH}, isSH=${isSH}, isTR=${isTR}.`;
|
|
767
|
+
if (!expansion && !isPKH && !isWPKH && !isSH && !isTR)
|
|
768
|
+
throw new Error(errorMsg);
|
|
769
|
+
const firstSignature = signatures && typeof signatures[0] === 'object'
|
|
770
|
+
? signatures[0]
|
|
771
|
+
: 'DANGEROUSLY_USE_FAKE_SIGNATURES';
|
|
772
|
+
if (expansion ? expansion.startsWith('pkh(') : isPKH) {
|
|
773
|
+
return ((32 + 4 + 4 + 1 + signatureSize(firstSignature) + 34) * 4 +
|
|
774
|
+
(isSegwitTx ? 1 : 0));
|
|
775
|
+
}
|
|
776
|
+
else if (expansion ? expansion.startsWith('wpkh(') : isWPKH) {
|
|
777
|
+
if (!isSegwitTx)
|
|
778
|
+
throw new Error('Should be SegwitTx');
|
|
779
|
+
return 41 * 4 + (1 + signatureSize(firstSignature) + 34);
|
|
780
|
+
}
|
|
781
|
+
else if (expansion ? expansion.startsWith('sh(wpkh(') : isSH) {
|
|
782
|
+
if (!isSegwitTx)
|
|
783
|
+
throw new Error('Should be SegwitTx');
|
|
784
|
+
return 64 * 4 + (1 + signatureSize(firstSignature) + 34);
|
|
785
|
+
}
|
|
786
|
+
else if (expansion?.startsWith('sh(wsh(')) {
|
|
787
|
+
if (!isSegwitTx)
|
|
788
|
+
throw new Error('Should be SegwitTx');
|
|
789
|
+
const witnessScript = this.getWitnessScript();
|
|
790
|
+
if (!witnessScript)
|
|
791
|
+
throw new Error('sh(wsh) must provide witnessScript');
|
|
792
|
+
const scriptSatisfaction = this.getScriptSatisfaction(signatures || 'DANGEROUSLY_USE_FAKE_SIGNATURES');
|
|
793
|
+
// sh(wsh) input: push of the p2wsh redeemScript (OP_0 <32-byte SHA256(witnessScript)>)
|
|
794
|
+
const redeemScript = this.getRedeemScript();
|
|
795
|
+
if (!redeemScript)
|
|
796
|
+
throw new Error('sh(wsh) must have redeemScript');
|
|
797
|
+
const shInput = concatBytes(Uint8Array.from([redeemScript.length]), redeemScript);
|
|
798
|
+
// witness: satisfaction chunks decompiled from scriptSatisfaction, plus witnessScript
|
|
799
|
+
const witnessChunks = decompileScript(scriptSatisfaction);
|
|
800
|
+
if (!witnessChunks)
|
|
801
|
+
throw new Error('Could not decompile script satisfaction');
|
|
802
|
+
const witness = witnessChunks.map(chunk => typeof chunk === 'number' ? new Uint8Array(0) : chunk);
|
|
803
|
+
witness.push(witnessScript);
|
|
804
|
+
return 4 * (40 + varSliceSize(shInput)) + vectorSize(witness);
|
|
805
|
+
}
|
|
806
|
+
else if (expansion?.startsWith('sh(')) {
|
|
807
|
+
const redeemScript = this.getRedeemScript();
|
|
808
|
+
if (!redeemScript)
|
|
809
|
+
throw new Error('sh() must provide redeemScript');
|
|
810
|
+
const scriptSatisfaction = this.getScriptSatisfaction(signatures || 'DANGEROUSLY_USE_FAKE_SIGNATURES');
|
|
811
|
+
// sh input: scriptSatisfaction + push of redeemScript
|
|
812
|
+
const shInput = concatBytes(scriptSatisfaction, Uint8Array.from([
|
|
813
|
+
redeemScript.length > 75 ? 0x4c : redeemScript.length
|
|
814
|
+
]), ...(redeemScript.length > 75
|
|
815
|
+
? [Uint8Array.from([redeemScript.length])]
|
|
816
|
+
: []), redeemScript);
|
|
817
|
+
return 4 * (40 + varSliceSize(shInput)) + (isSegwitTx ? 1 : 0);
|
|
818
|
+
}
|
|
819
|
+
else if (expansion?.startsWith('wsh(')) {
|
|
820
|
+
const witnessScript = this.getWitnessScript();
|
|
821
|
+
if (!witnessScript)
|
|
822
|
+
throw new Error('wsh must provide witnessScript');
|
|
823
|
+
const scriptSatisfaction = this.getScriptSatisfaction(signatures || 'DANGEROUSLY_USE_FAKE_SIGNATURES');
|
|
824
|
+
// wsh: empty scriptSig, witness = satisfaction chunks + witnessScript
|
|
825
|
+
const witnessChunks = decompileScript(scriptSatisfaction);
|
|
826
|
+
if (!witnessChunks)
|
|
827
|
+
throw new Error('Could not decompile script satisfaction');
|
|
828
|
+
const witness = witnessChunks.map(chunk => typeof chunk === 'number' ? new Uint8Array(0) : chunk);
|
|
829
|
+
witness.push(witnessScript);
|
|
830
|
+
const emptyInput = new Uint8Array(0);
|
|
831
|
+
return 4 * (40 + varSliceSize(emptyInput)) + vectorSize(witness);
|
|
832
|
+
}
|
|
833
|
+
else if (isTR && (!expansion || expansion === 'tr(@0)')) {
|
|
834
|
+
if (!isSegwitTx)
|
|
835
|
+
throw new Error('Should be SegwitTx');
|
|
836
|
+
return 41 * 4 + (1 + 65);
|
|
837
|
+
}
|
|
838
|
+
else {
|
|
839
|
+
throw new Error(errorMsg);
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
outputWeight() {
|
|
843
|
+
const { isPKH, isWPKH, isSH, isWSH, isTR } = this.guessOutput();
|
|
844
|
+
const errorMsg = `Output type not implemented. Currently supported: pkh=${isPKH}, wpkh=${isWPKH}, tr=${isTR}, sh=${isSH} and wsh=${isWSH}.`;
|
|
845
|
+
if (isPKH) {
|
|
846
|
+
return 34 * 4;
|
|
847
|
+
}
|
|
848
|
+
else if (isWPKH) {
|
|
849
|
+
return 31 * 4;
|
|
850
|
+
}
|
|
851
|
+
else if (isSH) {
|
|
852
|
+
return 32 * 4;
|
|
853
|
+
}
|
|
854
|
+
else if (isWSH) {
|
|
855
|
+
return 43 * 4;
|
|
856
|
+
}
|
|
857
|
+
else if (isTR) {
|
|
858
|
+
return 43 * 4;
|
|
859
|
+
}
|
|
860
|
+
else {
|
|
861
|
+
throw new Error(errorMsg);
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
updatePsbtAsInput({ psbt, txHex, txId, value, vout, rbf = true }) {
|
|
865
|
+
if (txHex === undefined) {
|
|
866
|
+
console.warn(`Warning: missing txHex may allow fee attacks`);
|
|
867
|
+
}
|
|
868
|
+
const isSegwit = this.isSegwit();
|
|
869
|
+
if (isSegwit === undefined) {
|
|
870
|
+
throw new Error(`Error: could not determine whether this is a segwit descriptor`);
|
|
871
|
+
}
|
|
872
|
+
const isTaproot = this.isTaproot();
|
|
873
|
+
if (isTaproot === undefined) {
|
|
874
|
+
throw new Error(`Error: could not determine whether this is a taproot descriptor`);
|
|
875
|
+
}
|
|
876
|
+
const index = updatePsbt({
|
|
877
|
+
psbt,
|
|
878
|
+
vout,
|
|
879
|
+
...(txHex !== undefined ? { txHex } : {}),
|
|
880
|
+
...(txId !== undefined ? { txId } : {}),
|
|
881
|
+
...(value !== undefined ? { value } : {}),
|
|
882
|
+
...(isTaproot
|
|
883
|
+
? {
|
|
884
|
+
tapInternalKey: this.getPayment()['tapInternalKey']
|
|
885
|
+
}
|
|
886
|
+
: {}),
|
|
887
|
+
sequence: this.getSequence(),
|
|
888
|
+
locktime: this.getLockTime(),
|
|
889
|
+
keysInfo: __classPrivateFieldGet(this, _Output_expansionMap, "f") ? Object.values(__classPrivateFieldGet(this, _Output_expansionMap, "f")) : [],
|
|
890
|
+
scriptPubKey: this.getScriptPubKey(),
|
|
891
|
+
isSegwit,
|
|
892
|
+
witnessScript: this.getWitnessScript(),
|
|
893
|
+
redeemScript: this.getRedeemScript(),
|
|
894
|
+
rbf
|
|
895
|
+
});
|
|
896
|
+
const finalizer = ({ psbt, validate = true }) => this.finalizePsbtInput({ index, psbt, validate });
|
|
897
|
+
return finalizer;
|
|
898
|
+
}
|
|
899
|
+
updatePsbtAsOutput({ psbt, value }) {
|
|
900
|
+
psbt.addOutput({
|
|
901
|
+
script: this.getScriptPubKey(),
|
|
902
|
+
amount: typeof value === 'bigint' ? value : BigInt(value)
|
|
903
|
+
});
|
|
904
|
+
}
|
|
905
|
+
finalizePsbtInput({ index, psbt, validate = true }) {
|
|
906
|
+
// scure's Transaction handles signature validation internally during finalization.
|
|
907
|
+
// The validate parameter is kept for API compatibility but signature validation
|
|
908
|
+
// is delegated to scure's finalizeIdx.
|
|
909
|
+
void validate;
|
|
910
|
+
__classPrivateFieldGet(this, _Output_instances, "m", _Output_assertPsbtInput).call(this, { index, psbt });
|
|
911
|
+
if (!__classPrivateFieldGet(this, _Output_miniscript, "f")) {
|
|
912
|
+
psbt.finalizeIdx(index);
|
|
913
|
+
}
|
|
914
|
+
else {
|
|
915
|
+
// For miniscript, we need custom finalization:
|
|
916
|
+
// 1. Extract partialSig from the input
|
|
917
|
+
const input = psbt.getInput(index);
|
|
918
|
+
const partialSigs = input.partialSig;
|
|
919
|
+
if (!partialSigs || partialSigs.length === 0)
|
|
920
|
+
throw new Error(`Error: cannot finalize without signatures`);
|
|
921
|
+
// Convert scure's [Uint8Array, Uint8Array][] to PartialSig[]
|
|
922
|
+
const signatures = partialSigs.map(([pubkey, signature]) => ({
|
|
923
|
+
pubkey,
|
|
924
|
+
signature
|
|
925
|
+
}));
|
|
926
|
+
// 2. Compute script satisfaction
|
|
927
|
+
const scriptSatisfaction = this.getScriptSatisfaction(signatures);
|
|
928
|
+
// 3. Compute final scripts using the locking script
|
|
929
|
+
const isSegwit = this.isSegwit() ?? false;
|
|
930
|
+
const redeemScript = this.getRedeemScript();
|
|
931
|
+
const isP2SH = redeemScript !== undefined;
|
|
932
|
+
const lockingScript = this.getWitnessScript() ?? redeemScript;
|
|
933
|
+
if (!lockingScript)
|
|
934
|
+
throw new Error(`Error: cannot determine locking script for miniscript finalization`);
|
|
935
|
+
const { finalScriptSig, finalScriptWitness } = computeFinalScripts(scriptSatisfaction, lockingScript, isSegwit, isP2SH, redeemScript);
|
|
936
|
+
// 4. Set final scripts on the input
|
|
937
|
+
const updateFields = {};
|
|
938
|
+
if (finalScriptSig)
|
|
939
|
+
updateFields['finalScriptSig'] = finalScriptSig;
|
|
940
|
+
if (finalScriptWitness)
|
|
941
|
+
updateFields['finalScriptWitness'] = finalScriptWitness;
|
|
942
|
+
psbt.updateInput(index, updateFields, true);
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
expand() {
|
|
946
|
+
return {
|
|
947
|
+
...(__classPrivateFieldGet(this, _Output_expandedExpression, "f") !== undefined
|
|
948
|
+
? { expandedExpression: __classPrivateFieldGet(this, _Output_expandedExpression, "f") }
|
|
949
|
+
: {}),
|
|
950
|
+
...(__classPrivateFieldGet(this, _Output_miniscript, "f") !== undefined
|
|
951
|
+
? { miniscript: __classPrivateFieldGet(this, _Output_miniscript, "f") }
|
|
952
|
+
: {}),
|
|
953
|
+
...(__classPrivateFieldGet(this, _Output_expandedMiniscript, "f") !== undefined
|
|
954
|
+
? { expandedMiniscript: __classPrivateFieldGet(this, _Output_expandedMiniscript, "f") }
|
|
955
|
+
: {}),
|
|
956
|
+
...(__classPrivateFieldGet(this, _Output_expansionMap, "f") !== undefined
|
|
957
|
+
? { expansionMap: __classPrivateFieldGet(this, _Output_expansionMap, "f") }
|
|
958
|
+
: {})
|
|
959
|
+
};
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
_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() {
|
|
963
|
+
const miniscript = __classPrivateFieldGet(this, _Output_miniscript, "f");
|
|
964
|
+
const preimages = __classPrivateFieldGet(this, _Output_preimages, "f");
|
|
965
|
+
const expandedMiniscript = __classPrivateFieldGet(this, _Output_expandedMiniscript, "f");
|
|
966
|
+
const expansionMap = __classPrivateFieldGet(this, _Output_expansionMap, "f");
|
|
967
|
+
const signersPubKeys = __classPrivateFieldGet(this, _Output_signersPubKeys, "f");
|
|
968
|
+
if (miniscript) {
|
|
969
|
+
if (expandedMiniscript === undefined || expansionMap === undefined)
|
|
970
|
+
throw new Error(`Error: cannot get time constraints from not expanded miniscript ${miniscript}`);
|
|
971
|
+
const fakeSignatures = signersPubKeys.map(pubkey => ({
|
|
972
|
+
pubkey,
|
|
973
|
+
signature: new Uint8Array(72)
|
|
974
|
+
}));
|
|
975
|
+
const { nLockTime, nSequence } = satisfyMiniscript({
|
|
976
|
+
expandedMiniscript,
|
|
977
|
+
expansionMap,
|
|
978
|
+
signatures: fakeSignatures,
|
|
979
|
+
preimages
|
|
980
|
+
});
|
|
981
|
+
return { nLockTime, nSequence };
|
|
982
|
+
}
|
|
983
|
+
else
|
|
984
|
+
return undefined;
|
|
985
|
+
}, _Output_assertPsbtInput = function _Output_assertPsbtInput({ psbt, index }) {
|
|
986
|
+
const input = psbt.getInput(index);
|
|
987
|
+
if (!input)
|
|
988
|
+
throw new Error(`Error: invalid input`);
|
|
989
|
+
const inputSequence = input.sequence;
|
|
990
|
+
const vout = input.index;
|
|
991
|
+
let scriptPubKey;
|
|
992
|
+
if (input.witnessUtxo) {
|
|
993
|
+
scriptPubKey = input.witnessUtxo.script;
|
|
994
|
+
}
|
|
995
|
+
else {
|
|
996
|
+
if (!input.nonWitnessUtxo)
|
|
997
|
+
throw new Error(`Error: input should have either witnessUtxo or nonWitnessUtxo`);
|
|
998
|
+
// nonWitnessUtxo is stored as a parsed tx struct by scure
|
|
999
|
+
const parsedTx = input.nonWitnessUtxo;
|
|
1000
|
+
const out = parsedTx.outputs[vout];
|
|
1001
|
+
if (!out || !out.script)
|
|
1002
|
+
throw new Error(`Error: utxo should exist`);
|
|
1003
|
+
scriptPubKey = out.script;
|
|
1004
|
+
}
|
|
1005
|
+
const locktime = this.getLockTime() || 0;
|
|
1006
|
+
const sequence = this.getSequence();
|
|
1007
|
+
const sequenceNoRBF = sequence !== undefined
|
|
1008
|
+
? sequence
|
|
1009
|
+
: locktime === 0
|
|
1010
|
+
? 0xffffffff
|
|
1011
|
+
: 0xfffffffe;
|
|
1012
|
+
const sequenceRBF = sequence !== undefined ? sequence : 0xfffffffd;
|
|
1013
|
+
const eqBufs = (buf1, buf2) => {
|
|
1014
|
+
if (buf1 && buf2)
|
|
1015
|
+
return equalBytes(buf1, buf2);
|
|
1016
|
+
return buf1 === undefined && buf2 === undefined;
|
|
1017
|
+
};
|
|
1018
|
+
if (!equalBytes(scriptPubKey, this.getScriptPubKey()) ||
|
|
1019
|
+
(sequenceRBF !== inputSequence && sequenceNoRBF !== inputSequence) ||
|
|
1020
|
+
locktime !== psbt.lockTime ||
|
|
1021
|
+
!eqBufs(this.getWitnessScript(), input.witnessScript) ||
|
|
1022
|
+
!eqBufs(this.getRedeemScript(), input.redeemScript)) {
|
|
1023
|
+
throw new Error(`Error: cannot finalize psbt index ${index} since it does not correspond to this descriptor`);
|
|
1024
|
+
}
|
|
1025
|
+
};
|
|
1026
|
+
return {
|
|
1027
|
+
Output,
|
|
1028
|
+
parseKeyExpression,
|
|
1029
|
+
expand,
|
|
1030
|
+
ECPair,
|
|
1031
|
+
BIP32
|
|
1032
|
+
};
|
|
1033
|
+
}
|