@scure/btc-signer 2.2.0 → 2.4.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 +196 -37
- package/index.d.ts +4 -4
- package/index.js +3 -4
- package/musig2.d.ts +28 -9
- package/musig2.js +46 -18
- package/net.d.ts +355 -0
- package/net.js +880 -0
- package/p2p.d.ts +0 -1
- package/p2p.js +23 -7
- package/package.json +18 -21
- package/payment.d.ts +18 -11
- package/payment.js +203 -58
- package/psbt.d.ts +680 -10
- package/psbt.js +204 -43
- package/script.d.ts +1 -2
- package/script.js +71 -59
- package/src/_type_test.ts +83 -0
- package/src/index.ts +4 -2
- package/src/musig2.ts +71 -19
- package/src/net.ts +1111 -0
- package/src/p2p.ts +22 -6
- package/src/payment.ts +233 -70
- package/src/psbt.ts +228 -39
- package/src/script.ts +57 -35
- package/src/transaction.ts +869 -168
- package/src/utils.ts +92 -4
- package/src/utxo.ts +223 -113
- package/transaction.d.ts +40 -7
- package/transaction.js +745 -151
- package/utils.d.ts +45 -2
- package/utils.js +80 -5
- package/utxo.d.ts +192 -2
- package/utxo.js +200 -99
- package/index.d.ts.map +0 -1
- package/index.js.map +0 -1
- package/musig2.d.ts.map +0 -1
- package/musig2.js.map +0 -1
- package/p2p.d.ts.map +0 -1
- package/p2p.js.map +0 -1
- package/payment.d.ts.map +0 -1
- package/payment.js.map +0 -1
- package/psbt.d.ts.map +0 -1
- package/psbt.js.map +0 -1
- package/script.d.ts.map +0 -1
- package/script.js.map +0 -1
- package/transaction.d.ts.map +0 -1
- package/transaction.js.map +0 -1
- package/utils.d.ts.map +0 -1
- package/utils.js.map +0 -1
- package/utxo.d.ts.map +0 -1
- package/utxo.js.map +0 -1
package/payment.js
CHANGED
|
@@ -6,10 +6,16 @@ import { TaprootControlBlock } from "./psbt.js";
|
|
|
6
6
|
import { MAX_SCRIPT_BYTE_LENGTH, OpToNum, Script, VarBytes } from "./script.js";
|
|
7
7
|
import * as u from "./utils.js";
|
|
8
8
|
import { NETWORK } from "./utils.js";
|
|
9
|
+
// Pay to Anchor (P2A)
|
|
10
|
+
// BIP433 Pay-to-Anchor witness program bytes; the scriptPubKey is `OP_1 <0x4e73>`.
|
|
11
|
+
const P2A_PROGRAM = /* @__PURE__ */ Uint8Array.from([0x4e, 0x73]);
|
|
9
12
|
const OutP2A = {
|
|
10
13
|
encode(from) {
|
|
11
14
|
// BIP433 defines P2A as the exact OP_1 <0x4e73> scriptPubKey.
|
|
12
|
-
if (from.length !== 2 ||
|
|
15
|
+
if (from.length !== 2 ||
|
|
16
|
+
from[0] !== 1 ||
|
|
17
|
+
!u.isBytes(from[1]) ||
|
|
18
|
+
!u.equalBytes(from[1], P2A_PROGRAM))
|
|
13
19
|
return;
|
|
14
20
|
return { type: 'p2a', script: Script.encode(from) };
|
|
15
21
|
},
|
|
@@ -18,7 +24,7 @@ const OutP2A = {
|
|
|
18
24
|
return;
|
|
19
25
|
// The decoded object keeps `script` for caller convenience, but the `p2a`
|
|
20
26
|
// tag always canonicalizes back to the fixed BIP433 script.
|
|
21
|
-
return [1,
|
|
27
|
+
return [1, Uint8Array.from(P2A_PROGRAM)];
|
|
22
28
|
},
|
|
23
29
|
};
|
|
24
30
|
function isValidPubkey(pub, type) {
|
|
@@ -54,8 +60,10 @@ const OutPKH = {
|
|
|
54
60
|
encode(from) {
|
|
55
61
|
if (from.length !== 5 || from[0] !== 'DUP' || from[1] !== 'HASH160' || !u.isBytes(from[2]))
|
|
56
62
|
return;
|
|
57
|
-
//
|
|
58
|
-
//
|
|
63
|
+
// Require the exact 20-byte HASH160 here so near-miss scripts fall through
|
|
64
|
+
// to OutUnknown instead of throwing in the OutScript validator on decode.
|
|
65
|
+
if (from[2].length !== 20)
|
|
66
|
+
return;
|
|
59
67
|
if (from[3] !== 'EQUALVERIFY' || from[4] !== 'CHECKSIG')
|
|
60
68
|
return;
|
|
61
69
|
return { type: 'pkh', hash: from[2] };
|
|
@@ -70,8 +78,10 @@ const OutSH = {
|
|
|
70
78
|
encode(from) {
|
|
71
79
|
if (from.length !== 3 || from[0] !== 'HASH160' || !u.isBytes(from[1]) || from[2] !== 'EQUAL')
|
|
72
80
|
return;
|
|
73
|
-
//
|
|
74
|
-
//
|
|
81
|
+
// Require the exact 20-byte HASH160 here so near-miss scripts fall through
|
|
82
|
+
// to OutUnknown instead of throwing in the OutScript validator on decode.
|
|
83
|
+
if (from[1].length !== 20)
|
|
84
|
+
return;
|
|
75
85
|
return { type: 'sh', hash: from[1] };
|
|
76
86
|
},
|
|
77
87
|
// OutScript validates `sh.hash` before this branch emits the canonical
|
|
@@ -119,11 +129,15 @@ const OutMS = {
|
|
|
119
129
|
const pubkeys = from.slice(1, -2);
|
|
120
130
|
if (n !== pubkeys.length)
|
|
121
131
|
return;
|
|
132
|
+
// Require valid ECDSA pubkeys and `0 < m <= n` here so near-miss
|
|
133
|
+
// CHECKMULTISIG scripts (garbage keys, degenerate 0-of-0) fall through to
|
|
134
|
+
// OutUnknown instead of throwing in the OutScript validator on decode.
|
|
135
|
+
// Script.decode only yields 0..16 for opcode numbers, so n <= 16 holds.
|
|
122
136
|
for (const pub of pubkeys)
|
|
123
|
-
if (!u.isBytes(pub))
|
|
137
|
+
if (!u.isBytes(pub) || !isValidPubkey(pub, u.PubT.ecdsa))
|
|
124
138
|
return;
|
|
125
|
-
|
|
126
|
-
|
|
139
|
+
if (!Number.isSafeInteger(m) || m < 1 || m > n)
|
|
140
|
+
return;
|
|
127
141
|
// We don't need n here because it is the same as pubkeys.length.
|
|
128
142
|
return { type: 'ms', m, pubkeys: pubkeys };
|
|
129
143
|
},
|
|
@@ -143,6 +157,11 @@ const OutTR = {
|
|
|
143
157
|
// other OP_1 program lengths remain reserved future witness programs and should fall through.
|
|
144
158
|
if (from.length !== 2 || from[0] !== 1 || !u.isBytes(from[1]) || from[1].length !== 32)
|
|
145
159
|
return;
|
|
160
|
+
// A 32-byte v1 program with an off-curve x coordinate is a fundable but
|
|
161
|
+
// taproot-unspendable output; classify it as unknown instead of throwing
|
|
162
|
+
// in the OutScript validator on decode.
|
|
163
|
+
if (!isValidPubkey(from[1], u.PubT.schnorr))
|
|
164
|
+
return;
|
|
146
165
|
return { type: 'tr', pubkey: from[1] };
|
|
147
166
|
},
|
|
148
167
|
// OutScript validates `tr.pubkey` before this branch emits the canonical
|
|
@@ -172,7 +191,7 @@ const OutTRNS = {
|
|
|
172
191
|
// BIP342 "Using a k-of-k script for every combination" documents the shape
|
|
173
192
|
// `<pubkey_1> CHECKSIGVERIFY ... <pubkey_n> CHECKSIG`; this matcher only
|
|
174
193
|
// classifies that embedded-pubkey form, so bare CHECKSIG stays unknown.
|
|
175
|
-
if (!pubkeys.length)
|
|
194
|
+
if (!pubkeys.length || pubkeys.length > 999)
|
|
176
195
|
return;
|
|
177
196
|
return { type: 'tr_ns', pubkeys };
|
|
178
197
|
},
|
|
@@ -206,10 +225,15 @@ const OutTRMS = {
|
|
|
206
225
|
return;
|
|
207
226
|
continue;
|
|
208
227
|
}
|
|
209
|
-
|
|
228
|
+
// Require actual Schnorr pubkeys here (same as tr_ns) so near-miss
|
|
229
|
+
// CHECKSIGADD scripts fall through to OutUnknown instead of throwing
|
|
230
|
+
// in the OutScript validator on decode.
|
|
231
|
+
if (!u.isBytes(elm) || !isValidPubkey(elm, u.PubT.schnorr))
|
|
210
232
|
return;
|
|
211
233
|
pubkeys.push(elm);
|
|
212
234
|
}
|
|
235
|
+
if (!Number.isSafeInteger(m) || m < 1 || m > pubkeys.length || pubkeys.length > 999)
|
|
236
|
+
return;
|
|
213
237
|
return { type: 'tr_ms', pubkeys, m };
|
|
214
238
|
},
|
|
215
239
|
decode: (to) => {
|
|
@@ -298,20 +322,40 @@ export const OutScript = /* @__PURE__ */ (() => Object.freeze(P.validate(_OutScr
|
|
|
298
322
|
throw new Error('OutScript/multisig: invalid params');
|
|
299
323
|
}
|
|
300
324
|
if (i.type === 'tr_ns' || i.type === 'tr_ms') {
|
|
325
|
+
const n = i.pubkeys.length;
|
|
326
|
+
// BIP342 keeps the 1,000-element stack limit. Both supported tapscript multisig forms
|
|
327
|
+
// start with n signatures and then push a pubkey, so n must stay at or below 999.
|
|
328
|
+
if (n > 999)
|
|
329
|
+
throw new Error(`OutScript/${i.type}: invalid params`);
|
|
301
330
|
for (const p of i.pubkeys)
|
|
302
331
|
if (!isValidPubkey(p, u.PubT.schnorr))
|
|
303
332
|
throw new Error(`OutScript/${i.type}: wrong pubkey`);
|
|
304
333
|
}
|
|
305
334
|
if (i.type === 'tr_ms') {
|
|
306
335
|
const n = i.pubkeys.length;
|
|
307
|
-
// BIP 342 keeps the 1000-element stack limit. This CHECKSIG/CHECKSIGADD form
|
|
308
|
-
// momentarily has n witness items plus one pushed pubkey on the stack, so n must stay <= 999.
|
|
309
336
|
anumber(i.m, 'm');
|
|
310
|
-
if (i.m <= 0 ||
|
|
337
|
+
if (i.m <= 0 || i.m > n)
|
|
311
338
|
throw new Error('OutScript/tr_ms: invalid params');
|
|
312
339
|
}
|
|
313
340
|
return i;
|
|
314
341
|
})))();
|
|
342
|
+
// Internal raw-aware boundary for actual scriptPubKeys and P2SH-nested witness programs. Generic
|
|
343
|
+
// OutScript remains semantic because witnessScript and tapscript children may use the same opcodes
|
|
344
|
+
// without claiming BIP141 witness-program framing.
|
|
345
|
+
export const _WitnessOutScript = /* @__PURE__ */ (() => Object.freeze(P.wrap({
|
|
346
|
+
encodeStream: (w, value) => OutScript.encodeStream(w, value),
|
|
347
|
+
decodeStream: (r) => {
|
|
348
|
+
const raw = r.bytes(r.leftBytes);
|
|
349
|
+
const out = OutScript.decode(raw);
|
|
350
|
+
// Script.decode normalizes push opcodes, but BIP141 recognition requires the exact direct
|
|
351
|
+
// push. Reject only when this explicit outer-program boundary is selected by the caller.
|
|
352
|
+
if (out &&
|
|
353
|
+
(out.type === 'p2a' || out.type === 'wpkh' || out.type === 'wsh' || out.type === 'tr') &&
|
|
354
|
+
!u.equalBytes(raw, OutScript.encode(out)))
|
|
355
|
+
throw new Error('OutScript: non-canonical witness program');
|
|
356
|
+
return out;
|
|
357
|
+
},
|
|
358
|
+
})))();
|
|
315
359
|
// Basic sanity check for scripts
|
|
316
360
|
function checkWSH(s, witnessScript) {
|
|
317
361
|
if (!u.equalBytes(s.hash, u.sha256(witnessScript)))
|
|
@@ -346,7 +390,7 @@ export function checkScript(script, redeemScript, witnessScript) {
|
|
|
346
390
|
let hasWsh = false;
|
|
347
391
|
let r = undefined;
|
|
348
392
|
if (script) {
|
|
349
|
-
const s =
|
|
393
|
+
const s = _WitnessOutScript.decode(script);
|
|
350
394
|
// BIP174 Data Signers Check For bullets: provided redeemScript must match
|
|
351
395
|
// the scriptPubKey, and provided witnessScript must match the scriptPubKey
|
|
352
396
|
// or redeemScript instead of being silently ignored as stray metadata.
|
|
@@ -358,7 +402,7 @@ export function checkScript(script, redeemScript, witnessScript) {
|
|
|
358
402
|
throw new Error('checkScript: redeemScript without P2SH');
|
|
359
403
|
if (!u.equalBytes(s.hash, u.hash160(redeemScript)))
|
|
360
404
|
throw new Error('checkScript: sh wrong redeemScript hash');
|
|
361
|
-
r =
|
|
405
|
+
r = _WitnessOutScript.decode(redeemScript);
|
|
362
406
|
if (r?.type === 'tr' || r?.type === 'tr_ns' || r?.type === 'tr_ms')
|
|
363
407
|
throw new Error(`checkScript: P2${r.type} cannot be wrapped in P2SH`);
|
|
364
408
|
// Not sure if this unspendable, but we cannot represent this via PSBT
|
|
@@ -373,7 +417,7 @@ export function checkScript(script, redeemScript, witnessScript) {
|
|
|
373
417
|
}
|
|
374
418
|
if (redeemScript) {
|
|
375
419
|
if (r === undefined)
|
|
376
|
-
r =
|
|
420
|
+
r = _WitnessOutScript.decode(redeemScript);
|
|
377
421
|
if (r?.type === 'wsh') {
|
|
378
422
|
hasWsh = true;
|
|
379
423
|
if (witnessScript)
|
|
@@ -383,12 +427,21 @@ export function checkScript(script, redeemScript, witnessScript) {
|
|
|
383
427
|
if (witnessScript && !hasWsh)
|
|
384
428
|
throw new Error('checkScript: witnessScript without P2WSH');
|
|
385
429
|
}
|
|
386
|
-
function uniqPubkey(pubkeys) {
|
|
430
|
+
function uniqPubkey(pubkeys, type) {
|
|
387
431
|
const map = {};
|
|
388
432
|
for (const pub of pubkeys) {
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
433
|
+
u.validatePubkey(pub, type);
|
|
434
|
+
let normalized = pub;
|
|
435
|
+
if (type === u.PubT.ecdsa && pub.length === 65) {
|
|
436
|
+
// Compressed and uncompressed SEC1 encodings can represent the same curve point. Normalize
|
|
437
|
+
// valid uncompressed keys before comparing so one signer cannot occupy multiple threshold
|
|
438
|
+
// slots merely by changing the serialization. `allowSamePubkeys` remains the explicit
|
|
439
|
+
// compatibility escape hatch for callers that intentionally construct duplicate-key scripts.
|
|
440
|
+
normalized = new Uint8Array(33);
|
|
441
|
+
normalized[0] = 2 | (pub[64] & 1);
|
|
442
|
+
normalized.set(pub.subarray(1, 33), 1);
|
|
443
|
+
}
|
|
444
|
+
const key = hex.encode(normalized);
|
|
392
445
|
if (map[key])
|
|
393
446
|
throw new Error(`Multisig: non-uniq pubkey: ${pubkeys.map(hex.encode)}`);
|
|
394
447
|
map[key] = true;
|
|
@@ -442,10 +495,23 @@ export const p2pkh = (publicKey, network = NETWORK) => {
|
|
|
442
495
|
hash,
|
|
443
496
|
};
|
|
444
497
|
};
|
|
498
|
+
const checkCanonicalScript = (script, name, allowNonCanonicalScript) => {
|
|
499
|
+
if (allowNonCanonicalScript)
|
|
500
|
+
return;
|
|
501
|
+
const decoded = Script.decode(script);
|
|
502
|
+
const nonMinimalNumber = decoded.some((op) => u.isBytes(op) && op.length === 1 && ((1 <= op[0] && op[0] <= 16) || op[0] === 0x81));
|
|
503
|
+
// Core's default MINIMALDATA policy rejects these spends from its mempool, so address-producing
|
|
504
|
+
// helpers require an explicit opt-in even though the original bytes remain consensus-valid.
|
|
505
|
+
if (nonMinimalNumber || !u.equalBytes(script, Script.encode(decoded))) {
|
|
506
|
+
const wrapper = name === 'redeemScript' ? 'P2SH' : 'P2WSH';
|
|
507
|
+
throw new Error(`${wrapper}: non-canonical ${name}`);
|
|
508
|
+
}
|
|
509
|
+
};
|
|
445
510
|
/**
|
|
446
511
|
* Wraps a child script inside P2SH.
|
|
447
512
|
* @param child - child payment descriptor to wrap
|
|
448
513
|
* @param network - address network parameters
|
|
514
|
+
* @param allowNonCanonicalScript - whether to create an address for a non-minimal child script
|
|
449
515
|
* @returns P2SH descriptor preserving witness metadata when present.
|
|
450
516
|
* @throws If the wrapped script combination is invalid or unsupported. {@link Error}
|
|
451
517
|
* @example
|
|
@@ -456,7 +522,8 @@ export const p2pkh = (publicKey, network = NETWORK) => {
|
|
|
456
522
|
* p2sh(p2wsh(p2pk(hex.decode('0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798'))));
|
|
457
523
|
* ```
|
|
458
524
|
*/
|
|
459
|
-
export const p2sh = (child, network = NETWORK) => {
|
|
525
|
+
export const p2sh = (child, network = NETWORK, allowNonCanonicalScript = false) => {
|
|
526
|
+
u.validateObject(child, {}, {}, 'child');
|
|
460
527
|
// It is already tested inside noble-hashes and checkScript
|
|
461
528
|
// BIP16 redeemScripts are pushed by scriptSig, so anything over the 520-byte pushed-element
|
|
462
529
|
// limit would be fundable by HASH160 but unspendable once wrapped in P2SH.
|
|
@@ -466,6 +533,7 @@ export const p2sh = (child, network = NETWORK) => {
|
|
|
466
533
|
throw new Error(`Wrong script: ${typeof c.script}, expected Uint8Array`);
|
|
467
534
|
if (cs.length > MAX_SCRIPT_BYTE_LENGTH)
|
|
468
535
|
throw new Error(`P2SH: redeemScript exceeds ${MAX_SCRIPT_BYTE_LENGTH}-byte push limit: len=${cs.length}`);
|
|
536
|
+
checkCanonicalScript(cs, 'redeemScript', allowNonCanonicalScript);
|
|
469
537
|
const hash = u.hash160(cs);
|
|
470
538
|
const out = { type: 'sh', hash };
|
|
471
539
|
const script = OutScript.encode(out);
|
|
@@ -495,6 +563,7 @@ export const p2sh = (child, network = NETWORK) => {
|
|
|
495
563
|
* Wraps a child script inside native SegWit P2WSH.
|
|
496
564
|
* @param child - child payment descriptor to wrap
|
|
497
565
|
* @param network - address network parameters
|
|
566
|
+
* @param allowNonCanonicalScript - whether to create an address for a non-minimal child script
|
|
498
567
|
* @returns P2WSH descriptor.
|
|
499
568
|
* @throws If the wrapped script combination is invalid or unsupported. {@link Error}
|
|
500
569
|
* @example
|
|
@@ -505,7 +574,8 @@ export const p2sh = (child, network = NETWORK) => {
|
|
|
505
574
|
* p2wsh(p2pk(hex.decode('0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798')));
|
|
506
575
|
* ```
|
|
507
576
|
*/
|
|
508
|
-
export const p2wsh = (child, network = NETWORK) => {
|
|
577
|
+
export const p2wsh = (child, network = NETWORK, allowNonCanonicalScript = false) => {
|
|
578
|
+
u.validateObject(child, {}, {}, 'child');
|
|
509
579
|
const cs = child.script;
|
|
510
580
|
if (!u.isBytes(cs))
|
|
511
581
|
throw new Error(`Wrong script: ${typeof cs}, expected Uint8Array`);
|
|
@@ -513,6 +583,7 @@ export const p2wsh = (child, network = NETWORK) => {
|
|
|
513
583
|
// and that witnessScript is limited to 10,000 bytes, so larger wrapped scripts must reject.
|
|
514
584
|
if (cs.length > 10000)
|
|
515
585
|
throw new Error('P2WSH: witnessScript exceeds 10,000 bytes');
|
|
586
|
+
checkCanonicalScript(cs, 'witnessScript', allowNonCanonicalScript);
|
|
516
587
|
const hash = u.sha256(cs);
|
|
517
588
|
const script = OutScript.encode({ type: 'wsh', hash });
|
|
518
589
|
checkScript(script, undefined, cs);
|
|
@@ -571,7 +642,7 @@ export const p2ms = (m, pubkeys, allowSamePubkeys = false) => {
|
|
|
571
642
|
// BIP 11 only standardized bare multisig up to 3 keys; this helper still permits up to 16
|
|
572
643
|
// because the same script shape is commonly wrapped by p2sh()/p2wsh() instead of used bare.
|
|
573
644
|
if (!allowSamePubkeys)
|
|
574
|
-
uniqPubkey(pubkeys);
|
|
645
|
+
uniqPubkey(pubkeys, u.PubT.ecdsa);
|
|
575
646
|
return {
|
|
576
647
|
type: 'ms',
|
|
577
648
|
script: OutScript.encode({ type: 'ms', pubkeys, m }),
|
|
@@ -584,9 +655,17 @@ function checkTaprootScript(script, internalPubKey, allowUnknownOutputs = false,
|
|
|
584
655
|
// disable custom. All custom scripts for taproot should have prefix 'tr_'
|
|
585
656
|
if (customScripts) {
|
|
586
657
|
const cs = P.apply(Script, P.coders.match(customScripts));
|
|
587
|
-
|
|
658
|
+
let c;
|
|
659
|
+
// match() throws when no custom coder matches; treat that as "not a custom
|
|
660
|
+
// script" so the allowUnknownOutputs escape below stays reachable.
|
|
661
|
+
try {
|
|
662
|
+
c = cs.decode(script);
|
|
663
|
+
}
|
|
664
|
+
catch (e) {
|
|
665
|
+
c = undefined;
|
|
666
|
+
}
|
|
588
667
|
if (c !== undefined) {
|
|
589
|
-
if (
|
|
668
|
+
if (!u.astring(c.type, 'c.type').startsWith('tr_'))
|
|
590
669
|
throw new Error(`P2TR: invalid custom type=${c.type}`);
|
|
591
670
|
return;
|
|
592
671
|
}
|
|
@@ -599,7 +678,7 @@ function checkTaprootScript(script, internalPubKey, allowUnknownOutputs = false,
|
|
|
599
678
|
const outms = out;
|
|
600
679
|
if (!allowUnknownOutputs && outms.pubkeys) {
|
|
601
680
|
for (const p of outms.pubkeys) {
|
|
602
|
-
if (u.equalBytes(p, u.
|
|
681
|
+
if (u.equalBytes(p, u.taprootNumsKey()))
|
|
603
682
|
throw new Error('Unspendable taproot key in leaf script');
|
|
604
683
|
// It's likely a mistake at this point:
|
|
605
684
|
// 1. p2tr(A, p2tr_ns(2, [A, B])) == p2tr(A, p2tr_pk(B)) (A or B key)
|
|
@@ -632,6 +711,15 @@ function checkTaprootScript(script, internalPubKey, allowUnknownOutputs = false,
|
|
|
632
711
|
* ```
|
|
633
712
|
*/
|
|
634
713
|
export function taprootListToTree(taprootList) {
|
|
714
|
+
u.aarray(taprootList, 'taprootList', (leaf, title) => {
|
|
715
|
+
// p2tr reduces non-binary trees through this helper, so nested branch arrays are valid here.
|
|
716
|
+
if (Array.isArray(leaf))
|
|
717
|
+
return;
|
|
718
|
+
u.validateObject(leaf, {}, {}, title);
|
|
719
|
+
// This helper only arranges weighted tree nodes; p2tr validates leaf scripts while hashing.
|
|
720
|
+
if (leaf.weight !== undefined)
|
|
721
|
+
anumber(leaf.weight, title + '.weight');
|
|
722
|
+
});
|
|
635
723
|
// Empty flat lists cannot represent a taproot script tree; omit the tree entirely for
|
|
636
724
|
// key-path-only outputs instead of passing [] here, otherwise this helper would return
|
|
637
725
|
// undefined and downstream taproot tree walkers would fail much later on a non-tree value.
|
|
@@ -683,21 +771,30 @@ function taprootWalkTree(tree) {
|
|
|
683
771
|
// Keep a stable left-to-right DFS leaf order when flattening the annotated tree.
|
|
684
772
|
return [...taprootWalkTree(tree.left), ...taprootWalkTree(tree.right)];
|
|
685
773
|
}
|
|
686
|
-
|
|
687
|
-
|
|
774
|
+
// BIP 341 control blocks can encode at most 128 sibling hashes.
|
|
775
|
+
const TAPROOT_MAX_DEPTH = 128;
|
|
776
|
+
function taprootHashTree(tree, internalPubKey, allowUnknownOutputs = false, customScripts, depth = 0) {
|
|
777
|
+
// Reject before inspecting or descending into the next node, which also bounds the
|
|
778
|
+
// recursion used by the path annotation and tree-flattening passes below.
|
|
779
|
+
if (depth > TAPROOT_MAX_DEPTH)
|
|
780
|
+
throw new RangeError(`P2TR: tree depth exceeds ${TAPROOT_MAX_DEPTH}`);
|
|
781
|
+
if (tree === undefined)
|
|
688
782
|
throw new Error('taprootHashTree: empty tree');
|
|
783
|
+
if (!Array.isArray(tree) && !P.utils.isPlainObject(tree))
|
|
784
|
+
throw new TypeError('"tree" expected object or array, got type=' + typeof tree);
|
|
689
785
|
if (Array.isArray(tree) && tree.length === 1)
|
|
690
786
|
tree = tree[0];
|
|
691
787
|
// Terminal node (leaf)
|
|
692
788
|
if (!Array.isArray(tree)) {
|
|
789
|
+
u.validateObject(tree, {}, {}, 'tree');
|
|
693
790
|
const version = tree.leafVersion;
|
|
694
791
|
const { script: leafScript } = tree;
|
|
695
792
|
// Earliest tree walk where we can validate tapScripts
|
|
696
793
|
if (tree.tapLeafScript || (tree.tapMerkleRoot && !u.equalBytes(tree.tapMerkleRoot, P.EMPTY)))
|
|
697
794
|
throw new Error('P2TR: tapRoot leafScript cannot have tree');
|
|
698
|
-
const script = typeof leafScript === 'string'
|
|
699
|
-
|
|
700
|
-
|
|
795
|
+
const script = typeof leafScript === 'string'
|
|
796
|
+
? hex.decode(leafScript)
|
|
797
|
+
: abytes(leafScript, undefined, 'tree.script');
|
|
701
798
|
checkTaprootScript(script, internalPubKey, allowUnknownOutputs, customScripts);
|
|
702
799
|
return {
|
|
703
800
|
type: 'leaf',
|
|
@@ -713,8 +810,8 @@ function taprootHashTree(tree, internalPubKey, allowUnknownOutputs = false, cust
|
|
|
713
810
|
throw new Error('hashTree: non binary tree!');
|
|
714
811
|
// branch
|
|
715
812
|
// Both nodes should exist
|
|
716
|
-
const left = taprootHashTree(tree[0], internalPubKey, allowUnknownOutputs, customScripts);
|
|
717
|
-
const right = taprootHashTree(tree[1], internalPubKey, allowUnknownOutputs, customScripts);
|
|
813
|
+
const left = taprootHashTree(tree[0], internalPubKey, allowUnknownOutputs, customScripts, depth + 1);
|
|
814
|
+
const right = taprootHashTree(tree[1], internalPubKey, allowUnknownOutputs, customScripts, depth + 1);
|
|
718
815
|
// BIP 341 sorts TapBranch child hashes lexicographically for hashing, but the original
|
|
719
816
|
// left/right structure still determines the control-block sibling paths for each leaf.
|
|
720
817
|
let [lH, rH] = [left.hash, right.hash];
|
|
@@ -759,25 +856,27 @@ export function p2tr(internalPubKey, tree, network = NETWORK, allowUnknownOutput
|
|
|
759
856
|
throw new Error('p2tr: should have pubKey or scriptTree (or both)');
|
|
760
857
|
const pubKey = typeof internalPubKey === 'string'
|
|
761
858
|
? hex.decode(internalPubKey)
|
|
762
|
-
: internalPubKey
|
|
859
|
+
: (internalPubKey ?? u.taprootNumsKey());
|
|
763
860
|
if (!isValidPubkey(pubKey, u.PubT.schnorr))
|
|
764
861
|
throw new Error('p2tr: non-schnorr pubkey');
|
|
765
862
|
if (tree) {
|
|
766
863
|
let hashedTree = taprootAddPath(taprootHashTree(tree, pubKey, allowUnknownOutputs, customScripts));
|
|
767
864
|
const tapMerkleRoot = hashedTree.hash;
|
|
768
865
|
const [tweakedPubkey, parity] = u.taprootTweakPubkey(pubKey, tapMerkleRoot);
|
|
866
|
+
const tapLeafScript = [];
|
|
769
867
|
const leaves = taprootWalkTree(hashedTree).map((l) => {
|
|
770
868
|
const version = tapLeafVersion(l.version);
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
internalKey: pubKey,
|
|
778
|
-
merklePath: l.path,
|
|
779
|
-
}),
|
|
869
|
+
// Leaf versions are stored as the base even byte; only the control block adds the
|
|
870
|
+
// output-key parity bit required by BIP 341 script-path spending.
|
|
871
|
+
const controlBlock = {
|
|
872
|
+
version: version + parity,
|
|
873
|
+
internalKey: pubKey,
|
|
874
|
+
merklePath: l.path,
|
|
780
875
|
};
|
|
876
|
+
// Skip an encode/decode copy for performance; callers must treat returned metadata as
|
|
877
|
+
// immutable.
|
|
878
|
+
tapLeafScript.push([controlBlock, u.concatBytes(l.script, new Uint8Array([version]))]);
|
|
879
|
+
return { ...l, controlBlock: TaprootControlBlock.encode(controlBlock) };
|
|
781
880
|
});
|
|
782
881
|
return {
|
|
783
882
|
type: 'tr',
|
|
@@ -788,10 +887,7 @@ export function p2tr(internalPubKey, tree, network = NETWORK, allowUnknownOutput
|
|
|
788
887
|
// PSBT stuff
|
|
789
888
|
tapInternalKey: pubKey,
|
|
790
889
|
leaves,
|
|
791
|
-
tapLeafScript
|
|
792
|
-
TaprootControlBlock.decode(l.controlBlock),
|
|
793
|
-
u.concatBytes(l.script, new Uint8Array([tapLeafVersion(l.version)])),
|
|
794
|
-
]),
|
|
890
|
+
tapLeafScript,
|
|
795
891
|
tapMerkleRoot,
|
|
796
892
|
};
|
|
797
893
|
}
|
|
@@ -810,27 +906,47 @@ export function p2tr(internalPubKey, tree, network = NETWORK, allowUnknownOutput
|
|
|
810
906
|
};
|
|
811
907
|
}
|
|
812
908
|
}
|
|
909
|
+
/** Maximum number of combinations materialized by one default helper call. */
|
|
910
|
+
export const MAX_COMBINATIONS = 4096;
|
|
911
|
+
const combinationCount = (n, m) => {
|
|
912
|
+
const k = Math.min(m, n - m);
|
|
913
|
+
let count = 1;
|
|
914
|
+
for (let i = 1; i <= k; i++) {
|
|
915
|
+
count = (count * (n - k + i)) / i;
|
|
916
|
+
if (!Number.isSafeInteger(count))
|
|
917
|
+
return Number.POSITIVE_INFINITY;
|
|
918
|
+
}
|
|
919
|
+
return count;
|
|
920
|
+
};
|
|
813
921
|
// Returns all combinations of size M from lst
|
|
814
922
|
/**
|
|
815
923
|
* Returns all size-`m` combinations from a list.
|
|
816
924
|
* @param m - size of each combination
|
|
817
925
|
* @param list - input items to combine
|
|
926
|
+
* @param maxCombinations - maximum result rows to materialize
|
|
818
927
|
* @returns Array of combinations.
|
|
819
928
|
* @throws If the combination size or input list is invalid. {@link Error}
|
|
929
|
+
* @throws If the requested result exceeds the materialization limit. {@link RangeError}
|
|
820
930
|
* @example
|
|
821
931
|
* Enumerate all size-two subsets of a short list.
|
|
822
932
|
* ```ts
|
|
823
933
|
* combinations(2, [1, 2, 3]);
|
|
824
934
|
* ```
|
|
825
935
|
*/
|
|
826
|
-
export function combinations(m, list) {
|
|
936
|
+
export function combinations(m, list, maxCombinations = MAX_COMBINATIONS) {
|
|
827
937
|
const res = [];
|
|
828
938
|
if (!Array.isArray(list))
|
|
829
939
|
throw new Error('combinations: lst arg should be array');
|
|
830
940
|
const n = list.length;
|
|
831
941
|
anumber(m, 'm');
|
|
942
|
+
anumber(maxCombinations, 'maxCombinations');
|
|
832
943
|
if (m < 1 || m > n)
|
|
833
944
|
throw new Error('combinations: m must satisfy 1 <= m <= lst.length');
|
|
945
|
+
if (maxCombinations < 1)
|
|
946
|
+
throw new Error('combinations: maxCombinations must be >= 1');
|
|
947
|
+
const count = combinationCount(n, m);
|
|
948
|
+
if (count > maxCombinations)
|
|
949
|
+
throw new RangeError(`combinations: C(${n}, ${m}) exceeds materialization limit=${maxCombinations}`);
|
|
834
950
|
/*
|
|
835
951
|
Basically works as M nested loops like:
|
|
836
952
|
for (;idx[0]<lst.length;idx[0]++) for (idx[1]=idx[0]+1;idx[1]<lst.length;idx[1]++)
|
|
@@ -866,6 +982,7 @@ export function combinations(m, list) {
|
|
|
866
982
|
* @param allowSamePubkeys - whether duplicate keys are allowed
|
|
867
983
|
* @returns Array of taproot leaf descriptors.
|
|
868
984
|
* @throws If the taproot multisig parameters are invalid. {@link Error}
|
|
985
|
+
* @throws If the requested leaf set exceeds the materialization limit. {@link RangeError}
|
|
869
986
|
* @example
|
|
870
987
|
* Build the leaf set for an M-of-N taproot `CHECKSIGVERIFY` policy.
|
|
871
988
|
* ```ts
|
|
@@ -875,12 +992,29 @@ export function combinations(m, list) {
|
|
|
875
992
|
* ```
|
|
876
993
|
*/
|
|
877
994
|
export const p2tr_ns = (m, pubkeys, allowSamePubkeys = false) => {
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
995
|
+
anumber(m, 'm');
|
|
996
|
+
if (m > 999)
|
|
997
|
+
throw new Error('OutScript/tr_ns: invalid params');
|
|
998
|
+
// Enforce the allocation bound before doing curve work on an attacker-controlled key list.
|
|
999
|
+
const keySets = combinations(m, pubkeys);
|
|
1000
|
+
if (allowSamePubkeys) {
|
|
1001
|
+
for (const pubkey of pubkeys)
|
|
1002
|
+
u.validatePubkey(pubkey, u.PubT.schnorr);
|
|
1003
|
+
}
|
|
1004
|
+
else
|
|
1005
|
+
uniqPubkey(pubkeys, u.PubT.schnorr);
|
|
1006
|
+
return keySets.map((keys) => {
|
|
1007
|
+
// Keys were validated once above. Encoding through OutScript here would repeat lift_x for
|
|
1008
|
+
// every key in every combination, turning a bounded leaf set into avoidable curve-level work.
|
|
1009
|
+
const ops = [];
|
|
1010
|
+
for (let i = 0; i < keys.length - 1; i++)
|
|
1011
|
+
ops.push(keys[i], 'CHECKSIGVERIFY');
|
|
1012
|
+
ops.push(keys[keys.length - 1], 'CHECKSIG');
|
|
1013
|
+
return {
|
|
1014
|
+
type: 'tr_ns',
|
|
1015
|
+
script: Script.encode(ops),
|
|
1016
|
+
};
|
|
1017
|
+
});
|
|
884
1018
|
};
|
|
885
1019
|
/**
|
|
886
1020
|
* Builds a single-key taproot leaf script.
|
|
@@ -889,6 +1023,7 @@ export const p2tr_ns = (m, pubkeys, allowSamePubkeys = false) => {
|
|
|
889
1023
|
* @param pubkey - Schnorr public key
|
|
890
1024
|
* @returns Taproot single-key leaf descriptor.
|
|
891
1025
|
* @throws If the taproot single-key leaf cannot be encoded. {@link Error}
|
|
1026
|
+
* @throws If the delegated leaf policy exceeds its supported range. {@link RangeError}
|
|
892
1027
|
* @example
|
|
893
1028
|
* Build a single-key tapscript leaf.
|
|
894
1029
|
* ```ts
|
|
@@ -915,7 +1050,7 @@ export const p2tr_pk = (pubkey) => p2tr_ns(1, [pubkey], undefined)[0];
|
|
|
915
1050
|
*/
|
|
916
1051
|
export function p2tr_ms(m, pubkeys, allowSamePubkeys = false) {
|
|
917
1052
|
if (!allowSamePubkeys)
|
|
918
|
-
uniqPubkey(pubkeys);
|
|
1053
|
+
uniqPubkey(pubkeys, u.PubT.schnorr);
|
|
919
1054
|
return {
|
|
920
1055
|
type: 'tr_ms',
|
|
921
1056
|
script: OutScript.encode({ type: 'tr_ms', pubkeys, m }),
|
|
@@ -929,6 +1064,7 @@ export function p2tr_ms(m, pubkeys, allowSamePubkeys = false) {
|
|
|
929
1064
|
* @param network - address network parameters
|
|
930
1065
|
* @returns Encoded Bitcoin address.
|
|
931
1066
|
* @throws If the requested address type is unknown. {@link Error}
|
|
1067
|
+
* @throws If a key-derived script value is outside its supported range. {@link RangeError}
|
|
932
1068
|
* @example
|
|
933
1069
|
* Pick the output type first, then derive the matching address from the private key.
|
|
934
1070
|
* ```ts
|
|
@@ -938,6 +1074,7 @@ export function p2tr_ms(m, pubkeys, allowSamePubkeys = false) {
|
|
|
938
1074
|
* ```
|
|
939
1075
|
*/
|
|
940
1076
|
export function getAddress(type, privKey, network = NETWORK) {
|
|
1077
|
+
u.astring(type, 'type');
|
|
941
1078
|
if (type === 'tr') {
|
|
942
1079
|
return p2tr(u.pubSchnorr(privKey), undefined, network).address;
|
|
943
1080
|
}
|
|
@@ -1076,15 +1213,21 @@ export function WIF(network = NETWORK) {
|
|
|
1076
1213
|
* ```
|
|
1077
1214
|
*/
|
|
1078
1215
|
export function Address(network = NETWORK) {
|
|
1216
|
+
u.validateObject(network, {}, {}, 'network');
|
|
1079
1217
|
return {
|
|
1080
1218
|
encode(from) {
|
|
1219
|
+
u.validateObject(from, {}, {}, 'from');
|
|
1081
1220
|
const { type } = from;
|
|
1221
|
+
u.astring(type, 'from.type');
|
|
1082
1222
|
if (type === 'wpkh')
|
|
1083
1223
|
return programToWitness(0, from.hash, network);
|
|
1084
1224
|
else if (type === 'wsh')
|
|
1085
1225
|
return programToWitness(0, from.hash, network);
|
|
1086
1226
|
else if (type === 'tr')
|
|
1087
1227
|
return programToWitness(1, from.pubkey, network);
|
|
1228
|
+
// BIP433 P2A is the fixed v1 witness program 0x4e73 ('bc1pfeessrawgf').
|
|
1229
|
+
else if (type === 'p2a')
|
|
1230
|
+
return programToWitness(1, P2A_PROGRAM, network);
|
|
1088
1231
|
else if (type === 'pkh')
|
|
1089
1232
|
return formatKey(from.hash, [network.pubKeyHash]);
|
|
1090
1233
|
else if (type === 'sh')
|
|
@@ -1092,6 +1235,7 @@ export function Address(network = NETWORK) {
|
|
|
1092
1235
|
throw new Error(`Unknown address type=${type}`);
|
|
1093
1236
|
},
|
|
1094
1237
|
decode(address) {
|
|
1238
|
+
u.astring(address, 'address');
|
|
1095
1239
|
if (address.length < 14 || address.length > 74)
|
|
1096
1240
|
throw new Error('Invalid address length');
|
|
1097
1241
|
// Bech32
|
|
@@ -1119,8 +1263,10 @@ export function Address(network = NETWORK) {
|
|
|
1119
1263
|
return { type: 'wpkh', hash: data };
|
|
1120
1264
|
else if (version === 1 && data.length === 32)
|
|
1121
1265
|
return { type: 'tr', pubkey: data };
|
|
1266
|
+
else if (version === 1 && u.equalBytes(data, P2A_PROGRAM))
|
|
1267
|
+
return { type: 'p2a', script: Script.encode([1, data]) };
|
|
1122
1268
|
// Future witness versions can still be valid addresses, but this helper
|
|
1123
|
-
// only returns typed descriptors for recognized v0 and
|
|
1269
|
+
// only returns typed descriptors for recognized v0, taproot and P2A templates.
|
|
1124
1270
|
else
|
|
1125
1271
|
throw new Error('Unknown witness program');
|
|
1126
1272
|
}
|
|
@@ -1141,4 +1287,3 @@ export function Address(network = NETWORK) {
|
|
|
1141
1287
|
},
|
|
1142
1288
|
};
|
|
1143
1289
|
}
|
|
1144
|
-
//# sourceMappingURL=payment.js.map
|