@scure/btc-signer 2.3.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 +49 -10
- package/index.d.ts +4 -3
- package/index.js +3 -3
- package/musig2.d.ts +17 -3
- package/musig2.js +18 -6
- package/net.js +7 -2
- package/package.json +12 -11
- package/payment.d.ts +16 -5
- package/payment.js +118 -27
- package/psbt.d.ts +680 -9
- package/psbt.js +187 -38
- package/src/_type_test.ts +14 -0
- package/src/index.ts +4 -2
- package/src/musig2.ts +32 -7
- package/src/net.ts +7 -2
- package/src/payment.ts +145 -32
- package/src/psbt.ts +210 -35
- package/src/transaction.ts +790 -136
- package/src/utils.ts +24 -2
- package/src/utxo.ts +185 -71
- package/transaction.d.ts +40 -6
- package/transaction.js +667 -123
- package/utils.d.ts +15 -1
- package/utils.js +21 -2
- package/utxo.d.ts +192 -1
- package/utxo.js +166 -69
package/src/payment.ts
CHANGED
|
@@ -228,7 +228,7 @@ const OutTRNS: Coder<OptScript, OutTRNSType | undefined> = {
|
|
|
228
228
|
// BIP342 "Using a k-of-k script for every combination" documents the shape
|
|
229
229
|
// `<pubkey_1> CHECKSIGVERIFY ... <pubkey_n> CHECKSIG`; this matcher only
|
|
230
230
|
// classifies that embedded-pubkey form, so bare CHECKSIG stays unknown.
|
|
231
|
-
if (!pubkeys.length) return;
|
|
231
|
+
if (!pubkeys.length || pubkeys.length > 999) return;
|
|
232
232
|
return { type: 'tr_ns', pubkeys } as TRet<OutTRNSType | undefined>;
|
|
233
233
|
},
|
|
234
234
|
decode: (to: TArg<OutTRNSType>): TRet<OptScript> => {
|
|
@@ -393,16 +393,18 @@ export const OutScript: TRet<
|
|
|
393
393
|
if (i.m <= 0 || n > 16 || i.m > n) throw new Error('OutScript/multisig: invalid params');
|
|
394
394
|
}
|
|
395
395
|
if (i.type === 'tr_ns' || i.type === 'tr_ms') {
|
|
396
|
+
const n = i.pubkeys.length;
|
|
397
|
+
// BIP342 keeps the 1,000-element stack limit. Both supported tapscript multisig forms
|
|
398
|
+
// start with n signatures and then push a pubkey, so n must stay at or below 999.
|
|
399
|
+
if (n > 999) throw new Error(`OutScript/${i.type}: invalid params`);
|
|
396
400
|
for (const p of i.pubkeys)
|
|
397
401
|
if (!isValidPubkey(p, u.PubT.schnorr))
|
|
398
402
|
throw new Error(`OutScript/${i.type}: wrong pubkey`);
|
|
399
403
|
}
|
|
400
404
|
if (i.type === 'tr_ms') {
|
|
401
405
|
const n = i.pubkeys.length;
|
|
402
|
-
// BIP 342 keeps the 1000-element stack limit. This CHECKSIG/CHECKSIGADD form
|
|
403
|
-
// momentarily has n witness items plus one pushed pubkey on the stack, so n must stay <= 999.
|
|
404
406
|
anumber(i.m, 'm');
|
|
405
|
-
if (i.m <= 0 ||
|
|
407
|
+
if (i.m <= 0 || i.m > n) throw new Error('OutScript/tr_ms: invalid params');
|
|
406
408
|
}
|
|
407
409
|
return i;
|
|
408
410
|
})
|
|
@@ -426,6 +428,28 @@ export const OutScript: TRet<
|
|
|
426
428
|
>;
|
|
427
429
|
/** Type of the output-script coder. */
|
|
428
430
|
export type OutScriptType = typeof OutScript;
|
|
431
|
+
// Internal raw-aware boundary for actual scriptPubKeys and P2SH-nested witness programs. Generic
|
|
432
|
+
// OutScript remains semantic because witnessScript and tapscript children may use the same opcodes
|
|
433
|
+
// without claiming BIP141 witness-program framing.
|
|
434
|
+
export const _WitnessOutScript: OutScriptType = /* @__PURE__ */ (() =>
|
|
435
|
+
Object.freeze(
|
|
436
|
+
P.wrap<P.UnwrapCoder<typeof _OutScript>>({
|
|
437
|
+
encodeStream: (w, value) => OutScript.encodeStream(w, value),
|
|
438
|
+
decodeStream: (r) => {
|
|
439
|
+
const raw = r.bytes(r.leftBytes);
|
|
440
|
+
const out = OutScript.decode(raw);
|
|
441
|
+
// Script.decode normalizes push opcodes, but BIP141 recognition requires the exact direct
|
|
442
|
+
// push. Reject only when this explicit outer-program boundary is selected by the caller.
|
|
443
|
+
if (
|
|
444
|
+
out &&
|
|
445
|
+
(out.type === 'p2a' || out.type === 'wpkh' || out.type === 'wsh' || out.type === 'tr') &&
|
|
446
|
+
!u.equalBytes(raw, OutScript.encode(out))
|
|
447
|
+
)
|
|
448
|
+
throw new Error('OutScript: non-canonical witness program');
|
|
449
|
+
return out;
|
|
450
|
+
},
|
|
451
|
+
})
|
|
452
|
+
))() as OutScriptType;
|
|
429
453
|
// TRet-wrapping OutScript changes decode() to the normalized descriptor surface, but the local
|
|
430
454
|
// checkScript/Address caches still need an explicit alias that can carry the decode-side `undefined`.
|
|
431
455
|
type AddressValue = NonNullable<ReturnType<OutScriptType['decode']>>;
|
|
@@ -470,7 +494,7 @@ export function checkScript(
|
|
|
470
494
|
let hasWsh = false;
|
|
471
495
|
let r: OutScriptValue = undefined;
|
|
472
496
|
if (script) {
|
|
473
|
-
const s =
|
|
497
|
+
const s = _WitnessOutScript.decode(script);
|
|
474
498
|
// BIP174 Data Signers Check For bullets: provided redeemScript must match
|
|
475
499
|
// the scriptPubKey, and provided witnessScript must match the scriptPubKey
|
|
476
500
|
// or redeemScript instead of being silently ignored as stray metadata.
|
|
@@ -481,7 +505,7 @@ export function checkScript(
|
|
|
481
505
|
if (s.type !== 'sh') throw new Error('checkScript: redeemScript without P2SH');
|
|
482
506
|
if (!u.equalBytes(s.hash, u.hash160(redeemScript)))
|
|
483
507
|
throw new Error('checkScript: sh wrong redeemScript hash');
|
|
484
|
-
r =
|
|
508
|
+
r = _WitnessOutScript.decode(redeemScript) as OutScriptValue;
|
|
485
509
|
if (r?.type === 'tr' || r?.type === 'tr_ns' || r?.type === 'tr_ms')
|
|
486
510
|
throw new Error(`checkScript: P2${r.type} cannot be wrapped in P2SH`);
|
|
487
511
|
// Not sure if this unspendable, but we cannot represent this via PSBT
|
|
@@ -493,7 +517,7 @@ export function checkScript(
|
|
|
493
517
|
}
|
|
494
518
|
}
|
|
495
519
|
if (redeemScript) {
|
|
496
|
-
if (r === undefined) r =
|
|
520
|
+
if (r === undefined) r = _WitnessOutScript.decode(redeemScript) as OutScriptValue;
|
|
497
521
|
if (r?.type === 'wsh') {
|
|
498
522
|
hasWsh = true;
|
|
499
523
|
if (witnessScript) checkWSH(r as TArg<OutWSHType>, witnessScript);
|
|
@@ -502,12 +526,21 @@ export function checkScript(
|
|
|
502
526
|
if (witnessScript && !hasWsh) throw new Error('checkScript: witnessScript without P2WSH');
|
|
503
527
|
}
|
|
504
528
|
|
|
505
|
-
function uniqPubkey(pubkeys: TArg<Bytes[]
|
|
529
|
+
function uniqPubkey(pubkeys: TArg<Bytes[]>, type: u.PubT) {
|
|
506
530
|
const map: Record<string, boolean> = {};
|
|
507
531
|
for (const pub of pubkeys) {
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
532
|
+
u.validatePubkey(pub, type);
|
|
533
|
+
let normalized = pub;
|
|
534
|
+
if (type === u.PubT.ecdsa && pub.length === 65) {
|
|
535
|
+
// Compressed and uncompressed SEC1 encodings can represent the same curve point. Normalize
|
|
536
|
+
// valid uncompressed keys before comparing so one signer cannot occupy multiple threshold
|
|
537
|
+
// slots merely by changing the serialization. `allowSamePubkeys` remains the explicit
|
|
538
|
+
// compatibility escape hatch for callers that intentionally construct duplicate-key scripts.
|
|
539
|
+
normalized = new Uint8Array(33);
|
|
540
|
+
normalized[0] = 2 | (pub[64] & 1);
|
|
541
|
+
normalized.set(pub.subarray(1, 33), 1);
|
|
542
|
+
}
|
|
543
|
+
const key = hex.encode(normalized);
|
|
511
544
|
if (map[key]) throw new Error(`Multisig: non-uniq pubkey: ${pubkeys.map(hex.encode)}`);
|
|
512
545
|
map[key] = true;
|
|
513
546
|
}
|
|
@@ -609,10 +642,30 @@ export type P2SHWithoutWitness = Omit<P2SHBase, 'witnessScript'>;
|
|
|
609
642
|
export type P2SHReturn<T extends P2Ret> = T extends { witnessScript: Bytes }
|
|
610
643
|
? P2SHWithWitness
|
|
611
644
|
: P2SHWithoutWitness;
|
|
645
|
+
|
|
646
|
+
const checkCanonicalScript = (
|
|
647
|
+
script: TArg<Bytes>,
|
|
648
|
+
name: 'redeemScript' | 'witnessScript',
|
|
649
|
+
allowNonCanonicalScript: boolean
|
|
650
|
+
): void => {
|
|
651
|
+
if (allowNonCanonicalScript) return;
|
|
652
|
+
const decoded = Script.decode(script);
|
|
653
|
+
const nonMinimalNumber = decoded.some(
|
|
654
|
+
(op) => u.isBytes(op) && op.length === 1 && ((1 <= op[0] && op[0] <= 16) || op[0] === 0x81)
|
|
655
|
+
);
|
|
656
|
+
// Core's default MINIMALDATA policy rejects these spends from its mempool, so address-producing
|
|
657
|
+
// helpers require an explicit opt-in even though the original bytes remain consensus-valid.
|
|
658
|
+
if (nonMinimalNumber || !u.equalBytes(script, Script.encode(decoded))) {
|
|
659
|
+
const wrapper = name === 'redeemScript' ? 'P2SH' : 'P2WSH';
|
|
660
|
+
throw new Error(`${wrapper}: non-canonical ${name}`);
|
|
661
|
+
}
|
|
662
|
+
};
|
|
663
|
+
|
|
612
664
|
/**
|
|
613
665
|
* Wraps a child script inside P2SH.
|
|
614
666
|
* @param child - child payment descriptor to wrap
|
|
615
667
|
* @param network - address network parameters
|
|
668
|
+
* @param allowNonCanonicalScript - whether to create an address for a non-minimal child script
|
|
616
669
|
* @returns P2SH descriptor preserving witness metadata when present.
|
|
617
670
|
* @throws If the wrapped script combination is invalid or unsupported. {@link Error}
|
|
618
671
|
* @example
|
|
@@ -625,7 +678,8 @@ export type P2SHReturn<T extends P2Ret> = T extends { witnessScript: Bytes }
|
|
|
625
678
|
*/
|
|
626
679
|
export const p2sh = <T extends P2Ret>(
|
|
627
680
|
child: TArg<T>,
|
|
628
|
-
network: BTC_NETWORK = NETWORK
|
|
681
|
+
network: BTC_NETWORK = NETWORK,
|
|
682
|
+
allowNonCanonicalScript = false
|
|
629
683
|
): TRet<Extends<P2SHReturn<T>, P2Ret>> => {
|
|
630
684
|
u.validateObject(child as Record<string, any>, {}, {}, 'child');
|
|
631
685
|
// It is already tested inside noble-hashes and checkScript
|
|
@@ -638,6 +692,7 @@ export const p2sh = <T extends P2Ret>(
|
|
|
638
692
|
throw new Error(
|
|
639
693
|
`P2SH: redeemScript exceeds ${MAX_SCRIPT_BYTE_LENGTH}-byte push limit: len=${cs.length}`
|
|
640
694
|
);
|
|
695
|
+
checkCanonicalScript(cs, 'redeemScript', allowNonCanonicalScript);
|
|
641
696
|
const hash = u.hash160(cs);
|
|
642
697
|
const out = { type: 'sh', hash } as const;
|
|
643
698
|
const script = OutScript.encode(out);
|
|
@@ -680,6 +735,7 @@ export type P2WSH = {
|
|
|
680
735
|
* Wraps a child script inside native SegWit P2WSH.
|
|
681
736
|
* @param child - child payment descriptor to wrap
|
|
682
737
|
* @param network - address network parameters
|
|
738
|
+
* @param allowNonCanonicalScript - whether to create an address for a non-minimal child script
|
|
683
739
|
* @returns P2WSH descriptor.
|
|
684
740
|
* @throws If the wrapped script combination is invalid or unsupported. {@link Error}
|
|
685
741
|
* @example
|
|
@@ -692,7 +748,8 @@ export type P2WSH = {
|
|
|
692
748
|
*/
|
|
693
749
|
export const p2wsh = (
|
|
694
750
|
child: TArg<P2Ret>,
|
|
695
|
-
network: BTC_NETWORK = NETWORK
|
|
751
|
+
network: BTC_NETWORK = NETWORK,
|
|
752
|
+
allowNonCanonicalScript = false
|
|
696
753
|
): TRet<Extends<P2WSH, P2Ret>> => {
|
|
697
754
|
u.validateObject(child as Record<string, any>, {}, {}, 'child');
|
|
698
755
|
const cs = child.script;
|
|
@@ -700,6 +757,7 @@ export const p2wsh = (
|
|
|
700
757
|
// BIP141 P2WSH says the witness "must consist of ... a serialized script (witnessScript)"
|
|
701
758
|
// and that witnessScript is limited to 10,000 bytes, so larger wrapped scripts must reject.
|
|
702
759
|
if (cs.length > 10000) throw new Error('P2WSH: witnessScript exceeds 10,000 bytes');
|
|
760
|
+
checkCanonicalScript(cs, 'witnessScript', allowNonCanonicalScript);
|
|
703
761
|
const hash = u.sha256(cs);
|
|
704
762
|
const script = OutScript.encode({ type: 'wsh', hash });
|
|
705
763
|
checkScript(script, undefined, cs);
|
|
@@ -782,7 +840,7 @@ export const p2ms = (
|
|
|
782
840
|
): TRet<Extends<P2MS, P2Ret>> => {
|
|
783
841
|
// BIP 11 only standardized bare multisig up to 3 keys; this helper still permits up to 16
|
|
784
842
|
// because the same script shape is commonly wrapped by p2sh()/p2wsh() instead of used bare.
|
|
785
|
-
if (!allowSamePubkeys) uniqPubkey(pubkeys);
|
|
843
|
+
if (!allowSamePubkeys) uniqPubkey(pubkeys, u.PubT.ecdsa);
|
|
786
844
|
return {
|
|
787
845
|
type: 'ms',
|
|
788
846
|
script: OutScript.encode({ type: 'ms', pubkeys, m }),
|
|
@@ -826,7 +884,7 @@ function checkTaprootScript(
|
|
|
826
884
|
const outms = out as OutTRNSType | OutTRMSType;
|
|
827
885
|
if (!allowUnknownOutputs && outms.pubkeys) {
|
|
828
886
|
for (const p of outms.pubkeys) {
|
|
829
|
-
if (u.equalBytes(p, u.
|
|
887
|
+
if (u.equalBytes(p, u.taprootNumsKey()))
|
|
830
888
|
throw new Error('Unspendable taproot key in leaf script');
|
|
831
889
|
// It's likely a mistake at this point:
|
|
832
890
|
// 1. p2tr(A, p2tr_ns(2, [A, B])) == p2tr(A, p2tr_pk(B)) (A or B key)
|
|
@@ -978,12 +1036,19 @@ function taprootWalkTree(tree: TArg<HashedTreeWithPath>): TRet<TaprootLeaf[]> {
|
|
|
978
1036
|
return [...taprootWalkTree(tree.left), ...taprootWalkTree(tree.right)] as TRet<TaprootLeaf[]>;
|
|
979
1037
|
}
|
|
980
1038
|
|
|
1039
|
+
// BIP 341 control blocks can encode at most 128 sibling hashes.
|
|
1040
|
+
const TAPROOT_MAX_DEPTH = 128;
|
|
981
1041
|
function taprootHashTree(
|
|
982
1042
|
tree: TArg<TaprootScriptTree>,
|
|
983
1043
|
internalPubKey: TArg<Bytes>,
|
|
984
1044
|
allowUnknownOutputs = false,
|
|
985
|
-
customScripts?: TArg<CustomScript[]
|
|
1045
|
+
customScripts?: TArg<CustomScript[]>,
|
|
1046
|
+
depth = 0
|
|
986
1047
|
): TRet<HashedTree> {
|
|
1048
|
+
// Reject before inspecting or descending into the next node, which also bounds the
|
|
1049
|
+
// recursion used by the path annotation and tree-flattening passes below.
|
|
1050
|
+
if (depth > TAPROOT_MAX_DEPTH)
|
|
1051
|
+
throw new RangeError(`P2TR: tree depth exceeds ${TAPROOT_MAX_DEPTH}`);
|
|
987
1052
|
if (tree === undefined) throw new Error('taprootHashTree: empty tree');
|
|
988
1053
|
if (!Array.isArray(tree) && !P.utils.isPlainObject(tree))
|
|
989
1054
|
throw new TypeError('"tree" expected object or array, got type=' + typeof tree);
|
|
@@ -1013,8 +1078,20 @@ function taprootHashTree(
|
|
|
1013
1078
|
if (tree.length !== 2) throw new Error('hashTree: non binary tree!');
|
|
1014
1079
|
// branch
|
|
1015
1080
|
// Both nodes should exist
|
|
1016
|
-
const left = taprootHashTree(
|
|
1017
|
-
|
|
1081
|
+
const left = taprootHashTree(
|
|
1082
|
+
tree[0],
|
|
1083
|
+
internalPubKey,
|
|
1084
|
+
allowUnknownOutputs,
|
|
1085
|
+
customScripts,
|
|
1086
|
+
depth + 1
|
|
1087
|
+
);
|
|
1088
|
+
const right = taprootHashTree(
|
|
1089
|
+
tree[1],
|
|
1090
|
+
internalPubKey,
|
|
1091
|
+
allowUnknownOutputs,
|
|
1092
|
+
customScripts,
|
|
1093
|
+
depth + 1
|
|
1094
|
+
);
|
|
1018
1095
|
// BIP 341 sorts TapBranch child hashes lexicographically for hashing, but the original
|
|
1019
1096
|
// left/right structure still determines the control-block sibling paths for each leaf.
|
|
1020
1097
|
let [lH, rH] = [left.hash, right.hash];
|
|
@@ -1056,7 +1133,7 @@ export const tapLeafHash = (script: TArg<Bytes>, version: number = TAP_LEAF_VERS
|
|
|
1056
1133
|
|
|
1057
1134
|
// Works as key OR tree.
|
|
1058
1135
|
// If we only have tree, need to add unspendable key, otherwise
|
|
1059
|
-
// complex multisig wallet can be spent by owner of key only. See
|
|
1136
|
+
// complex multisig wallet can be spent by owner of key only. See taprootNumsKey
|
|
1060
1137
|
/** Conditional taproot return type for key-only or tree-backed outputs. */
|
|
1061
1138
|
export type P2TRRet<T> = T extends TaprootScriptTree ? P2TR_TREE : P2TR;
|
|
1062
1139
|
/**
|
|
@@ -1068,6 +1145,7 @@ export type P2TRRet<T> = T extends TaprootScriptTree ? P2TR_TREE : P2TR;
|
|
|
1068
1145
|
* @param customScripts - optional custom script codecs for taproot leaves
|
|
1069
1146
|
* @returns Taproot descriptor with optional script-path metadata.
|
|
1070
1147
|
* @throws If the internal key or taproot script tree is invalid. {@link Error}
|
|
1148
|
+
* @throws If a numeric script value is outside its supported range. {@link RangeError}
|
|
1071
1149
|
* @example
|
|
1072
1150
|
* Combine script leaves into a final taproot output descriptor and address.
|
|
1073
1151
|
* ```ts
|
|
@@ -1105,7 +1183,7 @@ export function p2tr(
|
|
|
1105
1183
|
const pubKey =
|
|
1106
1184
|
typeof internalPubKey === 'string'
|
|
1107
1185
|
? hex.decode(internalPubKey)
|
|
1108
|
-
: internalPubKey
|
|
1186
|
+
: (internalPubKey ?? u.taprootNumsKey());
|
|
1109
1187
|
if (!isValidPubkey(pubKey, u.PubT.schnorr)) throw new Error('p2tr: non-schnorr pubkey');
|
|
1110
1188
|
if (tree) {
|
|
1111
1189
|
let hashedTree = taprootAddPath(
|
|
@@ -1156,25 +1234,47 @@ export function p2tr(
|
|
|
1156
1234
|
}
|
|
1157
1235
|
}
|
|
1158
1236
|
|
|
1237
|
+
/** Maximum number of combinations materialized by one default helper call. */
|
|
1238
|
+
export const MAX_COMBINATIONS = 4096;
|
|
1239
|
+
|
|
1240
|
+
const combinationCount = (n: number, m: number): number => {
|
|
1241
|
+
const k = Math.min(m, n - m);
|
|
1242
|
+
let count = 1;
|
|
1243
|
+
for (let i = 1; i <= k; i++) {
|
|
1244
|
+
count = (count * (n - k + i)) / i;
|
|
1245
|
+
if (!Number.isSafeInteger(count)) return Number.POSITIVE_INFINITY;
|
|
1246
|
+
}
|
|
1247
|
+
return count;
|
|
1248
|
+
};
|
|
1249
|
+
|
|
1159
1250
|
// Returns all combinations of size M from lst
|
|
1160
1251
|
/**
|
|
1161
1252
|
* Returns all size-`m` combinations from a list.
|
|
1162
1253
|
* @param m - size of each combination
|
|
1163
1254
|
* @param list - input items to combine
|
|
1255
|
+
* @param maxCombinations - maximum result rows to materialize
|
|
1164
1256
|
* @returns Array of combinations.
|
|
1165
1257
|
* @throws If the combination size or input list is invalid. {@link Error}
|
|
1258
|
+
* @throws If the requested result exceeds the materialization limit. {@link RangeError}
|
|
1166
1259
|
* @example
|
|
1167
1260
|
* Enumerate all size-two subsets of a short list.
|
|
1168
1261
|
* ```ts
|
|
1169
1262
|
* combinations(2, [1, 2, 3]);
|
|
1170
1263
|
* ```
|
|
1171
1264
|
*/
|
|
1172
|
-
export function combinations<T>(m: number, list: T[]): T[][] {
|
|
1265
|
+
export function combinations<T>(m: number, list: T[], maxCombinations = MAX_COMBINATIONS): T[][] {
|
|
1173
1266
|
const res: T[][] = [];
|
|
1174
1267
|
if (!Array.isArray(list)) throw new Error('combinations: lst arg should be array');
|
|
1175
1268
|
const n = list.length;
|
|
1176
1269
|
anumber(m, 'm');
|
|
1270
|
+
anumber(maxCombinations, 'maxCombinations');
|
|
1177
1271
|
if (m < 1 || m > n) throw new Error('combinations: m must satisfy 1 <= m <= lst.length');
|
|
1272
|
+
if (maxCombinations < 1) throw new Error('combinations: maxCombinations must be >= 1');
|
|
1273
|
+
const count = combinationCount(n, m);
|
|
1274
|
+
if (count > maxCombinations)
|
|
1275
|
+
throw new RangeError(
|
|
1276
|
+
`combinations: C(${n}, ${m}) exceeds materialization limit=${maxCombinations}`
|
|
1277
|
+
);
|
|
1178
1278
|
/*
|
|
1179
1279
|
Basically works as M nested loops like:
|
|
1180
1280
|
for (;idx[0]<lst.length;idx[0]++) for (idx[1]=idx[0]+1;idx[1]<lst.length;idx[1]++)
|
|
@@ -1204,8 +1304,8 @@ export function combinations<T>(m: number, list: T[]): T[][] {
|
|
|
1204
1304
|
|
|
1205
1305
|
/**
|
|
1206
1306
|
* M-of-N multi-leaf wallet via p2tr_ns. If m == n, single script is emitted.
|
|
1207
|
-
*
|
|
1208
|
-
*
|
|
1307
|
+
* It materializes C(n, m) leaves up to {@link MAX_COMBINATIONS}; middle-of-the-range thresholds
|
|
1308
|
+
* above that bound are rejected before allocation.
|
|
1209
1309
|
* `2-of-[A,B,C] => [A,B] | [A,C] | [B,C]`
|
|
1210
1310
|
*/
|
|
1211
1311
|
export type P2TR_NS = {
|
|
@@ -1221,6 +1321,7 @@ export type P2TR_NS = {
|
|
|
1221
1321
|
* @param allowSamePubkeys - whether duplicate keys are allowed
|
|
1222
1322
|
* @returns Array of taproot leaf descriptors.
|
|
1223
1323
|
* @throws If the taproot multisig parameters are invalid. {@link Error}
|
|
1324
|
+
* @throws If the requested leaf set exceeds the materialization limit. {@link RangeError}
|
|
1224
1325
|
* @example
|
|
1225
1326
|
* Build the leaf set for an M-of-N taproot `CHECKSIGVERIFY` policy.
|
|
1226
1327
|
* ```ts
|
|
@@ -1234,14 +1335,24 @@ export const p2tr_ns = (
|
|
|
1234
1335
|
pubkeys: TArg<Bytes[]>,
|
|
1235
1336
|
allowSamePubkeys = false
|
|
1236
1337
|
): TRet<Extends<P2TR_NS, P2Ret>[]> => {
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
)
|
|
1338
|
+
anumber(m, 'm');
|
|
1339
|
+
if (m > 999) throw new Error('OutScript/tr_ns: invalid params');
|
|
1340
|
+
// Enforce the allocation bound before doing curve work on an attacker-controlled key list.
|
|
1341
|
+
const keySets = combinations(m, pubkeys);
|
|
1342
|
+
if (allowSamePubkeys) {
|
|
1343
|
+
for (const pubkey of pubkeys) u.validatePubkey(pubkey, u.PubT.schnorr);
|
|
1344
|
+
} else uniqPubkey(pubkeys, u.PubT.schnorr);
|
|
1345
|
+
return keySets.map((keys) => {
|
|
1346
|
+
// Keys were validated once above. Encoding through OutScript here would repeat lift_x for
|
|
1347
|
+
// every key in every combination, turning a bounded leaf set into avoidable curve-level work.
|
|
1348
|
+
const ops: ScriptType = [];
|
|
1349
|
+
for (let i = 0; i < keys.length - 1; i++) ops.push(keys[i], 'CHECKSIGVERIFY');
|
|
1350
|
+
ops.push(keys[keys.length - 1], 'CHECKSIG');
|
|
1351
|
+
return {
|
|
1352
|
+
type: 'tr_ns',
|
|
1353
|
+
script: Script.encode(ops),
|
|
1354
|
+
} as const as TRet<Extends<P2TR_NS, P2Ret>>;
|
|
1355
|
+
}) as TRet<Extends<P2TR_NS, P2Ret>[]>;
|
|
1245
1356
|
};
|
|
1246
1357
|
// Taproot public key (case of p2tr_ns)
|
|
1247
1358
|
/** Single-key taproot leaf descriptor. */
|
|
@@ -1253,6 +1364,7 @@ export type P2TR_PK = P2TR_NS;
|
|
|
1253
1364
|
* @param pubkey - Schnorr public key
|
|
1254
1365
|
* @returns Taproot single-key leaf descriptor.
|
|
1255
1366
|
* @throws If the taproot single-key leaf cannot be encoded. {@link Error}
|
|
1367
|
+
* @throws If the delegated leaf policy exceeds its supported range. {@link RangeError}
|
|
1256
1368
|
* @example
|
|
1257
1369
|
* Build a single-key tapscript leaf.
|
|
1258
1370
|
* ```ts
|
|
@@ -1291,7 +1403,7 @@ export function p2tr_ms(
|
|
|
1291
1403
|
pubkeys: TArg<Bytes[]>,
|
|
1292
1404
|
allowSamePubkeys = false
|
|
1293
1405
|
): TRet<Extends<P2TR_MS, P2Ret>> {
|
|
1294
|
-
if (!allowSamePubkeys) uniqPubkey(pubkeys);
|
|
1406
|
+
if (!allowSamePubkeys) uniqPubkey(pubkeys, u.PubT.schnorr);
|
|
1295
1407
|
return {
|
|
1296
1408
|
type: 'tr_ms',
|
|
1297
1409
|
script: OutScript.encode({ type: 'tr_ms', pubkeys, m }),
|
|
@@ -1306,6 +1418,7 @@ export function p2tr_ms(
|
|
|
1306
1418
|
* @param network - address network parameters
|
|
1307
1419
|
* @returns Encoded Bitcoin address.
|
|
1308
1420
|
* @throws If the requested address type is unknown. {@link Error}
|
|
1421
|
+
* @throws If a key-derived script value is outside its supported range. {@link RangeError}
|
|
1309
1422
|
* @example
|
|
1310
1423
|
* Pick the output type first, then derive the matching address from the private key.
|
|
1311
1424
|
* ```ts
|