@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/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 || n > 999 || i.m > n) throw new Error('OutScript/tr_ms: invalid params');
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 = OutScript.decode(script);
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 = OutScript.decode(redeemScript) as OutScriptValue;
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 = OutScript.decode(redeemScript) as OutScriptValue;
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
- // Exact-byte duplicate filter only: BIP383 valid vectors still permit the
509
- // same point to appear in compressed and uncompressed SEC1 form in multi().
510
- const key = hex.encode(pub);
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.TAPROOT_UNSPENDABLE_KEY))
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(tree[0], internalPubKey, allowUnknownOutputs, customScripts);
1017
- const right = taprootHashTree(tree[1], internalPubKey, allowUnknownOutputs, customScripts);
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 TAPROOT_UNSPENDABLE_KEY
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 || u.TAPROOT_UNSPENDABLE_KEY;
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
- * Takes O(n^2) if m != n. 99-of-100 is ok, 5-of-100 is not.
1208
- * It materializes C(n, m) leaves, so middle-of-the-range thresholds blow up combinatorially.
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
- if (!allowSamePubkeys) uniqPubkey(pubkeys);
1238
- return combinations(m, pubkeys).map(
1239
- (i) =>
1240
- ({
1241
- type: 'tr_ns',
1242
- script: OutScript.encode({ type: 'tr_ns', pubkeys: i }),
1243
- }) as const as TRet<Extends<P2TR_NS, P2Ret>>
1244
- ) as TRet<Extends<P2TR_NS, P2Ret>[]>;
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