@scure/btc-signer 2.0.1 → 2.3.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/musig2.ts CHANGED
@@ -1,8 +1,9 @@
1
1
  import { schnorr, secp256k1 } from '@noble/curves/secp256k1.js';
2
+ import type { WeierstrassPoint } from '@noble/curves/abstract/weierstrass.js';
2
3
  import { aInRange, concatBytes, equalBytes, numberToBytesBE } from '@noble/curves/utils.js';
3
4
  import { abytes, anumber, randomBytes } from '@noble/hashes/utils.js';
4
5
  import * as P from 'micro-packed';
5
- import { compareBytes, hasEven } from './utils.ts';
6
+ import { compareBytes, hasEven, type TArg, type TRet, validateObject } from './utils.ts';
6
7
 
7
8
  /*
8
9
  MuSig2. This is not the full protocol: only an implementation of primitives from BIP-327.
@@ -16,19 +17,46 @@ Links:
16
17
  - https://github.com/bitcoin/bips/blob/master/bip-0327/reference.py
17
18
  */
18
19
  // Types
20
+ /** Represents a pair of public and secret nonces used in MuSig2 signing. */
21
+ export type Nonces = {
22
+ /** Public nonce that gets shared with the other participants. */
23
+ public: Uint8Array;
24
+ /** Secret nonce that stays local until partial signing finishes. */
25
+ secret: Uint8Array;
26
+ };
19
27
  /**
20
- * Represents a pair of public and secret nonces used in MuSig2 signing.
28
+ * Represents a deterministic nonce, including its public part and the
29
+ * resulting partial signature.
21
30
  */
22
- export type Nonces = { public: Uint8Array; secret: Uint8Array };
23
- /**
24
- * Represents a deterministic nonce, including its public part and the resulting partial signature.
25
- */
26
- export type DetNonce = { publicNonce: Uint8Array; partialSig: Uint8Array };
31
+ export type DetNonce = {
32
+ /** Public nonce that the signer shares for this deterministic signing round. */
33
+ publicNonce: Uint8Array;
34
+ /** Partial signature produced after combining all participant data. */
35
+ partialSig: Uint8Array;
36
+ };
37
+ /** MuSig2 key aggregation context used by signing sessions. */
38
+ export type KeyAggregate = {
39
+ /** Aggregate public key before x-only export. */
40
+ aggPublicKey: WeierstrassPoint<bigint>;
41
+ /** Accumulated sign from x-only tweaks. */
42
+ gAcc: bigint;
43
+ /** Accumulated tweak scalar. */
44
+ tweakAcc: bigint;
45
+ };
27
46
  /**
28
47
  * Represents an error indicating an invalid contribution from a signer.
29
48
  * This allows pointing out which participant is malicious and what specifically is wrong.
49
+ * @param idx - signer index with the invalid contribution
50
+ * @param m - error message
51
+ * @example
52
+ * Create an error that points to the participant who sent invalid data.
53
+ * ```ts
54
+ * new InvalidContributionErr(0, 'pubkey');
55
+ * ```
30
56
  */
