@scure/btc-signer 2.0.0 → 2.2.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/payment.js CHANGED
@@ -1,11 +1,14 @@
1
1
  import { bech32, bech32m, createBase58check, hex } from '@scure/base';
2
+ import { anumber } from '@noble/hashes/utils.js';
3
+ import { abytes } from '@noble/curves/utils.js';
2
4
  import * as P from 'micro-packed';
3
5
  import { TaprootControlBlock } from "./psbt.js";
4
- import { OpToNum, Script, VarBytes } from "./script.js";
6
+ import { MAX_SCRIPT_BYTE_LENGTH, OpToNum, Script, VarBytes } from "./script.js";
5
7
  import * as u from "./utils.js";
6
8
  import { NETWORK } from "./utils.js";
7
9
  const OutP2A = {
8
10
  encode(from) {
11
+ // BIP433 defines P2A as the exact OP_1 <0x4e73> scriptPubKey.
9
12
  if (from.length !== 2 || from[0] !== 1 || !u.isBytes(from[1]) || hex.encode(from[1]) !== '4e73')
10
13
  return;
11
14
  return { type: 'p2a', script: Script.encode(from) };
@@ -13,10 +16,13 @@ const OutP2A = {
13
16
  decode: (to) => {
14
17
  if (to.type !== 'p2a')
15
18
  return;
19
+ // The decoded object keeps `script` for caller convenience, but the `p2a`
20
+ // tag always canonicalizes back to the fixed BIP433 script.
16
21
  return [1, hex.decode('4e73')];
17
22
  },
18
23
  };
19
24
  function isValidPubkey(pub, type) {
25
+ // Payment coders use a boolean guard here and normalize validatePubkey failures to false.
20
26
  try {
21
27
  u.validatePubkey(pub, type);
22
28
  return true;
@@ -27,6 +33,8 @@ function isValidPubkey(pub, type) {
27
33
  }
28
34
  const OutPK = {
29
35
  encode(from) {
36
+ // BIP380/BIP381 `pk(KEY)` only admits SEC1 ECDSA pubkeys here; x-only
37
+ // 32-byte CHECKSIG scripts are left for the later tapscript coders.
30
38
  if (from.length !== 2 ||
31
39
  !u.isBytes(from[0]) ||
32
40
  !isValidPubkey(from[0], u.PubT.ecdsa) ||
@@ -34,44 +42,69 @@ const OutPK = {
34
42
  return;
35
43
  return { type: 'pk', pubkey: from[0] };
36
44
  },
37
- decode: (to) => (to.type === 'pk' ? [to.pubkey, 'CHECKSIG'] : undefined),
45
+ decode: (to) => {
46
+ if (to.type !== 'pk')
47
+ return;
48
+ // OutScript validates `pk.pubkey` before this branch emits the canonical
49
+ // `<pubkey> CHECKSIG` script.
50
+ return [to.pubkey, 'CHECKSIG'];
51
+ },
38
52
  };
39
53
  const OutPKH = {
40
54
  encode(from) {
41
55
  if (from.length !== 5 || from[0] !== 'DUP' || from[1] !== 'HASH160' || !u.isBytes(from[2]))
42
56
  return;
57
+ // OutScript validates that the pushed HASH160 is exactly 20 bytes.
58
+ // This child matcher only recognizes the canonical P2PKH opcode skeleton.
43
59
  if (from[3] !== 'EQUALVERIFY' || from[4] !== 'CHECKSIG')
44
60
  return;
45
61
  return { type: 'pkh', hash: from[2] };
46
62
  },
47
- decode: (to) => to.type === 'pkh' ? ['DUP', 'HASH160', to.hash, 'EQUALVERIFY', 'CHECKSIG'] : undefined,
63
+ // OutScript validates `pkh.hash` before this branch emits the canonical
64
+ // `DUP HASH160 <hash> EQUALVERIFY CHECKSIG` script.
65
+ decode: (to) => (to.type === 'pkh'
66
+ ? ['DUP', 'HASH160', to.hash, 'EQUALVERIFY', 'CHECKSIG']
67
+ : undefined),
48
68
  };
49
69
  const OutSH = {
50
70
  encode(from) {
51
71
  if (from.length !== 3 || from[0] !== 'HASH160' || !u.isBytes(from[1]) || from[2] !== 'EQUAL')
52
72
  return;
73
+ // OutScript validates that the pushed HASH160 is exactly 20 bytes.
74
+ // This child matcher only recognizes the canonical P2SH opcode skeleton.
53
75
  return { type: 'sh', hash: from[1] };
54
76
  },
55
- decode: (to) => to.type === 'sh' ? ['HASH160', to.hash, 'EQUAL'] : undefined,
77
+ // OutScript validates `sh.hash` before this branch emits the canonical
78
+ // `HASH160 <hash> EQUAL` script.
79
+ decode: (to) => (to.type === 'sh' ? ['HASH160', to.hash, 'EQUAL'] : undefined),
56
80
  };
57
81
  const OutWSH = {
58
82
  encode(from) {
59
83
  if (from.length !== 2 || from[0] !== 0 || !u.isBytes(from[1]))
60
84
  return;
85
+ // BIP382 `wsh()` is specifically the version-0 32-byte witness program.
86
+ // Other witness versions stay with the later coders.
61
87
  if (from[1].length !== 32)
62
88
  return;
63
89
  return { type: 'wsh', hash: from[1] };
64
90
  },
91
+ // OutScript validates `wsh.hash` before this branch emits the canonical
92
+ // version-0 32-byte witness program.
65
93
  decode: (to) => (to.type === 'wsh' ? [0, to.hash] : undefined),
66
94
  };
67
95
  const OutWPKH = {
68
96
  encode(from) {
69
97
  if (from.length !== 2 || from[0] !== 0 || !u.isBytes(from[1]))
70
98
  return;
99
+ // BIP382 `wpkh()` is specifically the version-0 20-byte witness program.
100
+ // Compressed-key restrictions are enforced upstream, and other witness
101
+ // versions stay with the later coders.
71
102
  if (from[1].length !== 20)
72
103
  return;
73
104
  return { type: 'wpkh', hash: from[1] };
74
105
  },
106
+ // OutScript validates `wpkh.hash` before this branch emits the canonical
107
+ // version-0 20-byte witness program.
75
108
  decode: (to) => (to.type === 'wpkh' ? [0, to.hash] : undefined),
76
109
  };
77
110
  const OutMS = {
@@ -89,17 +122,31 @@ const OutMS = {
89
122
  for (const pub of pubkeys)
90
123
  if (!u.isBytes(pub))
91
124
  return;
92
- return { type: 'ms', m, pubkeys: pubkeys }; // we don't need n, since it is the same as pubkeys
125
+ // OutScript validates pubkey encodings and `0 < m <= n <= 16`.
126
+ // This child matcher only recognizes the canonical CHECKMULTISIG skeleton.
127
+ // We don't need n here because it is the same as pubkeys.length.
128
+ return { type: 'ms', m, pubkeys: pubkeys };
93
129
  },
94
130
  // checkmultisig(n, ..pubkeys, m)
95
- decode: (to) => to.type === 'ms' ? [to.m, ...to.pubkeys, to.pubkeys.length, 'CHECKMULTISIG'] : undefined,
131
+ decode: (to) =>
132
+ // OutScript validates multisig pubkeys and `0 < m <= n <= 16`.
133
+ // This branch only emits the canonical `m <pubkeys...> n CHECKMULTISIG`
134
+ // script.
135
+ (to.type === 'ms'
136
+ ? [to.m, ...to.pubkeys, to.pubkeys.length, 'CHECKMULTISIG']
137
+ : undefined),
96
138
  };
97
139
  const OutTR = {
98
140
  encode(from) {
99
- if (from.length !== 2 || from[0] !== 1 || !u.isBytes(from[1]))
141
+ // BIP141 witness programs are `OP_0..OP_16` followed by a direct 2..40-byte push.
142
+ // BIP341 assigns native taproot meaning only to version 1 with a 32-byte x-only program;
143
+ // other OP_1 program lengths remain reserved future witness programs and should fall through.
144
+ if (from.length !== 2 || from[0] !== 1 || !u.isBytes(from[1]) || from[1].length !== 32)
100
145
  return;
101
146
  return { type: 'tr', pubkey: from[1] };
102
147
  },
148
+ // OutScript validates `tr.pubkey` before this branch emits the canonical
149
+ // version-1 32-byte witness program.
103
150
  decode: (to) => (to.type === 'tr' ? [1, to.pubkey] : undefined),
104
151
  };
105
152
  const OutTRNS = {
@@ -116,10 +163,17 @@ const OutTRNS = {
116
163
  return;
117
164
  continue;
118
165
  }
119
- if (!u.isBytes(elm))
166
+ // Require actual Schnorr pubkeys here so near-miss `<bytes> CHECKSIG`
167
+ // scripts fall through to OutUnknown instead of failing later.
168
+ if (!u.isBytes(elm) || !isValidPubkey(elm, u.PubT.schnorr))
120
169
  return;
121
170
  pubkeys.push(elm);
122
171
  }
172
+ // BIP342 "Using a k-of-k script for every combination" documents the shape
173
+ // `<pubkey_1> CHECKSIGVERIFY ... <pubkey_n> CHECKSIG`; this matcher only
174
+ // classifies that embedded-pubkey form, so bare CHECKSIG stays unknown.
175
+ if (!pubkeys.length)
176
+ return;
123
177
  return { type: 'tr_ns', pubkeys };
124
178
  },
125
179
  decode: (to) => {
@@ -128,6 +182,8 @@ const OutTRNS = {
128
182
  const out = [];
129
183
  for (let i = 0; i < to.pubkeys.length - 1; i++)
130
184
  out.push(to.pubkeys[i], 'CHECKSIGVERIFY');
185
+ // This branch assumes at least one Schnorr pubkey; [] would otherwise emit
186
+ // `[undefined, CHECKSIG]` and only fail later in Script.encode.
131
187
  out.push(to.pubkeys[to.pubkeys.length - 1], 'CHECKSIG');
132
188
  return out;
133
189
  },
@@ -143,13 +199,15 @@ const OutTRMS = {
143
199
  return;
144
200
  for (let i = 0; i < last - 1; i++) {
145
201
  const elm = from[i];
202
+ // Structural mismatches should fall through to OutUnknown instead of
203
+ // throwing from the tr_ms matcher.
146
204
  if (i & 1) {
147
205
  if (elm !== (i === 1 ? 'CHECKSIG' : 'CHECKSIGADD'))
148
- throw new Error('OutScript.encode/tr_ms: wrong element');
206
+ return;
149
207
  continue;
150
208
  }
151
209
  if (!u.isBytes(elm))
152
- throw new Error('OutScript.encode/tr_ms: wrong key element');
210
+ return;
153
211
  pubkeys.push(elm);
154
212
  }
155
213
  return { type: 'tr_ms', pubkeys, m };
@@ -160,18 +218,29 @@ const OutTRMS = {
160
218
  const out = [to.pubkeys[0], 'CHECKSIG'];
161
219
  for (let i = 1; i < to.pubkeys.length; i++)
162
220
  out.push(to.pubkeys[i], 'CHECKSIGADD');
221
+ // This branch assumes `m` was already validated as an integer ScriptNum;
222
+ // fractional JS numbers would otherwise serialize as a different threshold.
163
223
  out.push(to.m, 'NUMEQUAL');
164
224
  return out;
165
225
  },
166
226
  };
167
227
  const OutUnknown = {
168
228
  encode(from) {
229
+ // This is the catch-all fallback for scripts no structured coder recognized,
230
+ // so earlier matchers must return `undefined` instead of throwing on mismatch.
231
+ // Because this reserializes the parsed Script AST, unknown scripts preserve
232
+ // semantics but not original non-minimal push spellings.
169
233
  return { type: 'unknown', script: Script.encode(from) };
170
234
  },
171
- decode: (to) => to.type === 'unknown' ? Script.decode(to.script) : undefined,
235
+ decode: (to) =>
236
+ // This reparses `unknown.script` through the semantic Script codec, so raw
237
+ // bytes must still be syntactically parseable and may canonicalize on re-encode.
238
+ (to.type === 'unknown' ? Script.decode(to.script) : undefined),
172
239
  };
173
240
  // /Payments
174
- const OutScripts = [
241
+ const OutScripts = /* @__PURE__ */ (() => [
242
+ // Order is semantic: specific structured coders run first and the catch-all
243
+ // unknown fallback must stay last.
175
244
  OutP2A,
176
245
  OutPK,
177
246
  OutPKH,
@@ -183,15 +252,28 @@ const OutScripts = [
183
252
  OutTRNS,
184
253
  OutTRMS,
185
254
  OutUnknown,
186
- ];
255
+ ])();
187
256
  // TODO: we can support user supplied output scripts now
188
257
  // - addOutScript
189
258
  // - removeOutScript
190
259
  // - We can do that as log we modify array in-place
191
260
  // - Actually is very hard, since there is sign/finalize logic
192
- const _OutScript = P.apply(Script, P.coders.match(OutScripts));
261
+ // Raw composition of semantic Script parsing with the ordered output-script
262
+ // matcher; OutScript adds the higher-level validation layer on top.
263
+ const _OutScript = /* @__PURE__ */ (() => P.apply(Script, P.coders.match(OutScripts)))();
193
264
  // We can validate this once, because of packed & coders
194
- export const OutScript = P.validate(_OutScript, (i) => {
265
+ /**
266
+ * Coder for recognized Bitcoin output scripts.
267
+ * @example
268
+ * Decode a serialized output script back into the tagged payment descriptor.
269
+ * ```ts
270
+ * import { OutScript, p2wpkh } from '@scure/btc-signer/payment.js';
271
+ * import { pubECDSA, randomPrivateKeyBytes } from '@scure/btc-signer/utils.js';
272
+ * const pay = p2wpkh(pubECDSA(randomPrivateKeyBytes()));
273
+ * OutScript.decode(pay.script);
274
+ * ```
275
+ */
276
+ export const OutScript = /* @__PURE__ */ (() => Object.freeze(P.validate(_OutScript, (i) => {
195
277
  if (i.type === 'pk' && !isValidPubkey(i.pubkey, u.PubT.ecdsa))
196
278
  throw new Error('OutScript/pk: wrong key');
197
279
  if ((i.type === 'pkh' || i.type === 'sh' || i.type === 'wpkh') &&
@@ -209,6 +291,9 @@ export const OutScript = P.validate(_OutScript, (i) => {
209
291
  for (const p of i.pubkeys)
210
292
  if (!isValidPubkey(p, u.PubT.ecdsa))
211
293
  throw new Error('OutScript/multisig: wrong pubkey');
294
+ // Range checks are not enough here: non-integer JS numbers like 1.5 would
295
+ // otherwise slip through and serialize as a different ScriptNum threshold.
296
+ anumber(i.m, 'm');
212
297
  if (i.m <= 0 || n > 16 || i.m > n)
213
298
  throw new Error('OutScript/multisig: invalid params');
214
299
  }
@@ -219,61 +304,133 @@ export const OutScript = P.validate(_OutScript, (i) => {
219
304
  }
220
305
  if (i.type === 'tr_ms') {
221
306
  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
+ anumber(i.m, 'm');
222
310
  if (i.m <= 0 || n > 999 || i.m > n)
223
311
  throw new Error('OutScript/tr_ms: invalid params');
224
312
  }
225
313
  return i;
226
- });
314
+ })))();
227
315
  // Basic sanity check for scripts
228
316
  function checkWSH(s, witnessScript) {
229
317
  if (!u.equalBytes(s.hash, u.sha256(witnessScript)))
230
318
  throw new Error('checkScript: wsh wrong witnessScript hash');
319
+ // BIP141 only requires the witnessScript hash match; the type-based rejects
320
+ // below are an extra descriptor sanity layer for BIP382 invalid-descriptor
321
+ // bullets `wpkh() nested in wsh()` and `wsh() nested in wsh()`.
231
322
  const w = OutScript.decode(witnessScript);
232
323
  if (w.type === 'tr' || w.type === 'tr_ns' || w.type === 'tr_ms')
233
324
  throw new Error(`checkScript: P2${w.type} cannot be wrapped in P2SH`);
234
- if (w.type === 'wpkh' || w.type === 'sh')
325
+ if (w.type === 'wpkh' || w.type === 'wsh' || w.type === 'sh')
235
326
  throw new Error(`checkScript: P2${w.type} cannot be wrapped in P2WSH`);
236
327
  }
328
+ /**
329
+ * Validates that nested redeem and witness scripts match their wrappers.
330
+ * @param script - top-level output script
331
+ * @param redeemScript - optional redeem script for P2SH wrappers
332
+ * @param witnessScript - optional witness script for P2WSH wrappers
333
+ * @throws If the script nesting is invalid or unsupported. {@link Error}
334
+ * @example
335
+ * Verify that wrapped scripts and hashes still match after custom edits.
336
+ * ```ts
337
+ * import { hex } from '@scure/base';
338
+ * import { checkScript, p2pkh, p2sh } from '@scure/btc-signer/payment.js';
339
+ * const wrapped = p2sh(
340
+ * p2pkh(hex.decode('0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798'))
341
+ * );
342
+ * checkScript(wrapped.script, wrapped.redeemScript);
343
+ * ```
344
+ */
237
345
  export function checkScript(script, redeemScript, witnessScript) {
346
+ let hasWsh = false;
347
+ let r = undefined;
238
348
  if (script) {
239
349
  const s = OutScript.decode(script);
350
+ // BIP174 Data Signers Check For bullets: provided redeemScript must match
351
+ // the scriptPubKey, and provided witnessScript must match the scriptPubKey
352
+ // or redeemScript instead of being silently ignored as stray metadata.
240
353
  // ms||pk maybe work, but there will be no address, hard to spend
241
354
  if (s.type === 'tr_ns' || s.type === 'tr_ms' || s.type === 'ms' || s.type == 'pk')
242
355
  throw new Error(`checkScript: non-wrapped ${s.type}`);
243
- if (s.type === 'sh' && redeemScript) {
356
+ if (redeemScript) {
357
+ if (s.type !== 'sh')
358
+ throw new Error('checkScript: redeemScript without P2SH');
244
359
  if (!u.equalBytes(s.hash, u.hash160(redeemScript)))
245
360
  throw new Error('checkScript: sh wrong redeemScript hash');
246
- const r = OutScript.decode(redeemScript);
247
- if (r.type === 'tr' || r.type === 'tr_ns' || r.type === 'tr_ms')
361
+ r = OutScript.decode(redeemScript);
362
+ if (r?.type === 'tr' || r?.type === 'tr_ns' || r?.type === 'tr_ms')
248
363
  throw new Error(`checkScript: P2${r.type} cannot be wrapped in P2SH`);
249
364
  // Not sure if this unspendable, but we cannot represent this via PSBT
250
- if (r.type === 'sh')
365
+ if (r?.type === 'sh')
251
366
  throw new Error('checkScript: P2SH cannot be wrapped in P2SH');
252
367
  }
253
- if (s.type === 'wsh' && witnessScript)
254
- checkWSH(s, witnessScript);
368
+ if (s.type === 'wsh') {
369
+ hasWsh = true;
370
+ if (witnessScript)
371
+ checkWSH(s, witnessScript);
372
+ }
255
373
  }
256
374
  if (redeemScript) {
257
- const r = OutScript.decode(redeemScript);
258
- if (r.type === 'wsh' && witnessScript)
259
- checkWSH(r, witnessScript);
375
+ if (r === undefined)
376
+ r = OutScript.decode(redeemScript);
377
+ if (r?.type === 'wsh') {
378
+ hasWsh = true;
379
+ if (witnessScript)
380
+ checkWSH(r, witnessScript);
381
+ }
260
382
  }
383
+ if (witnessScript && !hasWsh)
384
+ throw new Error('checkScript: witnessScript without P2WSH');
261
385
  }
262
386
  function uniqPubkey(pubkeys) {
263
387
  const map = {};
264
388
  for (const pub of pubkeys) {
389
+ // Exact-byte duplicate filter only: BIP383 valid vectors still permit the
390
+ // same point to appear in compressed and uncompressed SEC1 form in multi().
265
391
  const key = hex.encode(pub);
266
392
  if (map[key])
267
393
  throw new Error(`Multisig: non-uniq pubkey: ${pubkeys.map(hex.encode)}`);
268
394
  map[key] = true;
269
395
  }
270
396
  }
397
+ /**
398
+ * Builds a pay-to-public-key script.
399
+ * @param pubkey - compressed or uncompressed ECDSA public key
400
+ * @param _network - unused network placeholder for API consistency
401
+ * @returns P2PK descriptor.
402
+ * @throws If the public key cannot be encoded as a P2PK output. {@link Error}
403
+ * @example
404
+ * Build a bare pay-to-public-key output.
405
+ * ```ts
406
+ * import { hex } from '@scure/base';
407
+ * import { p2pk } from '@scure/btc-signer/payment.js';
408
+ * p2pk(hex.decode('0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798'));
409
+ * ```
410
+ */
271
411
  export const p2pk = (pubkey, _network = NETWORK) => {
272
412
  // network is unused
273
413
  if (!isValidPubkey(pubkey, u.PubT.ecdsa))
274
414
  throw new Error('P2PK: invalid publicKey');
275
- return { type: 'pk', script: OutScript.encode({ type: 'pk', pubkey }) };
415
+ return {
416
+ type: 'pk',
417
+ script: OutScript.encode({ type: 'pk', pubkey }),
418
+ };
276
419
  };
420
+ /**
421
+ * Builds a P2PKH output from a public key.
422
+ * @param publicKey - compressed or uncompressed ECDSA public key bytes; HASH160 commits to the exact encoding
423
+ * @param network - address network parameters
424
+ * @returns P2PKH descriptor.
425
+ * @throws If the public key cannot be encoded as a P2PKH output. {@link Error}
426
+ * @example
427
+ * Build a classic pay-to-public-key-hash output.
428
+ * ```ts
429
+ * import { hex } from '@scure/base';
430
+ * import { p2pkh } from '@scure/btc-signer/payment.js';
431
+ * p2pkh(hex.decode('0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798'));
432
+ * ```
433
+ */
277
434
  export const p2pkh = (publicKey, network = NETWORK) => {
278
435
  if (!isValidPubkey(publicKey, u.PubT.ecdsa))
279
436
  throw new Error('P2PKH: invalid publicKey');
@@ -285,52 +442,106 @@ export const p2pkh = (publicKey, network = NETWORK) => {
285
442
  hash,
286
443
  };
287
444
  };
445
+ /**
446
+ * Wraps a child script inside P2SH.
447
+ * @param child - child payment descriptor to wrap
448
+ * @param network - address network parameters
449
+ * @returns P2SH descriptor preserving witness metadata when present.
450
+ * @throws If the wrapped script combination is invalid or unsupported. {@link Error}
451
+ * @example
452
+ * Wrap a child script in P2SH so it gets a base58 address form.
453
+ * ```ts
454
+ * import { hex } from '@scure/base';
455
+ * import { p2pk, p2sh, p2wsh } from '@scure/btc-signer/payment.js';
456
+ * p2sh(p2wsh(p2pk(hex.decode('0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798'))));
457
+ * ```
458
+ */
288
459
  export const p2sh = (child, network = NETWORK) => {
289
460
  // It is already tested inside noble-hashes and checkScript
290
- const cs = child.script;
461
+ // BIP16 redeemScripts are pushed by scriptSig, so anything over the 520-byte pushed-element
462
+ // limit would be fundable by HASH160 but unspendable once wrapped in P2SH.
463
+ const c = child;
464
+ const cs = c.script;
291
465
  if (!u.isBytes(cs))
292
- throw new Error(`Wrong script: ${typeof child.script}, expected Uint8Array`);
466
+ throw new Error(`Wrong script: ${typeof c.script}, expected Uint8Array`);
467
+ if (cs.length > MAX_SCRIPT_BYTE_LENGTH)
468
+ throw new Error(`P2SH: redeemScript exceeds ${MAX_SCRIPT_BYTE_LENGTH}-byte push limit: len=${cs.length}`);
293
469
  const hash = u.hash160(cs);
294
- const script = OutScript.encode({ type: 'sh', hash });
295
- checkScript(script, cs, child.witnessScript);
296
- if (child.witnessScript) {
470
+ const out = { type: 'sh', hash };
471
+ const script = OutScript.encode(out);
472
+ const address = Address(network).encode(out);
473
+ checkScript(script, cs, c.witnessScript);
474
+ if (c.witnessScript) {
297
475
  return {
298
476
  type: 'sh',
299
477
  redeemScript: cs,
300
- script: OutScript.encode({ type: 'sh', hash }),
301
- address: Address(network).encode({ type: 'sh', hash }),
478
+ script,
479
+ address,
302
480
  hash,
303
- witnessScript: child.witnessScript,
481
+ witnessScript: c.witnessScript,
304
482
  };
305
483
  }
306
484
  else {
307
485
  return {
308
486
  type: 'sh',
309
487
  redeemScript: cs,
310
- script: OutScript.encode({ type: 'sh', hash }),
311
- address: Address(network).encode({ type: 'sh', hash }),
488
+ script,
489
+ address,
312
490
  hash,
313
491
  };
314
492
  }
315
493
  };
494
+ /**
495
+ * Wraps a child script inside native SegWit P2WSH.
496
+ * @param child - child payment descriptor to wrap
497
+ * @param network - address network parameters
498
+ * @returns P2WSH descriptor.
499
+ * @throws If the wrapped script combination is invalid or unsupported. {@link Error}
500
+ * @example
501
+ * Wrap a child script in native SegWit P2WSH.
502
+ * ```ts
503
+ * import { hex } from '@scure/base';
504
+ * import { p2pk, p2wsh } from '@scure/btc-signer/payment.js';
505
+ * p2wsh(p2pk(hex.decode('0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798')));
506
+ * ```
507
+ */
316
508
  export const p2wsh = (child, network = NETWORK) => {
317
509
  const cs = child.script;
318
510
  if (!u.isBytes(cs))
319
511
  throw new Error(`Wrong script: ${typeof cs}, expected Uint8Array`);
512
+ // BIP141 P2WSH says the witness "must consist of ... a serialized script (witnessScript)"
513
+ // and that witnessScript is limited to 10,000 bytes, so larger wrapped scripts must reject.
514
+ if (cs.length > 10000)
515
+ throw new Error('P2WSH: witnessScript exceeds 10,000 bytes');
320
516
  const hash = u.sha256(cs);
321
517
  const script = OutScript.encode({ type: 'wsh', hash });
322
518
  checkScript(script, undefined, cs);
323
519
  return {
324
520
  type: 'wsh',
325
521
  witnessScript: cs,
326
- script: OutScript.encode({ type: 'wsh', hash }),
522
+ script,
327
523
  address: Address(network).encode({ type: 'wsh', hash }),
328
524
  hash,
329
525
  };
330
526
  };
527
+ /**
528
+ * Builds a native SegWit P2WPKH output from a public key.
529
+ * @param publicKey - compressed ECDSA public key
530
+ * @param network - address network parameters
531
+ * @returns P2WPKH descriptor.
532
+ * @throws If the public key cannot be encoded as a P2WPKH output. {@link Error}
533
+ * @example
534
+ * Build a native SegWit pay-to-public-key-hash output.
535
+ * ```ts
536
+ * import { hex } from '@scure/base';
537
+ * import { p2wpkh } from '@scure/btc-signer/payment.js';
538
+ * p2wpkh(hex.decode('0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798'));
539
+ * ```
540
+ */
331
541
  export const p2wpkh = (publicKey, network = NETWORK) => {
332
542
  if (!isValidPubkey(publicKey, u.PubT.ecdsa))
333
543
  throw new Error('P2WPKH: invalid publicKey');
544
+ // BIP 143 default policy: version-0 witness programs MUST use 33-byte compressed ECDSA keys.
334
545
  if (publicKey.length === 65)
335
546
  throw new Error('P2WPKH: uncompressed public key');
336
547
  const hash = u.hash160(publicKey);
@@ -341,7 +552,24 @@ export const p2wpkh = (publicKey, network = NETWORK) => {
341
552
  hash,
342
553
  };
343
554
  };
555
+ /**
556
+ * Builds a bare multisig script.
557
+ * @param m - number of required signatures
558
+ * @param pubkeys - participating public keys
559
+ * @param allowSamePubkeys - whether duplicate keys are allowed
560
+ * @returns P2MS descriptor.
561
+ * @throws If the multisig parameters are invalid. {@link Error}
562
+ * @example
563
+ * Build a bare multisig output script.
564
+ * ```ts
565
+ * import { hex } from '@scure/base';
566
+ * import { p2ms } from '@scure/btc-signer/payment.js';
567
+ * p2ms(1, [hex.decode('0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798')], true);
568
+ * ```
569
+ */
344
570
  export const p2ms = (m, pubkeys, allowSamePubkeys = false) => {
571
+ // BIP 11 only standardized bare multisig up to 3 keys; this helper still permits up to 16
572
+ // because the same script shape is commonly wrapped by p2sh()/p2wsh() instead of used bare.
345
573
  if (!allowSamePubkeys)
346
574
  uniqPubkey(pubkeys);
347
575
  return {
@@ -387,7 +615,28 @@ function checkTaprootScript(script, internalPubKey, allowUnknownOutputs = false,
387
615
  }
388
616
  }
389
617
  // Helper for generating binary tree from list, with weights
618
+ /**
619
+ * Converts a flat list of weighted leaves into a binary taproot tree.
620
+ * @param taprootList - weighted leaves to arrange
621
+ * @returns Binary taproot script tree.
622
+ * @throws If the list is empty and cannot describe any tree. {@link Error}
623
+ * @example
624
+ * Start from a flat weighted list, then let the helper build the binary tree shape.
625
+ * ```ts
626
+ * import { hex } from '@scure/base';
627
+ * import { p2tr_pk, taprootListToTree } from '@scure/btc-signer/payment.js';
628
+ * taprootListToTree([
629
+ * p2tr_pk(hex.decode('f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9')),
630
+ * p2tr_pk(hex.decode('dff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659')),
631
+ * ]);
632
+ * ```
633
+ */
390
634
  export function taprootListToTree(taprootList) {
635
+ // Empty flat lists cannot represent a taproot script tree; omit the tree entirely for
636
+ // key-path-only outputs instead of passing [] here, otherwise this helper would return
637
+ // undefined and downstream taproot tree walkers would fail much later on a non-tree value.
638
+ if (!taprootList.length)
639
+ throw new Error('taprootListToTree: empty tree');
391
640
  // Clone input in order to not corrupt it
392
641
  const lst = Array.from(taprootList);
393
642
  // We have at least 2 elements => can create branch
@@ -418,7 +667,8 @@ function taprootAddPath(tree, path = []) {
418
667
  return {
419
668
  ...tree,
420
669
  path,
421
- // Left element has right hash in path and otherwise
670
+ // BIP 341 control blocks serialize sibling hashes from leaf to root, so prepend the
671
+ // current sibling before descending into the child subtree.
422
672
  left: taprootAddPath(tree.left, [tree.right.hash, ...path]),
423
673
  right: taprootAddPath(tree.right, [tree.left.hash, ...path]),
424
674
  };
@@ -430,6 +680,7 @@ function taprootWalkTree(tree) {
430
680
  return [tree];
431
681
  if (tree.type !== 'branch')
432
682
  throw new Error(`taprootWalkTree: wrong type=${tree}`);
683
+ // Keep a stable left-to-right DFS leaf order when flattening the annotated tree.
433
684
  return [...taprootWalkTree(tree.left), ...taprootWalkTree(tree.right)];
434
685
  }
435
686
  function taprootHashTree(tree, internalPubKey, allowUnknownOutputs = false, customScripts) {
@@ -439,7 +690,8 @@ function taprootHashTree(tree, internalPubKey, allowUnknownOutputs = false, cust
439
690
  tree = tree[0];
440
691
  // Terminal node (leaf)
441
692
  if (!Array.isArray(tree)) {
442
- const { leafVersion: version, script: leafScript } = tree;
693
+ const version = tree.leafVersion;
694
+ const { script: leafScript } = tree;
443
695
  // Earliest tree walk where we can validate tapScripts
444
696
  if (tree.tapLeafScript || (tree.tapMerkleRoot && !u.equalBytes(tree.tapMerkleRoot, P.EMPTY)))
445
697
  throw new Error('P2TR: tapRoot leafScript cannot have tree');
@@ -451,7 +703,7 @@ function taprootHashTree(tree, internalPubKey, allowUnknownOutputs = false, cust
451
703
  type: 'leaf',
452
704
  version,
453
705
  script,
454
- hash: tapLeafHash(script, version),
706
+ hash: tapLeafHash(script, tapLeafVersion(version)),
455
707
  };
456
708
  }
457
709
  // If tree / branch is not binary tree, convert it
@@ -463,14 +715,44 @@ function taprootHashTree(tree, internalPubKey, allowUnknownOutputs = false, cust
463
715
  // Both nodes should exist
464
716
  const left = taprootHashTree(tree[0], internalPubKey, allowUnknownOutputs, customScripts);
465
717
  const right = taprootHashTree(tree[1], internalPubKey, allowUnknownOutputs, customScripts);
466
- // We cannot swap left/right here, since it will change structure of tree
718
+ // BIP 341 sorts TapBranch child hashes lexicographically for hashing, but the original
719
+ // left/right structure still determines the control-block sibling paths for each leaf.
467
720
  let [lH, rH] = [left.hash, right.hash];
468
721
  if (u.compareBytes(rH, lH) === -1)
469
722
  [lH, rH] = [rH, lH];
470
- return { type: 'branch', left, right, hash: u.tagSchnorr('TapBranch', lH, rH) };
723
+ return {
724
+ type: 'branch',
725
+ left,
726
+ right,
727
+ hash: u.tagSchnorr('TapBranch', lH, rH),
728
+ };
471
729
  }
730
+ /** Default tapleaf version used by taproot script-path outputs before adding the parity bit. */
472
731
  export const TAP_LEAF_VERSION = 0xc0;
473
- export const tapLeafHash = (script, version = TAP_LEAF_VERSION) => u.tagSchnorr('TapLeaf', new Uint8Array([version]), VarBytes.encode(script));
732
+ const tapLeafVersion = (version) => {
733
+ if (version === undefined)
734
+ return TAP_LEAF_VERSION;
735
+ anumber(version, 'leafVersion');
736
+ // BIP341 script-path validation defines the effective leaf version as `v = c[0] & 0xfe`
737
+ // and says it cannot be odd or `0x50`; tapleaf hashes also serialize this as one byte.
738
+ if (version > 0xfe || version === 0x50 || !!(version & 1))
739
+ throw new Error(`P2TR: invalid leafVersion=${version}`);
740
+ return version;
741
+ };
742
+ /**
743
+ * Computes the tagged hash of a tapleaf script.
744
+ * @param script - tapleaf script bytes
745
+ * @param version - base even tapleaf version byte (for tapscript, `0xc0`)
746
+ * @returns Tapleaf hash.
747
+ * @throws If the tapleaf version is not a valid even one-byte version.
748
+ * {@link Error}
749
+ * @example
750
+ * Hash a finalized tapscript leaf before placing it into a Merkle tree.
751
+ * ```ts
752
+ * tapLeafHash(new Uint8Array([0x51]));
753
+ * ```
754
+ */
755
+ export const tapLeafHash = (script, version = TAP_LEAF_VERSION) => u.tagSchnorr('TapLeaf', new Uint8Array([tapLeafVersion(version)]), VarBytes.encode(script));
474
756
  export function p2tr(internalPubKey, tree, network = NETWORK, allowUnknownOutputs = false, customScripts) {
475
757
  // Unspendable
476
758
  if (!internalPubKey && !tree)
@@ -484,14 +766,19 @@ export function p2tr(internalPubKey, tree, network = NETWORK, allowUnknownOutput
484
766
  let hashedTree = taprootAddPath(taprootHashTree(tree, pubKey, allowUnknownOutputs, customScripts));
485
767
  const tapMerkleRoot = hashedTree.hash;
486
768
  const [tweakedPubkey, parity] = u.taprootTweakPubkey(pubKey, tapMerkleRoot);
487
- const leaves = taprootWalkTree(hashedTree).map((l) => ({
488
- ...l,
489
- controlBlock: TaprootControlBlock.encode({
490
- version: (l.version || TAP_LEAF_VERSION) + parity,
491
- internalKey: pubKey,
492
- merklePath: l.path,
493
- }),
494
- }));
769
+ const leaves = taprootWalkTree(hashedTree).map((l) => {
770
+ const version = tapLeafVersion(l.version);
771
+ return {
772
+ ...l,
773
+ // Leaf versions are stored as the base even byte; only the control block adds the
774
+ // output-key parity bit required by BIP 341 script-path spending.
775
+ controlBlock: TaprootControlBlock.encode({
776
+ version: version + parity,
777
+ internalKey: pubKey,
778
+ merklePath: l.path,
779
+ }),
780
+ };
781
+ });
495
782
  return {
496
783
  type: 'tr',
497
784
  script: OutScript.encode({ type: 'tr', pubkey: tweakedPubkey }),
@@ -503,12 +790,14 @@ export function p2tr(internalPubKey, tree, network = NETWORK, allowUnknownOutput
503
790
  leaves,
504
791
  tapLeafScript: leaves.map((l) => [
505
792
  TaprootControlBlock.decode(l.controlBlock),
506
- u.concatBytes(l.script, new Uint8Array([l.version || TAP_LEAF_VERSION])),
793
+ u.concatBytes(l.script, new Uint8Array([tapLeafVersion(l.version)])),
507
794
  ]),
508
795
  tapMerkleRoot,
509
796
  };
510
797
  }
511
798
  else {
799
+ // BIP 341 / BIP 86: key-only Taproot still tweaks with the empty Merkle root so the
800
+ // output commits to an unspendable script path instead of leaving the key untweaked.
512
801
  const tweakedPubkey = u.taprootTweakPubkey(pubKey, P.EMPTY)[0];
513
802
  return {
514
803
  type: 'tr',
@@ -522,18 +811,33 @@ export function p2tr(internalPubKey, tree, network = NETWORK, allowUnknownOutput
522
811
  }
523
812
  }
524
813
  // Returns all combinations of size M from lst
814
+ /**
815
+ * Returns all size-`m` combinations from a list.
816
+ * @param m - size of each combination
817
+ * @param list - input items to combine
818
+ * @returns Array of combinations.
819
+ * @throws If the combination size or input list is invalid. {@link Error}
820
+ * @example
821
+ * Enumerate all size-two subsets of a short list.
822
+ * ```ts
823
+ * combinations(2, [1, 2, 3]);
824
+ * ```
825
+ */
525
826
  export function combinations(m, list) {
526
827
  const res = [];
527
828
  if (!Array.isArray(list))
528
829
  throw new Error('combinations: lst arg should be array');
529
830
  const n = list.length;
530
- if (m > n)
531
- throw new Error('combinations: m > lst.length, no combinations possible');
831
+ anumber(m, 'm');
832
+ if (m < 1 || m > n)
833
+ throw new Error('combinations: m must satisfy 1 <= m <= lst.length');
532
834
  /*
533
835
  Basically works as M nested loops like:
534
836
  for (;idx[0]<lst.length;idx[0]++) for (idx[1]=idx[0]+1;idx[1]<lst.length;idx[1]++)
535
837
  but since we cannot create nested loops dynamically, we unroll it to a single loop
536
838
  */
839
+ // This unrolled-loop implementation assumes an integer 1 <= m <= n; zero, negative, and
840
+ // fractional m values need an explicit guard before entering the loop.
537
841
  const idx = Array.from({ length: m }, (_, i) => i);
538
842
  const last = idx.length - 1;
539
843
  main: for (;;) {
@@ -555,6 +859,21 @@ export function combinations(m, list) {
555
859
  }
556
860
  return res;
557
861
  }
862
+ /**
863
+ * Builds the leaf set for an M-of-N `CHECKSIGVERIFY` taproot policy.
864
+ * @param m - number of required signatures
865
+ * @param pubkeys - participating Schnorr public keys
866
+ * @param allowSamePubkeys - whether duplicate keys are allowed
867
+ * @returns Array of taproot leaf descriptors.
868
+ * @throws If the taproot multisig parameters are invalid. {@link Error}
869
+ * @example
870
+ * Build the leaf set for an M-of-N taproot `CHECKSIGVERIFY` policy.
871
+ * ```ts
872
+ * import { hex } from '@scure/base';
873
+ * import { p2tr_ns } from '@scure/btc-signer/payment.js';
874
+ * p2tr_ns(1, [hex.decode('f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9')], true);
875
+ * ```
876
+ */
558
877
  export const p2tr_ns = (m, pubkeys, allowSamePubkeys = false) => {
559
878
  if (!allowSamePubkeys)
560
879
  uniqPubkey(pubkeys);
@@ -563,7 +882,37 @@ export const p2tr_ns = (m, pubkeys, allowSamePubkeys = false) => {
563
882
  script: OutScript.encode({ type: 'tr_ns', pubkeys: i }),
564
883
  }));
565
884
  };
885
+ /**
886
+ * Builds a single-key taproot leaf script.
887
+ * BIP 341 design guidance: if this is the most likely single-key spend path, prefer
888
+ * using that key as the `p2tr()` internal key instead of forcing it into a script leaf.
889
+ * @param pubkey - Schnorr public key
890
+ * @returns Taproot single-key leaf descriptor.
891
+ * @throws If the taproot single-key leaf cannot be encoded. {@link Error}
892
+ * @example
893
+ * Build a single-key tapscript leaf.
894
+ * ```ts
895
+ * import { hex } from '@scure/base';
896
+ * import { p2tr_pk } from '@scure/btc-signer/payment.js';
897
+ * p2tr_pk(hex.decode('f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9'));
898
+ * ```
899
+ */
566
900
  export const p2tr_pk = (pubkey) => p2tr_ns(1, [pubkey], undefined)[0];
901
+ /**
902
+ * Builds a `CHECKSIGADD` taproot multisig leaf.
903
+ * @param m - number of required signatures
904
+ * @param pubkeys - participating Schnorr public keys
905
+ * @param allowSamePubkeys - whether duplicate keys are allowed
906
+ * @returns Taproot multisig leaf descriptor.
907
+ * @throws If the taproot multisig parameters are invalid. {@link Error}
908
+ * @example
909
+ * Build a `CHECKSIGADD` taproot multisig leaf.
910
+ * ```ts
911
+ * import { hex } from '@scure/base';
912
+ * import { p2tr_ms } from '@scure/btc-signer/payment.js';
913
+ * p2tr_ms(1, [hex.decode('f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9')], true);
914
+ * ```
915
+ */
567
916
  export function p2tr_ms(m, pubkeys, allowSamePubkeys = false) {
568
917
  if (!allowSamePubkeys)
569
918
  uniqPubkey(pubkeys);
@@ -573,10 +922,27 @@ export function p2tr_ms(m, pubkeys, allowSamePubkeys = false) {
573
922
  };
574
923
  }
575
924
  // Simple pubkey address, without complex scripts
925
+ /**
926
+ * Derives a simple address from a private key.
927
+ * @param type - address type to derive
928
+ * @param privKey - private key bytes
929
+ * @param network - address network parameters
930
+ * @returns Encoded Bitcoin address.
931
+ * @throws If the requested address type is unknown. {@link Error}
932
+ * @example
933
+ * Pick the output type first, then derive the matching address from the private key.
934
+ * ```ts
935
+ * import { getAddress } from '@scure/btc-signer/payment.js';
936
+ * import { randomPrivateKeyBytes } from '@scure/btc-signer/utils.js';
937
+ * getAddress('wpkh', randomPrivateKeyBytes());
938
+ * ```
939
+ */
576
940
  export function getAddress(type, privKey, network = NETWORK) {
577
941
  if (type === 'tr') {
578
942
  return p2tr(u.pubSchnorr(privKey), undefined, network).address;
579
943
  }
944
+ // This convenience wrapper always uses the compressed ECDSA public key; derive
945
+ // `pubECDSA(privKey, false)` and call `p2pkh(...)` directly for legacy uncompressed P2PKH.
580
946
  const pubKey = u.pubECDSA(privKey);
581
947
  if (type === 'pkh')
582
948
  return p2pkh(pubKey, network).address;
@@ -584,15 +950,65 @@ export function getAddress(type, privKey, network = NETWORK) {
584
950
  return p2wpkh(pubKey, network).address;
585
951
  throw new Error(`getAddress: unknown type=${type}`);
586
952
  }
953
+ // BIP67 defines canonical multisig ordering only for compressed pubkeys; this helper still sorts
954
+ // raw bytes generically, and higher-level callers may accept uncompressed participants for compat.
587
955
  export const _sortPubkeys = (pubkeys) => Array.from(pubkeys).sort(u.compareBytes);
956
+ /**
957
+ * Builds a classic M-of-N multisig output, wrapped in P2SH or P2WSH.
958
+ * @param m - number of required signatures
959
+ * @param pubkeys - participating public keys
960
+ * @param sorted - whether to sort the public keys first
961
+ * @param witness - whether to wrap the result as native SegWit
962
+ * @param network - address network parameters
963
+ * @returns Multisig payment descriptor.
964
+ * @throws If the multisig parameters or wrapped script are invalid. {@link Error}
965
+ * @example
966
+ * Wrap a classic 2-of-2 script into an addressable multisig output.
967
+ * ```ts
968
+ * import { multisig } from '@scure/btc-signer/payment.js';
969
+ * import { pubECDSA, randomPrivateKeyBytes } from '@scure/btc-signer/utils.js';
970
+ * multisig(
971
+ * 2,
972
+ * [pubECDSA(randomPrivateKeyBytes()), pubECDSA(randomPrivateKeyBytes())],
973
+ * true,
974
+ * true
975
+ * );
976
+ * ```
977
+ */
588
978
  export function multisig(m, pubkeys, sorted = false, witness = false, network = NETWORK) {
979
+ // BIP 143 default policy: version-0 witness programs should use compressed ECDSA pubkeys only;
980
+ // witness multisig callers must avoid uncompressed keys because p2ms accepts generic ECDSA encodings.
981
+ // BIP 16 caps spendable compressed-key P2SH multisig at 15 pubkeys because larger redeem scripts
982
+ // exceed the 520-byte push limit; use witness=true for larger classic multisig sets.
589
983
  const ms = p2ms(m, sorted ? _sortPubkeys(pubkeys) : pubkeys);
590
- return witness ? p2wsh(ms, network) : p2sh(ms, network);
984
+ return (witness ? p2wsh(ms, network) : p2sh(ms, network));
591
985
  }
986
+ /**
987
+ * Builds a multisig output after lexicographically sorting the keys.
988
+ * @param m - number of required signatures
989
+ * @param pubkeys - participating public keys
990
+ * @param witness - whether to wrap the result as native SegWit
991
+ * @param network - address network parameters
992
+ * @returns Sorted multisig payment descriptor.
993
+ * @throws If the multisig parameters or wrapped script are invalid. {@link Error}
994
+ * @example
995
+ * Sort public keys deterministically before constructing the multisig address.
996
+ * ```ts
997
+ * import { sortedMultisig } from '@scure/btc-signer/payment.js';
998
+ * import { pubECDSA, randomPrivateKeyBytes } from '@scure/btc-signer/utils.js';
999
+ * sortedMultisig(
1000
+ * 2,
1001
+ * [pubECDSA(randomPrivateKeyBytes()), pubECDSA(randomPrivateKeyBytes())],
1002
+ * true
1003
+ * );
1004
+ * ```
1005
+ */
592
1006
  export function sortedMultisig(m, pubkeys, witness = false, network = NETWORK) {
1007
+ // BIP67 canonical multisig is compressed-only, but this wrapper intentionally keeps the generic
1008
+ // sorted-multisig behavior and still allows uncompressed participant keys for compatibility.
593
1009
  return multisig(m, pubkeys, true, witness, network);
594
1010
  }
595
- const base58check = createBase58check(u.sha256);
1011
+ const base58check = /* @__PURE__ */ createBase58check(u.sha256);
596
1012
  function validateWitness(version, data) {
597
1013
  if (data.length < 2 || data.length > 40)
598
1014
  throw new Error('Witness: invalid length');
@@ -603,15 +1019,31 @@ function validateWitness(version, data) {
603
1019
  }
604
1020
  function programToWitness(version, data, network = NETWORK) {
605
1021
  validateWitness(version, data);
1022
+ // BIP 350 keeps segwit v0 on Bech32, while witness versions 1+ switch to Bech32m.
606
1023
  const coder = version === 0 ? bech32 : bech32m;
607
1024
  return coder.encode(network.bech32, [version].concat(coder.toWords(data)));
608
1025
  }
609
1026
  function formatKey(hashed, prefix) {
1027
+ // Legacy Base58Check paths all serialize [version-byte || payload] before checksumming.
610
1028
  return base58check.encode(u.concatBytes(Uint8Array.from(prefix), hashed));
611
1029
  }
1030
+ /**
1031
+ * Wallet-import-format coder for private keys.
1032
+ * @param network - address network parameters
1033
+ * @returns WIF coder.
1034
+ * @example
1035
+ * Encode or decode wallet-import-format private keys.
1036
+ * ```ts
1037
+ * const coder = WIF();
1038
+ * coder.encode(new Uint8Array(32).fill(1));
1039
+ * ```
1040
+ */
612
1041
  export function WIF(network = NETWORK) {
613
1042
  return {
614
1043
  encode(privKey) {
1044
+ // Compressed WIF is exactly 32 private-key bytes plus the 0x01 suffix; shorter or longer
1045
+ // inputs must be rejected instead of being silently padded or truncated by subarray().
1046
+ abytes(privKey, 32, 'privKey');
615
1047
  const compressed = u.concatBytes(privKey, new Uint8Array([0x01]));
616
1048
  return formatKey(compressed.subarray(0, 33), [network.wif]);
617
1049
  },
@@ -630,6 +1062,19 @@ export function WIF(network = NETWORK) {
630
1062
  };
631
1063
  }
632
1064
  // Returns OutType, which can be used to create outscript
1065
+ /**
1066
+ * Address encoder/decoder for a specific Bitcoin network.
1067
+ * @param network - address network parameters
1068
+ * @returns Address coder backed by the provided network.
1069
+ * @example
1070
+ * Create a network-specific address coder and encode a payment descriptor.
1071
+ * ```ts
1072
+ * import { Address, p2wpkh } from '@scure/btc-signer/payment.js';
1073
+ * import { pubECDSA, randomPrivateKeyBytes } from '@scure/btc-signer/utils.js';
1074
+ * const coder = Address();
1075
+ * coder.encode(p2wpkh(pubECDSA(randomPrivateKeyBytes())));
1076
+ * ```
1077
+ */
633
1078
  export function Address(network = NETWORK) {
634
1079
  return {
635
1080
  encode(from) {
@@ -674,6 +1119,8 @@ export function Address(network = NETWORK) {
674
1119
  return { type: 'wpkh', hash: data };
675
1120
  else if (version === 1 && data.length === 32)
676
1121
  return { type: 'tr', pubkey: data };
1122
+ // Future witness versions can still be valid addresses, but this helper
1123
+ // only returns typed descriptors for recognized v0 and taproot templates.
677
1124
  else
678
1125
  throw new Error('Unknown witness program');
679
1126
  }