31
57
  export class InvalidContributionErr extends Error {
58
+ // BIP327 identifiable aborts blame exactly one signer by participant index in the
59
+ // caller's session ordering, so callers interpret idx using the same ordering they signed with.
32
60
  readonly idx: number; // Indice of participant
33
61
  constructor(idx: number, m: string) {
34
62
  super(m);
@@ -37,112 +65,211 @@ export class InvalidContributionErr extends Error {
37
65
  }
38
66
 
39
67
  // Utils
40
- const { taggedHash, pointToBytes } = schnorr.utils;
41
- const Point = secp256k1.Point;
68
+ // MuSig2 reuses BIP340 tagged hashing, i.e. SHA256(SHA256(tag) || SHA256(tag) || msg...),
69
+ // for all of the domain-separated hashes below (KeyAgg list, noncecoef, challenge, aux, ...).
70
+ const taggedHash = /* @__PURE__ */ (() => schnorr.utils.taggedHash)();
71
+ // BIP327 uses xbytes(P) = bytes(32, x(P)) for aggregate keys, nonce/challenge hashes,
72
+ // and final signatures, so this alias is intentionally x-only instead of 33-byte SEC1.
73
+ const pointToBytes = /* @__PURE__ */ (() => schnorr.utils.pointToBytes)();
74
+ // MuSig2 keeps aggregate keys/nonces as full secp256k1 points so it can represent infinity
75
+ // and inspect parity before exporting compressed or x-only encodings at the API boundaries.
76
+ const Point = /* @__PURE__ */ (() => secp256k1.Point)();
42
77
  type Point = typeof Point.BASE;
43
- const Fn = Point.Fn;
44
- const PUBKEY_LEN = secp256k1.lengths.publicKey!;
45
- const ZERO = new Uint8Array(PUBKEY_LEN); // Compressed zero point
78
+ // MuSig2 scalars live in Z_n with fixed 32-byte encodings: strict inputs like secret keys
79
+ // use Fn.fromBytes(...), tweak bytes allow 0 per ApplyTweak, and hash-derived values are
80
+ // intentionally reduced mod n.
81
+ const Fn = /* @__PURE__ */ (() => Point.Fn)();
82
+ // BIP327 signer keys are plain compressed pubkeys (33 bytes); only aggregate exports switch
83
+ // to BIP340's 32-byte x-only format, so the internal input length stays on secp256k1.publicKey.
84
+ const PUBKEY_LEN = /* @__PURE__ */ (() => secp256k1.lengths.publicKey!)();
85
+ // BIP327 uses bytes(33, 0) both as cbytes_ext(Point.ZERO) for infinity and as GetSecondKey's
86
+ // "no second distinct key" sentinel, so this all-zero compressed slot is intentionally out-of-band.
87
+ const ZERO = /* @__PURE__ */ new Uint8Array(PUBKEY_LEN); // Compressed zero point
88
+ // Be friendly to bad ECMAScript parsers by not using bigint literals.
89
+ // prettier-ignore
90
+ const _0n = /* @__PURE__ */ BigInt(0), _1n = /* @__PURE__ */ BigInt(1);
46
91
 
47
92
  // Encoding
48
93
  // TODO: re-use in PSBT?
49
- const compressed = P.apply(P.bytes(33), {
50
- decode: (p: Point) => (isZero(p) ? ZERO : p.toBytes(true)),
51
- encode: (b: Uint8Array) => (equalBytes(b, ZERO) ? Point.ZERO : Point.fromBytes(b)),
52
- });
53
- const scalar = P.validate(P.U256BE, (n) => {
54
- aInRange('n', n, 1n, Fn.ORDER);
55
- return n;
56
- });
57
- const PubNonce = P.struct({ R1: compressed, R2: compressed });
58
- const SecretNonce = P.struct({ k1: scalar, k2: scalar, publicKey: P.bytes(PUBKEY_LEN) });
94
+ // This is BIP327's cbytes_ext/cpoint_ext adapter: normal points stay in compressed SEC1,
95
+ // while Point.ZERO maps to bytes(33, 0) as the out-of-band infinity sentinel for aggnonce.
96
+ const compressed = /* @__PURE__ */ (() =>
97
+ P.apply(P.bytes(33), {
98
+ decode: (p: Point) => (isZero(p) ? ZERO : p.toBytes(true)),
99
+ encode: (b: TArg<Uint8Array>) => (equalBytes(b, ZERO) ? Point.ZERO : Point.fromBytes(b)),
100
+ }))();
101
+ // This coder is only for stored secnonce limbs k1/k2, which BIP327 requires to be
102
+ // nonzero scalars in [1, n); tweak scalars use different validation because 0 is allowed there.
103
+ const scalar = /* @__PURE__ */ (() =>
104
+ P.validate(P.U256BE, (n) => {
105
+ aInRange('n', n, _1n, Fn.ORDER);
106
+ return n;
107
+ }))();
108
+ // Shared for both per-signer pubnonce bytes and aggregate aggnonce bytes. Because it accepts
109
+ // the BIP327 infinity sentinel, so individual pubnonce callers still need
110
+ // an explicit zero-point check.
111
+ const PubNonce = /* @__PURE__ */ (() => P.struct({ R1: compressed, R2: compressed }))();
112
+ // BIP327 stores secnonce as k1 || k2 || pk so Sign can reject reused or
113
+ // invalid nonce limbs and detect when the caller pairs the nonce with a
114
+ // different individual public key than NonceGen used.
115
+ const SecretNonce = /* @__PURE__ */ (() =>
116
+ P.struct({
117
+ k1: scalar,
118
+ k2: scalar,
119
+ publicKey: P.bytes(PUBKEY_LEN),
120
+ }))();
59
121
 
60
- function abytesOptional(b: Uint8Array | undefined, ...lengths: number[]) {
122
+ function abytesOptional(b: TArg<Uint8Array | undefined>, ...lengths: number[]) {
123
+ // Optional-byte helper: exact-length checks only happen when callers pass them explicitly.
61
124
  if (b !== undefined) abytes(b, ...lengths);
62
125
  }
63
126
 
64
- function abytesArray(lst: Uint8Array[], ...lengths: number[]) {
65
- if (!Array.isArray(lst)) throw new Error('expected array');
127
+ function abytesArray(lst: TArg<Uint8Array[]>, ...lengths: number[]) {
128
+ // Element-shape helper only: callers still enforce list-size rules like BIP327's 0 < u < 2^32.
129
+ if (!Array.isArray(lst)) throw new TypeError('expected array');
66
130
  lst.forEach((i) => abytes(i, ...lengths));
67
131
  }
68
132
 
69
133
  function aXonly(lst: boolean[]) {
70
- if (!Array.isArray(lst)) throw new Error('expected array');
134
+ // BIP327 tweak modes are strict booleans; callers should run this before
135
+ // branching on isXonly values because plain JS truthiness would accept invalid inputs.
136
+ if (!Array.isArray(lst)) throw new TypeError('expected array');
71
137
  lst.forEach((i, j) => {
72
138
  if (typeof i !== 'boolean')
73
- throw new Error('expected boolean in xOnly array, got' + i + '(' + j + ')');
139
+ throw new TypeError('expected boolean in xOnly array, got' + i + '(' + j + ')');
74
140
  });
75
141
  }
76
142
 
77
- const taggedInt = (tag: string, ...messages: Uint8Array[]) =>
143
+ // BIP327/BIP340 treat tagged-hash outputs as big-endian integers reduced mod n for
144
+ // coefficients, nonce scalars, and challenges; this helper is that int(hash_tag(...)) mod n step.
145
+ const taggedInt = (tag: string, ...messages: TArg<Uint8Array[]>) =>
78
146
  Fn.create(Fn.fromBytes(taggedHash(tag, ...messages), true));
147
+ // BIP327 repeatedly says "use x if the point has even Y, otherwise use n - x"; this
148
+ // helper is that parity-conditioned scalar negation for nonce limbs and Q-dependent signs.
79
149
  const evenScalar = (p: Point, n: bigint) => (hasEven(p.y) ? n : Fn.neg(n));
80
150
 
81
151
  // Short utility for compat with reference implementation
82
- export function IndividualPubkey(seckey: Uint8Array): Uint8Array {
83
- return secp256k1.getPublicKey(seckey, true);
152
+ /**
153
+ * Derives a compressed secp256k1 public key from a private key.
154
+ * @param seckey - signer private key
155
+ * @returns Compressed public key bytes.
156
+ * @example
157
+ * Turn a signer's secret key into the compressed key format MuSig2 expects.
158
+ * ```ts
159
+ * import { schnorr } from '@noble/curves/secp256k1.js';
160
+ * import { IndividualPubkey } from '@scure/btc-signer/musig2.js';
161
+ * IndividualPubkey(schnorr.utils.randomSecretKey());
162
+ * ```
163
+ */
164
+ // BIP327 IndividualPubkey is cbytes(d*G): the 33-byte compressed signer key.
165
+ // Aggregate exports use separate x-only encoding and should not call this helper.
166
+ export function IndividualPubkey(seckey: TArg<Uint8Array>): TRet<Uint8Array> {
167
+ return secp256k1.getPublicKey(seckey, true) as TRet<Uint8Array>;
84
168
  }
85
169
  // Same, but returns Point
170
+ // Base-point multiply helper for the BIP327 x*G steps below. Point.BASE.multiply rejects 0,
171
+ // so call sites that allow zero scalars need to handle that case explicitly instead of using this.
86
172
  function mulBase(n: bigint): Point {
87
173
  return Point.BASE.multiply(n);
88
174
  }
175
+ // Local alias for BIP327's is_infinite(P): used both for rejecting infinity where cpoint(...)
176
+ // must never yield it and for the "if R' is infinite, use G" nonce-aggregation fallback.
89
177
  function isZero(point: Point): boolean {
90
178
  return point.equals(Point.ZERO);
91
179
  }
92
180
 
93
181
  /**
94
182
  * Lexicographically sorts an array of public keys.
95
- * @param publicKeys An array of public keys (Uint8Array).
183
+ * @param publicKeys - array of public keys
96
184
  * @returns A new array containing the sorted public keys.
97
- * @throws {Error} If the input is not an array or if any element is not a Uint8Array of the correct length.
185
+ * @throws On wrong argument types. {@link TypeError}
186
+ * @throws On wrong argument ranges or values. {@link RangeError}
187
+ * @example
188
+ * Sort participant public keys before building the aggregate MuSig2 key.
189
+ * ```ts
190
+ * import { IndividualPubkey, sortKeys } from '@scure/btc-signer/musig2.js';
191
+ * import { randomPrivateKeyBytes } from '@scure/btc-signer/utils.js';
192
+ * sortKeys([
193
+ * IndividualPubkey(randomPrivateKeyBytes()),
194
+ * IndividualPubkey(randomPrivateKeyBytes()),
195
+ * ]);
196
+ * ```
98
197
  */
99
- export function sortKeys(publicKeys: Uint8Array[]): Uint8Array[] {
198
+ export function sortKeys(publicKeys: TArg<Uint8Array[]>): TRet<Uint8Array[]> {
199
+ // BIP327 KeySort is defined for non-empty signer-key lists, and this helper returns a sorted copy
200
+ // so callers do not lose their original participant ordering as a side effect of key aggregation.
100
201
  abytesArray(publicKeys, PUBKEY_LEN);
101
- return publicKeys.sort(compareBytes);
202
+ if (!publicKeys.length) throw new RangeError('sortKeys: expected non-empty signer key list');
203
+ return Array.from(publicKeys).sort(compareBytes) as TRet<Uint8Array[]>;
102
204
  }
103
205
 
104
206
  // Finds second distinct key (to make coefficient 1)
105
- function getSecondKey(publicKeys: Uint8Array[]): Uint8Array {
207
+ function getSecondKey(publicKeys: TArg<Uint8Array[]>): TRet<Uint8Array> {
208
+ // BIP327 GetSecondKey returns bytes(33, 0) when all signer keys are equal; that sentinel
209
+ // means no key hits the special pk' = pk2 coefficient-1 shortcut in the all-equal case.
106
210
  abytesArray(publicKeys, PUBKEY_LEN);
107
211
  for (let j = 1; j < publicKeys.length; j++)
108
- if (!equalBytes(publicKeys[j], publicKeys[0])) return publicKeys[j];
109
- return ZERO;
212
+ if (!equalBytes(publicKeys[j], publicKeys[0])) return publicKeys[j] as TRet<Uint8Array>;
213
+ return ZERO as TRet<Uint8Array>;
110
214
  }
111
215
 
112
- function keyAggL(publicKeys: Uint8Array[]) {
216
+ function keyAggL(publicKeys: TArg<Uint8Array[]>): TRet<Uint8Array> {
217
+ // BIP327 HashKeys hashes keys in the caller-provided order. Run KeySort first when
218
+ // the surrounding protocol requires the canonical lexicographic participant ordering.
113
219
  abytesArray(publicKeys, PUBKEY_LEN);
114
- return taggedHash('KeyAgg list', ...publicKeys);
220
+ return taggedHash('KeyAgg list', ...publicKeys) as TRet<Uint8Array>;
115
221
  }
116
222
 
117
223
  function keyAggCoeffInternal(
118
- publicKey1: Uint8Array,
119
- publicKey2: Uint8Array,
120
- L: Uint8Array
224
+ publicKey1: TArg<Uint8Array>,
225
+ publicKey2: TArg<Uint8Array>,
226
+ L: TArg<Uint8Array>
121
227
  ): bigint {
228
+ // BIP327 only short-circuits to coefficient 1 for pk' = pk2. When all keys are equal,
229
+ // pk2 is the all-zero sentinel from GetSecondKey, so every real signer key still hashes.
122
230
  abytes(publicKey1, PUBKEY_LEN);
123
231
  abytes(publicKey2, PUBKEY_LEN);
124
- if (equalBytes(publicKey1, publicKey2)) return 1n;
232
+ if (equalBytes(publicKey1, publicKey2)) return _1n;
125
233
  return taggedInt('KeyAgg coefficient', L, publicKey1);
126
234
  }
127
235
 
128
236
  /**
129
237
  * Aggregates multiple public keys using the MuSig2 key aggregation algorithm.
130
- * @param publicKeys An array of individual public keys (Uint8Array).
131
- * @param tweaks An optional array of tweaks (Uint8Array) to apply to the aggregate public key.
132
- * @param isXonly An optional array of booleans indicating whether each tweak is an X-only tweak.
133
- * @returns An object containing the aggregate public key, accumulated sign, and accumulated tweak.
134
- * @throws {Error} If the input is invalid, such as non array publicKeys, tweaks and isXonly array length not matching.
135
- * @throws {InvalidContributionErr} If any of the public keys are invalid and cannot be processed.
238
+ * @param publicKeys - individual participant public keys
239
+ * @param tweaks - optional tweaks applied to the aggregate key
240
+ * @param isXonly - whether each tweak uses x-only semantics
241
+ * @returns An object containing the aggregate public key, accumulated sign,
242
+ * and accumulated tweak.
243
+ * @throws If the input is invalid, such as non-array public keys or mismatched
244
+ * tweak metadata. {@link Error}
245
+ * @throws On wrong argument types. {@link TypeError}
246
+ * @throws On wrong argument ranges or values. {@link RangeError}
247
+ * @throws If any of the public keys are invalid and cannot be processed.
248
+ * {@link InvalidContributionErr}
249
+ * @example
250
+ * Combine all participant public keys into the shared MuSig2 context.
251
+ * ```ts
252
+ * import { IndividualPubkey, keyAggregate } from '@scure/btc-signer/musig2.js';
253
+ * import { randomPrivateKeyBytes } from '@scure/btc-signer/utils.js';
254
+ * keyAggregate([
255
+ * IndividualPubkey(randomPrivateKeyBytes()),
256
+ * IndividualPubkey(randomPrivateKeyBytes()),
257
+ * ]);
258
+ * ```
136
259
  */
137
260
  export function keyAggregate(
138
- publicKeys: Uint8Array[],
139
- tweaks: Uint8Array[] = [],
261
+ publicKeys: TArg<Uint8Array[]>,
262
+ tweaks: TArg<Uint8Array[]> = [],
140
263
  isXonly: boolean[] = []
141
- ) {
264
+ ): KeyAggregate {
265
+ // BIP327 KeyAgg inputs require `0 < u < 2^32`, and ApplyTweak consumes a one-for-one
266
+ // list of boolean tweak modes; callers should enforce that public contract here.
142
267
  abytesArray(publicKeys, PUBKEY_LEN);
268
+ if (publicKeys.length < 1) throw new RangeError('keyAggregate: expected at least 1 public key');
143
269
  abytesArray(tweaks, 32);
270
+ aXonly(isXonly);
144
271
  if (tweaks.length !== isXonly.length)
145
- throw new Error('The tweaks and isXonly arrays must have the same length');
272
+ throw new RangeError('The tweaks and isXonly arrays must have the same length');
146
273
  // Aggregate
147
274
  const pk2 = getSecondKey(publicKeys);
148
275
  const L = keyAggL(publicKeys);
@@ -156,13 +283,19 @@ export function keyAggregate(
156
283
  }
157
284
  aggPublicKey = aggPublicKey.add(Pi.multiply(keyAggCoeffInternal(publicKeys[i], pk2, L)));
158
285
  }
286
+ // BIP327 KeyAggInternal: "Fail if is_infinite(Q)". Computationally unreachable for
287
+ // hash-derived coefficients, but the spec mandates the explicit check before tweaking.
288
+ if (isZero(aggPublicKey))
289
+ throw new Error('keyAggregate: aggregate public key cannot be infinity');
159
290
  let gAcc = Fn.ONE;
160
291
  let tweakAcc = Fn.ZERO;
161
292
  // Apply tweaks
162
293
  for (let i = 0; i < tweaks.length; i++) {
163
294
  const g = isXonly[i] && !hasEven(aggPublicKey.y) ? Fn.neg(Fn.ONE) : Fn.ONE;
164
- const t = Fn.fromBytes(tweaks[i]);
165
- aggPublicKey = aggPublicKey.multiply(g).add(mulBase(t));
295
+ // BIP327 ApplyTweak: `Let t = int(tweak); fail if t >= n`, so 32-byte zero tweaks are valid.
296
+ const t = Fn.fromBytes(tweaks[i], true);
297
+ if (!Fn.isValid(t)) throw new RangeError('invalid scalar: out of range');
298
+ aggPublicKey = aggPublicKey.multiply(g).add(Fn.is0(t) ? Point.ZERO : mulBase(t));
166
299
  if (isZero(aggPublicKey)) throw new Error('The result of tweaking cannot be infinity');
167
300
  gAcc = Fn.mul(g, gAcc);
168
301
  tweakAcc = Fn.add(t, Fn.mul(g, tweakAcc));
@@ -171,29 +304,49 @@ export function keyAggregate(
171
304
  }
172
305
  /**
173
306
  * Exports the aggregate public key to a byte array.
174
- * @param ctx The result of the keyAggregate function.
307
+ * @param ctx - result of {@link keyAggregate}
175
308
  * @returns The aggregate public key as a byte array.
309
+ * @example
310
+ * Serialize the aggregate key after building the MuSig2 context.
311
+ * ```ts
312
+ * import { IndividualPubkey, keyAggregate, keyAggExport } from '@scure/btc-signer/musig2.js';
313
+ * import { randomPrivateKeyBytes } from '@scure/btc-signer/utils.js';
314
+ * const ctx = keyAggregate([
315
+ * IndividualPubkey(randomPrivateKeyBytes()),
316
+ * IndividualPubkey(randomPrivateKeyBytes()),
317
+ * ]);
318
+ * keyAggExport(ctx);
319
+ * ```
176
320
  */
177
- export function keyAggExport(ctx: ReturnType<typeof keyAggregate>): Uint8Array {
178
- return pointToBytes(ctx.aggPublicKey);
321
+ export function keyAggExport(ctx: ReturnType<typeof keyAggregate>): TRet<Uint8Array> {
322
+ validateObject(ctx as Record<string, any>, {}, {}, 'ctx');
323
+ if (!(ctx.aggPublicKey instanceof Point))
324
+ throw new TypeError('"ctx.aggPublicKey" expected point, got type=' + typeof ctx.aggPublicKey);
325
+ // BIP327 GetXonlyPubkey returns xbytes(Q), so this is the 32-byte x-only aggregate key
326
+ // instead of the 33-byte compressed SEC1 form.
327
+ return pointToBytes(ctx.aggPublicKey) as TRet<Uint8Array>;
179
328
  }
180
329
 
181
- function aux(secret: Uint8Array, rand: Uint8Array): Uint8Array {
330
+ function aux(secret: TArg<Uint8Array>, rand: TArg<Uint8Array>): TRet<Uint8Array> {
331
+ // BIP327 NonceGen and DeterministicSign blind the caller-provided 32-byte randomness with
332
+ // hash_MuSig/aux(rand) before hashing the session inputs into nonce scalars.
182
333
  const rand2 = taggedHash('MuSig/aux', rand);
183
334
  if (secret.length !== rand2.length) throw new Error('Cannot XOR arrays of different lengths');
184
335
  const res = new Uint8Array(secret.length);
185
336
  for (let i = 0; i < secret.length; i++) res[i] = secret[i] ^ rand2[i];
186
- return res;
337
+ return res as TRet<Uint8Array>;
187
338
  }
188
339
 
189
340
  const nonceHash = (
190
- rand: Uint8Array,
191
- publicKey: Uint8Array,
192
- aggPublicKey: Uint8Array,
341
+ rand: TArg<Uint8Array>,
342
+ publicKey: TArg<Uint8Array>,
343
+ aggPublicKey: TArg<Uint8Array>,
193
344
  i: number,
194
- msgPrefixed: Uint8Array,
195
- extraIn: Uint8Array
345
+ msgPrefixed: TArg<Uint8Array>,
346
+ extraIn: TArg<Uint8Array>
196
347
  ): bigint =>
348
+ // BIP327 NonceGen hashes rand || len(pk) || pk || len(aggpk) || aggpk || m_prefixed ||
349
+ // len(extra_in) || extra_in || bytes(1, i - 1), so callers pass i = 0/1 here.
197
350
  taggedInt(
198
351
  'MuSig/nonce',
199
352
  rand,
@@ -209,32 +362,44 @@ const nonceHash = (
209
362
 
210
363
  /**
211
364
  * Generates a nonce pair (public and secret) for MuSig2 signing.
212
- * @param publicKey The individual public key of the signer (Uint8Array).
213
- * @param secretKey The secret key of the signer (Uint8Array). Optional, included to xor randomness
214
- * @param aggPublicKey The aggregate public key of all signers (Uint8Array).
215
- * @param msg The message to be signed (Uint8Array).
216
- * @param extraIn Extra input for nonce generation (Uint8Array).
217
- * @param rand Random 32-bytes for generating the nonces (Uint8Array).
365
+ * @param publicKey - individual public key of the signer
366
+ * @param secretKey - optional secret key, mixed in to blind the randomness source
367
+ * @param aggPublicKey - aggregate public key of all signers
368
+ * @param msg - message to be signed
369
+ * @param extraIn - extra input mixed into nonce generation
370
+ * @param rand - random 32-byte seed for the nonce derivation
218
371
  * @returns An object containing the public and secret nonces.
219
- * @throws {Error} If the input is invalid, such as non array publicKey, secretKey, aggPublicKey.
372
+ * @throws If the input is invalid, such as non-array public keys or malformed
373
+ * nonce inputs. {@link Error}
374
+ * @throws On wrong argument ranges or values. {@link RangeError}
375
+ * @example
376
+ * Generate the signer-local nonce pair before sharing the public nonce with peers.
377
+ * ```ts
378
+ * import { IndividualPubkey, nonceGen } from '@scure/btc-signer/musig2.js';
379
+ * import { randomPrivateKeyBytes } from '@scure/btc-signer/utils.js';
380
+ * const secretKey = randomPrivateKeyBytes();
381
+ * nonceGen(IndividualPubkey(secretKey), secretKey);
382
+ * ```
220
383
  */
221
384
  export function nonceGen(
222
- publicKey: Uint8Array,
223
- secretKey?: Uint8Array,
224
- aggPublicKey: Uint8Array = new Uint8Array(0),
225
- msg?: Uint8Array,
226
- extraIn: Uint8Array = new Uint8Array(0),
227
- rand: Uint8Array = randomBytes(32)
228
- ): Nonces {
385
+ publicKey: TArg<Uint8Array>,
386
+ secretKey?: TArg<Uint8Array>,
387
+ aggPublicKey: TArg<Uint8Array> = new Uint8Array(0),
388
+ msg?: TArg<Uint8Array>,
389
+ extraIn: TArg<Uint8Array> = new Uint8Array(0),
390
+ rand: TArg<Uint8Array> = randomBytes(32)
391
+ ): TRet<Nonces> {
229
392
  abytes(publicKey, PUBKEY_LEN);
230
393
  abytesOptional(secretKey, 32);
231
394
  abytes(aggPublicKey);
232
- if (![0, 32].includes(aggPublicKey.length)) throw new Error('wrong aggPublicKey');
395
+ if (![0, 32].includes(aggPublicKey.length)) throw new RangeError('wrong aggPublicKey');
233
396
  abytesOptional(msg);
234
397
  abytes(extraIn);
235
398
  abytes(rand, 32);
236
399
 
237
400
  if (secretKey !== undefined) rand = aux(secretKey, rand);
401
+ // BIP327 distinguishes an omitted message from an explicitly empty one so the two cases
402
+ // derive different nonces even when every other session parameter matches.
238
403
  const msgPrefixed =
239
404
  msg !== undefined
240
405
  ? concatBytes(Uint8Array.of(1), numberToBytesBE(msg.length, 8), msg)
@@ -244,18 +409,38 @@ export function nonceGen(
244
409
  return {
245
410
  secret: SecretNonce.encode({ k1, k2, publicKey }),
246
411
  public: PubNonce.encode({ R1: mulBase(k1), R2: mulBase(k2) }),
247
- };
412
+ } as TRet<Nonces>;
248
413
  }
249
414
 
250
415
  /**
251
416
  * Aggregates public nonces from multiple signers into a single aggregate nonce.
252
- * @param pubNonces An array of public nonces from each signer (Uint8Array). Each pubnonce is assumed to be 66 bytes (two 33‐byte parts).
417
+ * @param pubNonces - public nonces from each signer
253
418
  * @returns The aggregate nonce (Uint8Array).
254
- * @throws {Error} If the input is not an array or if any element is not a Uint8Array of the correct length.
255
- * @throws {InvalidContributionErr} If any of the public nonces are invalid and cannot be processed.
419
+ * @throws If the nonce payloads are malformed or contain infinity points. {@link Error}
420
+ * @throws On wrong argument types. {@link TypeError}
421
+ * @throws On wrong argument ranges or values. {@link RangeError}
422
+ * @throws If any of the public nonces are invalid and cannot be processed.
423
+ * {@link InvalidContributionErr}
424
+ * @example
425
+ * Combine all participant public nonces before building the session.
426
+ * ```ts
427
+ * import { IndividualPubkey, nonceAggregate, nonceGen } from '@scure/btc-signer/musig2.js';
428
+ * import { randomPrivateKeyBytes } from '@scure/btc-signer/utils.js';
429
+ * const alice = randomPrivateKeyBytes();
430
+ * const bob = randomPrivateKeyBytes();
431
+ * nonceAggregate([
432
+ * nonceGen(IndividualPubkey(alice), alice).public,
433
+ * nonceGen(IndividualPubkey(bob), bob).public,
434
+ * ]);
435
+ * ```
256
436
  */
257
- export function nonceAggregate(pubNonces: Uint8Array[]): Uint8Array {
437
+ export function nonceAggregate(pubNonces: TArg<Uint8Array[]>): TRet<Uint8Array> {
258
438
  abytesArray(pubNonces, 66);
439
+ // BIP327 NonceAgg input: "The number u of pubnonces with 0 < u < 2^32".
440
+ // cbytes_ext uses bytes(33, 0) as the infinity sentinel for summed real contributions,
441
+ // not as an "empty aggregate" encoding.
442
+ if (pubNonces.length < 1)
443
+ throw new RangeError('nonceAggregate: expected at least 1 public nonce');
259
444
  let R1 = Point.ZERO;
260
445
  let R2 = Point.ZERO;
261
446
  for (let i = 0; i < pubNonces.length; i++) {
@@ -269,12 +454,45 @@ export function nonceAggregate(pubNonces: Uint8Array[]): Uint8Array {
269
454
  throw new InvalidContributionErr(i, 'pubnonce');
270
455
  }
271
456
  }
272
- return PubNonce.encode({ R1, R2 });
457
+ return PubNonce.encode({ R1, R2 }) as TRet<Uint8Array>;
273
458
  }
274
459
 
275
460
  // Class allows us re-use pre-computed stuff
276
- // NOTE: it would be nice to aggregate nonce in construdctor, but there is test that passes already aggregated nonce here.
461
+ // NOTE: it would be nice to aggregate nonce in constructor, but there is a
462
+ // test that passes an already aggregated nonce here.
463
+ /**
464
+ * MuSig2 session context for partial signing and aggregation.
465
+ * @param aggNonce - aggregate nonce from all participants combined
466
+ * @param publicKeys - all participant public keys
467
+ * @param msg - message to be signed
468
+ * @param tweaks - optional tweaks applied to the aggregate public key
469
+ * @param isXonly - whether each tweak uses x-only semantics
470
+ * @example
471
+ * Build one session object and reuse it for all partial-signature steps.
472
+ * ```ts
473
+ * import {
474
+ * IndividualPubkey,
475
+ * Session,
476
+ * keyAggregate,
477
+ * keyAggExport,
478
+ * nonceAggregate,
479
+ * nonceGen,
480
+ * } from '@scure/btc-signer/musig2.js';
481
+ * import { randomPrivateKeyBytes } from '@scure/btc-signer/utils.js';
482
+ * const alice = randomPrivateKeyBytes();
483
+ * const bob = randomPrivateKeyBytes();
484
+ * const publicKeys = [IndividualPubkey(alice), IndividualPubkey(bob)];
485
+ * const agg = keyAggregate(publicKeys);
486
+ * const aggNonce = nonceAggregate([
487
+ * nonceGen(publicKeys[0], alice, keyAggExport(agg)).public,
488
+ * nonceGen(publicKeys[1], bob, keyAggExport(agg)).public,
489
+ * ]);
490
+ * const msg = new TextEncoder().encode('hello musig2');
491
+ * new Session(aggNonce, publicKeys, msg);
492
+ * ```
493
+ */
277
494
  export class Session {
495
+ private aggNonce: Uint8Array;
278
496
  private publicKeys: Uint8Array[];
279
497
  private Q: Point;
280
498
  private gAcc: bigint;
@@ -290,12 +508,12 @@ export class Session {
290
508
  * Constructor for the Session class.
291
509
  * It precomputes and stores values derived from the aggregate nonce, public keys,
292
510
  * message, and optional tweaks, optimizing the signing process.
293
- * @param aggNonce The aggregate nonce (Uint8Array) from all participants combined, must be 66 bytes.
294
- * @param publicKeys An array of public keys (Uint8Array) from each participant, must be 33 bytes.
295
- * @param msg The message (Uint8Array) to be signed.
296
- * @param tweaks Optional array of tweaks (Uint8Array) to be applied to the aggregate public key, each must be 32 bytes. Defaults to [].
297
- * @param isXonly Optional array of booleans indicating whether each tweak is an X-only tweak. Defaults to [].
298
- * @throws {Error} If the input is invalid, such as wrong array sizes or lengths.
511
+ * @param aggNonce - aggregate nonce from all participants combined
512
+ * @param publicKeys - participant public keys
513
+ * @param msg - message to be signed
514
+ * @param tweaks - tweaks applied to the aggregate public key
515
+ * @param isXonly - whether each tweak uses x-only semantics
516
+ * @throws If the input is invalid, such as wrong array sizes or lengths. {@link Error}
299
517
  */
300
518
  constructor(
301
519
  aggNonce: Uint8Array,
@@ -304,37 +522,48 @@ export class Session {
304
522
  tweaks: Uint8Array[] = [],
305
523
  isXonly: boolean[] = []
306
524
  ) {
525
+ abytes(aggNonce, 66);
307
526
  abytesArray(publicKeys, 33);
308
527
  abytesArray(tweaks, 32);
309
528
  aXonly(isXonly);
310
529
  abytes(msg);
311
530
  if (tweaks.length !== isXonly.length)
312
- throw new Error('The tweaks and isXonly arrays must have the same length');
531
+ throw new RangeError('The tweaks and isXonly arrays must have the same length');
313
532
  const { aggPublicKey, gAcc, tweakAcc } = keyAggregate(publicKeys, tweaks, isXonly);
314
533
  const { R1, R2 } = PubNonce.decode(aggNonce);
315
- this.publicKeys = publicKeys;
534
+ // BIP327 requires cached session-context state to be protected from third-party mutation,
535
+ // so Session must own detached copies of the caller-provided arrays and byte entries.
536
+ // Use Uint8Array.from(...) here instead of .slice() because Node Buffer overrides slice()
537
+ // to return a shared-memory view, which would let later caller mutation rewrite session state.
538
+ this.aggNonce = Uint8Array.from(aggNonce);
539
+ this.publicKeys = publicKeys.map((pk) => Uint8Array.from(pk));
316
540
  this.Q = aggPublicKey;
317
541
  this.gAcc = gAcc;
318
542
  this.tweakAcc = tweakAcc;
319
543
  this.b = taggedInt('MuSig/noncecoef', aggNonce, pointToBytes(aggPublicKey), msg);
320
- const R = R1.add(R2.multiply(this.b));
544
+ // b and the nonce points are public session values, so the faster variable-time
545
+ // multiplication is safe here; it also matches the reference point_mul, which
546
+ // maps a (negligible-probability) zero coefficient to infinity instead of failing.
547
+ const R = R1.add(R2.multiplyUnsafe(this.b));
321
548
  this.R = isZero(R) ? Point.BASE : R;
322
549
  this.e = taggedInt('BIP0340/challenge', pointToBytes(this.R), pointToBytes(aggPublicKey), msg);
323
- this.tweaks = tweaks;
324
- this.isXonly = isXonly;
325
- this.L = keyAggL(publicKeys);
326
- this.secondKey = getSecondKey(publicKeys);
550
+ this.tweaks = tweaks.map((t) => Uint8Array.from(t));
551
+ this.isXonly = isXonly.slice();
552
+ this.L = keyAggL(this.publicKeys);
553
+ this.secondKey = getSecondKey(this.publicKeys);
327
554
  }
328
555
  /**
329
556
  * Calculates the key aggregation coefficient for a given point.
330
557
  * @private
331
- * @param P The point to calculate the coefficient for.
558
+ * @param P - point to calculate the coefficient for
332
559
  * @returns The key aggregation coefficient as a bigint.
333
- * @throws {Error} If the provided public key is not included in the list of pubkeys.
560
+ * @throws If the provided public key is not included in the list of pubkeys. {@link Error}
334
561
  */
335
562
  private getSessionKeyAggCoeff(P: Point): bigint {
336
563
  const { publicKeys } = this;
337
564
  const pk = P.toBytes(true);
565
+ // BIP327 GetSessionKeyAggCoeff fails if cbytes(P) is not one of the session pubkeys;
566
+ // once membership is confirmed, the cached L/secondKey state is enough to derive KeyAggCoeff.
338
567
  const found = publicKeys.some((p) => equalBytes(p, pk));
339
568
  if (!found) throw new Error("The signer's pubkey must be included in the list of pubkeys");
340
569
  return keyAggCoeffInternal(pk, this.secondKey, this.L);
@@ -347,36 +576,45 @@ export class Session {
347
576
  const { Q, gAcc, b, R, e } = this;
348
577
  const s = Fn.fromBytes(partialSig, true);
349
578
  if (!Fn.isValid(s)) return false;
579
+ // BIP327 PartialSigVerifyInternal: `Let s = int(psig); fail if s >= n`, so s=0 must stay
580
+ // in the public verification equation and return false on mismatch instead of throwing.
350
581
  const { R1, R2 } = PubNonce.decode(publicNonce);
351
- const Re_s_ = R1.add(R2.multiply(b));
582
+ // Verification only handles public data (nonces, pubkeys, hash-derived scalars),
583
+ // so the faster variable-time multiplications are safe here; they also match the
584
+ // reference point_mul, which maps zero scalars to infinity instead of failing.
585
+ const Re_s_ = R1.add(R2.multiplyUnsafe(b));
352
586
  const Re_s = hasEven(R.y) ? Re_s_ : Re_s_.negate();
353
587
  const P = Point.fromBytes(publicKey);
354
588
  const a = this.getSessionKeyAggCoeff(P);
355
- const g = Fn.mul(evenScalar(Q, 1n), gAcc);
356
- const left = mulBase(s);
357
- const right = Re_s.add(P.multiply(Fn.mul(e, Fn.mul(a, g))));
589
+ const g = Fn.mul(evenScalar(Q, _1n), gAcc);
590
+ const left = Point.BASE.multiplyUnsafe(s);
591
+ const right = Re_s.add(P.multiplyUnsafe(Fn.mul(e, Fn.mul(a, g))));
358
592
  return left.equals(right);
359
593
  }
360
594
 
361
595
  /**
362
- * Generates a partial signature for a given message, secret nonce, secret key, and session context.
363
- * @param secretNonce The secret nonce for this signing session (Uint8Array). MUST be securely erased after use.
364
- * @param secret The secret key of the signer (Uint8Array).
365
- * @param sessionCtx The session context containing all necessary information for signing.
366
- * @param fastSign if set to true, the signature is created without checking validity.
596
+ * Generates a partial signature for a given message, secret nonce,
597
+ * secret key, and session context.
598
+ * @param secretNonce - secret nonce for this signing session; it is zeroed after use
599
+ * @param secret - secret key of the signer
600
+ * @param fastSign - if `true`, skip the self-verification pass
367
601
  * @returns The partial signature (Uint8Array).
368
- * @throws {Error} If the input is invalid, such as wrong array sizes, invalid nonce or secret key.
602
+ * @throws If the input is invalid, such as wrong array sizes,
603
+ * invalid nonce, or invalid secret key. {@link Error}
369
604
  */
370
605
  sign(secretNonce: Uint8Array, secret: Uint8Array, fastSign = false): Uint8Array {
371
606
  abytes(secret, 32);
372
- if (typeof fastSign !== 'boolean') throw new Error('expected boolean');
607
+ if (typeof fastSign !== 'boolean') throw new TypeError('expected boolean');
373
608
  const { Q, gAcc, b, R, e } = this;
374
609
  const { k1: k1_, k2: k2_, publicKey: originalPk } = SecretNonce.decode(secretNonce);
375
610
  // zero-out the first 64 bytes of secretNonce so it cannot be reused
376
- // TODO: this was in reference implementation, but feels very broken. Modifying input arguments is pretty bad.
611
+ // BIP327 permits overwriting the first 64 secnonce bytes after reading k1/k2 so
612
+ // accidental reuse fails fast instead of reusing the same nonce scalars.
613
+ // TODO: this was in the reference implementation, but feels very broken.
614
+ // Modifying input arguments is pretty bad.
377
615
  secretNonce.fill(0, 0, 64);
378
616
  if (!Fn.isValid(k1_)) throw new Error('wrong k1');
379
- if (!Fn.isValid(k2_)) throw new Error('wrong k1');
617
+ if (!Fn.isValid(k2_)) throw new Error('wrong k2');
380
618
  const k1 = evenScalar(R, k1_);
381
619
  const k2 = evenScalar(R, k2_);
382
620
  const d_ = Fn.fromBytes(secret);
@@ -385,7 +623,7 @@ export class Session {
385
623
  const pk = P.toBytes(true);
386
624
  if (!equalBytes(pk, originalPk)) throw new Error('Public key does not match nonceGen argument');
387
625
  const a = this.getSessionKeyAggCoeff(P);
388
- const g = evenScalar(Q, 1n);
626
+ const g = evenScalar(Q, _1n);
389
627
  const d = Fn.mul(g, Fn.mul(gAcc, d_));
390
628
  /// k1 + (b*k2) + (e*a*d)
391
629
  const s = Fn.add(k1, Fn.add(Fn.mul(b, k2), Fn.mul(e, Fn.mul(a, d))));
@@ -403,15 +641,12 @@ export class Session {
403
641
  }
404
642
  /**
405
643
  * Verifies a partial signature against the aggregate public key and other session parameters.
406
- * @param partialSig The partial signature to verify (Uint8Array).
407
- * @param pubNonces An array of public nonces from each signer (Uint8Array).
408
- * @param pubKeys An array of public keys from each signer (Uint8Array).
409
- * @param tweaks An array of tweaks applied to the aggregate public key.
410
- * @param isXonly An array of booleans indicating whether each tweak is an X-only tweak.
411
- * @param msg The message that was signed (Uint8Array).
412
- * @param i The index of the signer whose partial signature is being verified.
413
- * @returns True if the partial signature is valid, false otherwise.
414
- * @throws {Error} If the input is invalid, such as non array partialSig, pubNonces, pubKeys, tweaks.
644
+ * @param partialSig - partial signature to verify
645
+ * @param pubNonces - public nonces from each signer
646
+ * @param i - index of the signer whose partial signature is being verified
647
+ * @returns `true` if the partial signature is valid.
648
+ * @throws If the input is invalid, such as non-array partial signatures
649
+ * or mismatched nonce counts. {@link Error}
415
650
  */
416
651
  partialSigVerify(partialSig: Uint8Array, pubNonces: Uint8Array[], i: number): boolean {
417
652
  const { publicKeys, tweaks, isXonly } = this;
@@ -422,39 +657,47 @@ export class Session {
422
657
  aXonly(isXonly);
423
658
  anumber(i);
424
659
  if (pubNonces.length !== publicKeys.length)
425
- throw new Error('The pubNonces and publicKeys arrays must have the same length');
660
+ throw new RangeError('The pubNonces and publicKeys arrays must have the same length');
426
661
  if (tweaks.length !== isXonly.length)
427
- throw new Error('The tweaks and isXonly arrays must have the same length');
428
- if (i >= pubNonces.length) throw new Error('index outside of pubKeys/pubNonces');
662
+ throw new RangeError('The tweaks and isXonly arrays must have the same length');
663
+ // BIP327 PartialSigVerify rebuilds session_ctx from aggnonce = NonceAgg(pubnonce_1..u),
664
+ // and GetSessionValues derives b and R from that aggnonce. This Session caches b/R/e from
665
+ // the constructor aggNonce, so a stale Session would otherwise accept mismatched pubNonces
666
+ // as long as pubNonces[i] still matched the signer slot.
667
+ if (i >= pubNonces.length) throw new RangeError('index outside of pubKeys/pubNonces');
668
+ if (!equalBytes(this.aggNonce, nonceAggregate(pubNonces))) return false;
429
669
  return this.partialSigVerifyInternal(partialSig, pubNonces[i], publicKeys[i]);
430
670
  }
431
671
  /**
432
672
  * Aggregates partial signatures from multiple signers into a single final signature.
433
- * @param partialSigs An array of partial signatures from each signer (Uint8Array).
434
- * @param sessionCtx The session context containing all necessary information for signing.
673
+ * @param partialSigs - partial signatures from each signer
435
674
  * @returns The final aggregate signature (Uint8Array).
436
- * @throws {Error} If the input is invalid, such as wrong array sizes, invalid signature.
675
+ * @throws If the input is invalid, such as wrong array sizes or malformed
676
+ * signatures. {@link Error}
437
677
  */
438
- partialSigAgg(partialSigs: Uint8Array[]): Uint8Array {
678
+ partialSigAgg(partialSigs: TArg<Uint8Array[]>): TRet<Uint8Array> {
439
679
  abytesArray(partialSigs, 32);
680
+ // BIP327 PartialSigAgg is defined for a non-empty psig_1..u list tied to this session_ctx;
681
+ // [] is not a valid aggregate-signature input even though the sum starts from zero.
682
+ if (partialSigs.length < 1) throw new RangeError('partialSigs.length must be >= 1');
440
683
  const { Q, tweakAcc, R, e } = this;
441
- let s = 0n;
684
+ let s = _0n;
442
685
  for (let i = 0; i < partialSigs.length; i++) {
443
686
  const si = Fn.fromBytes(partialSigs[i], true);
444
687
  if (!Fn.isValid(si)) throw new InvalidContributionErr(i, 'psig');
445
688
  s = Fn.add(s, si);
446
689
  }
447
- const g = evenScalar(Q, 1n);
690
+ const g = evenScalar(Q, _1n);
448
691
  s = Fn.add(s, Fn.mul(e, Fn.mul(g, tweakAcc))); // s + e * g * tweakAcc
449
- return concatBytes(pointToBytes(R), Fn.toBytes(s));
692
+ return concatBytes(pointToBytes(R), Fn.toBytes(s)) as TRet<Uint8Array>;
450
693
  }
451
694
  }
452
695
 
453
696
  const deterministicNonceHash = (
454
- secret: Uint8Array,
455
- aggOtherNonce: Uint8Array,
456
- aggPublicKey: Uint8Array,
457
- msg: Uint8Array,
697
+ secret: TArg<Uint8Array>,
698
+ aggOtherNonce: TArg<Uint8Array>,
699
+ aggPublicKey: TArg<Uint8Array>,
700
+ msg: TArg<Uint8Array>,
458
701
  i: number
459
702
  ): bigint =>
460
703
  taggedInt(
@@ -464,36 +707,62 @@ const deterministicNonceHash = (
464
707
  aggPublicKey,
465
708
  numberToBytesBE(msg.length, 8),
466
709
  msg,
710
+ // BIP327 hashes bytes(1, i - 1) for i=1,2; callers pass 0/1 directly here.
467
711
  new Uint8Array([i])
468
712
  );
469
713
 
470
714
  /**
471
715
  * Generates a nonce pair and partial signature deterministically for a single signer.
472
- * @param secret The secret key of the signer (Uint8Array).
473
- * @param aggOtherNonce The aggregate public nonce of all other signers (Uint8Array).
474
- * @param publicKeys An array of all signers' public keys (Uint8Array).
475
- * @param tweaks An array of tweaks to apply to the aggregate public key.
476
- * @param isXonly An array of booleans indicating whether each tweak is an X-only tweak.
477
- * @param msg The message to be signed (Uint8Array).
478
- * @param rand Optional extra randomness (Uint8Array).
716
+ * @param secret - secret key of the signer
717
+ * @param aggOtherNonce - aggregate public nonce of the other signers
718
+ * @param publicKeys - public keys of all signers
719
+ * @param msg - message to be signed
720
+ * @param tweaks - tweaks applied to the aggregate public key
721
+ * @param isXonly - whether each tweak uses x-only semantics
722
+ * @param rand - optional extra randomness
723
+ * @param fastSign - whether to skip partial-signature self-verification
479
724
  * @returns An object containing the public nonce and partial signature.
725
+ * @throws If MuSig2 session setup or signing fails. {@link Error}
726
+ * @throws On wrong argument types. {@link TypeError}
727
+ * @throws On wrong argument ranges or values. {@link RangeError}
728
+ * @throws If one of the aggregated public keys or nonces is invalid. {@link InvalidContributionErr}
729
+ * @example
730
+ * Generate one signer's deterministic nonce and partial signature in one step.
731
+ * ```ts
732
+ * import {
733
+ * IndividualPubkey,
734
+ * deterministicSign,
735
+ * keyAggregate,
736
+ * keyAggExport,
737
+ * nonceGen,
738
+ * } from '@scure/btc-signer/musig2.js';
739
+ * import { randomPrivateKeyBytes } from '@scure/btc-signer/utils.js';
740
+ * const alice = randomPrivateKeyBytes();
741
+ * const bob = randomPrivateKeyBytes();
742
+ * const publicKeys = [IndividualPubkey(alice), IndividualPubkey(bob)];
743
+ * const agg = keyAggregate(publicKeys);
744
+ * const otherNonce = nonceGen(publicKeys[1], bob, keyAggExport(agg)).public;
745
+ * deterministicSign(alice, otherNonce, publicKeys, new TextEncoder().encode('hello musig2'));
746
+ * ```
480
747
  */
481
748
  export function deterministicSign(
482
- secret: Uint8Array,
483
- aggOtherNonce: Uint8Array,
484
- publicKeys: Uint8Array[],
485
- msg: Uint8Array,
486
- tweaks: Uint8Array[] = [],
749
+ secret: TArg<Uint8Array>,
750
+ aggOtherNonce: TArg<Uint8Array>,
751
+ publicKeys: TArg<Uint8Array[]>,
752
+ msg: TArg<Uint8Array>,
753
+ tweaks: TArg<Uint8Array[]> = [],
487
754
  isXonly: boolean[] = [],
488
- rand?: Uint8Array,
755
+ rand?: TArg<Uint8Array>,
489
756
  fastSign = false
490
- ): DetNonce {
757
+ ): TRet<DetNonce> {
491
758
  abytes(secret, 32);
492
759
  abytes(aggOtherNonce, 66);
493
760
  abytesArray(publicKeys, PUBKEY_LEN);
494
761
  abytesArray(tweaks, 32);
495
762
  abytes(msg);
496
- abytesOptional(rand);
763
+ abytesOptional(rand, 32);
764
+ // BIP327 DeterministicSign input bullet: `The auxiliary randomness rand: a 32-byte array`
765
+ // when present, so this optional argument still needs the exact-length check here.
497
766
  const sk = rand !== undefined ? aux(secret, rand) : secret;
498
767
  const aggPublicKey = keyAggExport(keyAggregate(publicKeys, tweaks, isXonly));
499
768
  const k1 = deterministicNonceHash(sk, aggOtherNonce, aggPublicKey, msg, 0);
@@ -505,5 +774,5 @@ export function deterministicSign(
505
774
  const aggNonce = nonceAggregate([publicNonce, aggOtherNonce]);
506
775
  const session = new Session(aggNonce, publicKeys, msg, tweaks, isXonly);
507
776
  const partialSig = session.sign(secretNonce, secret, fastSign);
508
- return { publicNonce, partialSig };
777
+ return { publicNonce, partialSig } as TRet<DetNonce>;
509
778
  }