@noble/post-quantum 0.6.0 → 0.7.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/index.ts CHANGED
@@ -14,6 +14,14 @@ import {
14
14
  slh_dsa_shake_192f, slh_dsa_shake_192s,
15
15
  slh_dsa_shake_256f, slh_dsa_shake_256s,
16
16
  } from '@noble/post-quantum/slh-dsa.js';
17
+ import {
18
+ falcon512, falcon512padded, falcon1024, falcon1024padded,
19
+ } from '@noble/post-quantum/falcon.js';
20
+ import {
21
+ ml_kem768_x25519, ml_kem768_p256, ml_kem1024_p384,
22
+ KitchenSink_ml_kem768_x25519, XWing,
23
+ QSF_ml_kem768_p256, QSF_ml_kem1024_p384,
24
+ } from '@noble/post-quantum/hybrid.js';
17
25
  ```
18
26
  */
19
27
  throw new Error('root module cannot be imported: import submodules instead. Check out README');
package/src/ml-dsa.ts CHANGED
@@ -25,6 +25,8 @@ import {
25
25
  type Signer,
26
26
  type SigOpts,
27
27
  splitCoder,
28
+ type TArg,
29
+ type TRet,
28
30
  validateOpts,
29
31
  validateSigOpts,
30
32
  validateVerOpts,
@@ -41,7 +43,7 @@ export type DSAInternalOpts = {
41
43
  */
42
44
  externalMu?: boolean;
43
45
  };
44
- function validateInternalOpts(opts: DSAInternalOpts) {
46
+ function validateInternalOpts(opts: TArg<DSAInternalOpts>) {
45
47
  validateOpts(opts);
46
48
  if (opts.externalMu !== undefined) abool(opts.externalMu, 'opts.externalMu');
47
49
  }
@@ -49,16 +51,29 @@ function validateInternalOpts(opts: DSAInternalOpts) {
49
51
  /** ML-DSA signer surface with access to the internal message formatting mode. */
50
52
  export type DSAInternal = CryptoKeys & {
51
53
  lengths: Signer['lengths'];
52
- sign: (msg: Uint8Array, secretKey: Uint8Array, opts?: SigOpts & DSAInternalOpts) => Uint8Array;
54
+ sign: (
55
+ msg: TArg<Uint8Array>,
56
+ secretKey: TArg<Uint8Array>,
57
+ opts?: TArg<SigOpts & DSAInternalOpts>
58
+ ) => TRet<Uint8Array>;
53
59
  verify: (
54
- sig: Uint8Array,
55
- msg: Uint8Array,
56
- pubKey: Uint8Array,
57
- opts?: VerOpts & DSAInternalOpts
60
+ sig: TArg<Uint8Array>,
61
+ msg: TArg<Uint8Array>,
62
+ pubKey: TArg<Uint8Array>,
63
+ opts?: TArg<VerOpts & DSAInternalOpts>
58
64
  ) => boolean;
59
65
  };
60
66
  /** Public ML-DSA signer surface. */
61
- export type DSA = Signer & { internal: DSAInternal };
67
+ export type DSA = Signer & {
68
+ internal: TRet<DSAInternal>;
69
+ securityLevel: number;
70
+ /**
71
+ * HashML-DSA (FIPS 204 §5.4) variant which signs a pre-hashed message.
72
+ * @param hash - Approved hash, checked against the parameter set security level.
73
+ * @returns Signer which pre-hashes `msg` before formatting `M'`.
74
+ */
75
+ prehash: (hash: TArg<CHash>) => TRet<Signer>;
76
+ };
62
77
 
63
78
  // Constants
64
79
  // FIPS 204 fixes ML-DSA over R = Z[X]/(X^256 + 1), so every polynomial has 256 coefficients.
@@ -106,15 +121,22 @@ export type DSAParam = {
106
121
  * This is only the Table 1 subset used directly here: `BETA = TAU * ETA` is derived later,
107
122
  * while `C_TILDE_BYTES`, `TR_BYTES`, `CRH_BYTES`, and `securityLevel` live in the preset wrappers.
108
123
  */
109
- export const PARAMS: Record<string, DSAParam> = /* @__PURE__ */ (() => ({
110
- 2: { K: 4, L: 4, D, GAMMA1: 2 ** 17, GAMMA2: GAMMA2_1, TAU: 39, ETA: 2, OMEGA: 80 },
111
- 3: { K: 6, L: 5, D, GAMMA1: 2 ** 19, GAMMA2: GAMMA2_2, TAU: 49, ETA: 4, OMEGA: 55 },
112
- 5: { K: 8, L: 7, D, GAMMA1: 2 ** 19, GAMMA2: GAMMA2_2, TAU: 60, ETA: 2, OMEGA: 75 },
113
- } as const))();
124
+ export const PARAMS: Record<string, DSAParam> = /* @__PURE__ */ (() =>
125
+ Object.freeze({
126
+ 2: Object.freeze({
127
+ K: 4, L: 4, D, GAMMA1: 2 ** 17, GAMMA2: GAMMA2_1, TAU: 39, ETA: 2, OMEGA: 80
128
+ }),
129
+ 3: Object.freeze({
130
+ K: 6, L: 5, D, GAMMA1: 2 ** 19, GAMMA2: GAMMA2_2, TAU: 49, ETA: 4, OMEGA: 55
131
+ }),
132
+ 5: Object.freeze({
133
+ K: 8, L: 7, D, GAMMA1: 2 ** 19, GAMMA2: GAMMA2_2, TAU: 60, ETA: 2, OMEGA: 75
134
+ }),
135
+ } as const))();
114
136
 
115
137
  // NOTE: there is a lot cases where negative numbers used (with smod instead of mod).
116
138
  type Poly = Int32Array;
117
- const newPoly = (n: number): Int32Array => new Int32Array(n);
139
+ const newPoly = (n: number): TRet<Int32Array> => new Int32Array(n) as TRet<Int32Array>;
118
140
 
119
141
  // Shared CRYSTALS helper in the ML-DSA branch: non-Kyber mode, 8-bit bit-reversal,
120
142
  // and Int32Array polys because ordinary-form coefficients can be negative / centered.
@@ -141,41 +163,52 @@ const polyCoder = (d: number, compress: IdNum = id, verify: IdNum = id) =>
141
163
  });
142
164
 
143
165
  // Mutates `a` in place; callers must pass same-length polynomials.
144
- const polyAdd = (a: Poly, b: Poly) => {
166
+ // NOTE: conditional-reduction variants (as in ml-kem) were measured performance-neutral here —
167
+ // int32 `%` with 23-bit Q is already cheap — so the simpler mod() form is kept for audit.
168
+ const polyAdd = (a_: TArg<Poly>, b_: TArg<Poly>): TRet<Poly> => {
169
+ const a = a_ as Poly;
170
+ const b = b_ as Poly;
145
171
  for (let i = 0; i < a.length; i++) a[i] = crystals.mod(a[i] + b[i]);
146
- return a;
172
+ return a as TRet<Poly>;
147
173
  };
148
174
  // Mutates `a` in place; callers must pass same-length polynomials.
149
- const polySub = (a: Poly, b: Poly): Poly => {
175
+ const polySub = (a_: TArg<Poly>, b_: TArg<Poly>): TRet<Poly> => {
176
+ const a = a_ as Poly;
177
+ const b = b_ as Poly;
150
178
  for (let i = 0; i < a.length; i++) a[i] = crystals.mod(a[i] - b[i]);
151
- return a;
179
+ return a as TRet<Poly>;
152
180
  };
153
181
 
154
182
  // Mutates `p` in place and assumes it is a decoded `t1`-range polynomial.
155
- const polyShiftl = (p: Poly): Poly => {
183
+ const polyShiftl = (p_: TArg<Poly>): TRet<Poly> => {
184
+ const p = p_ as Poly;
156
185
  for (let i = 0; i < N; i++) p[i] <<= D;
157
- return p;
186
+ return p as TRet<Poly>;
158
187
  };
159
188
 
160
- const polyChknorm = (p: Poly, B: number): boolean => {
189
+ const polyChknorm = (p_: TArg<Poly>, B: number): boolean => {
190
+ const p = p_ as Poly;
161
191
  // FIPS 204 Algorithms 7 and 8 express the same centered-norm check with explicit inequalities.
162
192
  for (let i = 0; i < N; i++) if (Math.abs(crystals.smod(p[i])) >= B) return true;
163
193
  return false;
164
194
  };
165
195
 
166
196
  // Both inputs must already be in NTT / `T_q` form.
167
- const MultiplyNTTs = (a: Poly, b: Poly): Poly => {
197
+ const MultiplyNTTs = (a_: TArg<Poly>, b_: TArg<Poly>): TRet<Poly> => {
198
+ const a = a_ as Poly;
199
+ const b = b_ as Poly;
168
200
  // NOTE: we don't use montgomery reduction in code, since it requires 64 bit ints,
169
201
  // which is not available in JS. mod(a[i] * b[i]) is ok, since Q is 23 bit,
170
202
  // which means a[i] * b[i] is 46 bit, which is safe to use in JS. (number is 53 bits).
171
203
  // Barrett reduction is slower than mod :(
172
204
  const c = newPoly(N);
173
205
  for (let i = 0; i < a.length; i++) c[i] = crystals.mod(a[i] * b[i]);
174
- return c;
206
+ return c as TRet<Poly>;
175
207
  };
176
208
 
177
209
  // Return poly in NTT representation
178
- function RejNTTPoly(xof: XofGet) {
210
+ function RejNTTPoly(xof_: TArg<XofGet>): TRet<Poly> {
211
+ const xof = xof_ as XofGet;
179
212
  // Samples a polynomial ∈ Tq. xof() must return byte lengths divisible by 3.
180
213
  const r = newPoly(N);
181
214
  // NOTE: we can represent 3xu24 as 4xu32, but it doesn't improve perf :(
@@ -188,7 +221,7 @@ function RejNTTPoly(xof: XofGet) {
188
221
  if (t < Q) r[j++] = t;
189
222
  }
190
223
  }
191
- return r;
224
+ return r as TRet<Poly>;
192
225
  }
193
226
 
194
227
  type DilithiumOpts = {
@@ -209,7 +242,8 @@ type DilithiumOpts = {
209
242
 
210
243
  // Instantiate one ML-DSA parameter set from the Table 1 lattice constants plus the
211
244
  // Table 2 byte lengths / hash-width choices used by the public wrappers below.
212
- function getDilithium(opts: DilithiumOpts) {
245
+ function getDilithium(opts_: TArg<DilithiumOpts>): TRet<DSA> {
246
+ const opts = opts_ as DilithiumOpts;
213
247
  const { K, L, GAMMA1, GAMMA2, TAU, ETA, OMEGA } = opts;
214
248
  const { CRH_BYTES, TR_BYTES, C_TILDE_BYTES, XOF128, XOF256, securityLevel } = opts;
215
249
 
@@ -252,13 +286,16 @@ function getDilithium(opts: DilithiumOpts) {
252
286
  return res0;
253
287
  };
254
288
 
289
+ // m = (q-1)/(2γ2): 44 for ML-DSA-44, 16 for 65/87. Hoisted out of UseHint, which runs
290
+ // per coefficient during verification.
291
+ const HINT_M = Math.floor((Q - 1) / (2 * GAMMA2));
255
292
  const UseHint = (h: number, r: number) => {
256
293
  // Returns the high bits of r adjusted according to hint h
257
- const m = Math.floor((Q - 1) / (2 * GAMMA2));
258
294
  const { r1, r0 } = decompose(r);
259
295
  // 3: if h = 1 and r0 > 0 return (r1 + 1) mod m
260
296
  // 4: if h = 1 and r0 ≤ 0 return (r1 − 1) mod m
261
- if (h === 1) return r0 > 0 ? crystals.mod(r1 + 1, m) | 0 : crystals.mod(r1 - 1, m) | 0;
297
+ if (h === 1)
298
+ return r0 > 0 ? crystals.mod(r1 + 1, HINT_M) | 0 : crystals.mod(r1 - 1, HINT_M) | 0;
262
299
  return r1 | 0;
263
300
  };
264
301
  const Power2Round = (r: number) => {
@@ -270,30 +307,31 @@ function getDilithium(opts: DilithiumOpts) {
270
307
 
271
308
  const hintCoder: BytesCoderLen<Poly[] | false> = {
272
309
  bytesLen: OMEGA + K,
273
- encode: (h: Poly[] | false) => {
310
+ encode: (h_: TArg<Poly[] | false>): TRet<Uint8Array> => {
311
+ const h = h_ as Poly[] | false;
274
312
  if (h === false) throw new Error('hint.encode: hint is false'); // should never happen
275
313
  const res = new Uint8Array(OMEGA + K);
276
314
  for (let i = 0, k = 0; i < K; i++) {
277
315
  for (let j = 0; j < N; j++) if (h[i][j] !== 0) res[k++] = j;
278
316
  res[OMEGA + i] = k;
279
317
  }
280
- return res;
318
+ return res as TRet<Uint8Array>;
281
319
  },
282
- decode: (buf: Uint8Array) => {
320
+ decode: (buf: TArg<Uint8Array>): TRet<Poly[] | false> => {
283
321
  const h = [];
284
322
  let k = 0;
285
323
  for (let i = 0; i < K; i++) {
286
324
  const hi = newPoly(N);
287
- if (buf[OMEGA + i] < k || buf[OMEGA + i] > OMEGA) return false;
325
+ if (buf[OMEGA + i] < k || buf[OMEGA + i] > OMEGA) return false as TRet<false>;
288
326
  for (let j = k; j < buf[OMEGA + i]; j++) {
289
- if (j > k && buf[j] <= buf[j - 1]) return false;
327
+ if (j > k && buf[j] <= buf[j - 1]) return false as TRet<false>;
290
328
  hi[buf[j]] = 1;
291
329
  }
292
330
  k = buf[OMEGA + i];
293
331
  h.push(hi);
294
332
  }
295
- for (let j = k; j < OMEGA; j++) if (buf[j] !== 0) return false;
296
- return h;
333
+ for (let j = k; j < OMEGA; j++) if (buf[j] !== 0) return false as TRet<false>;
334
+ return h as TRet<Poly[]>;
297
335
  },
298
336
  };
299
337
 
@@ -332,7 +370,8 @@ function getDilithium(opts: DilithiumOpts) {
332
370
  // Return poly in ordinary representation.
333
371
  // This helper returns ordinary-form `[-ETA, ETA]` coefficients for ExpandS; callers apply
334
372
  // `NTT.encode()` later when needed.
335
- function RejBoundedPoly(xof: XofGet) {
373
+ function RejBoundedPoly(xof_: TArg<XofGet>): TRet<Poly> {
374
+ const xof = xof_ as XofGet;
336
375
  // Samples an element a ∈ Rq with coeffcients in [−η, η] computed via rejection sampling from ρ.
337
376
  const r: Poly = newPoly(N);
338
377
  for (let j = 0; j < N; ) {
@@ -345,10 +384,10 @@ function getDilithium(opts: DilithiumOpts) {
345
384
  if (j < N && d2 !== false) r[j++] = d2;
346
385
  }
347
386
  }
348
- return r;
387
+ return r as TRet<Poly>;
349
388
  }
350
389
 
351
- const SampleInBall = (seed: Uint8Array) => {
390
+ const SampleInBall = (seed: TArg<Uint8Array>): TRet<Poly> => {
352
391
  // Samples a polynomial c ∈ Rq with coeffcients from {−1, 0, 1} and Hamming weight τ
353
392
  const pre = newPoly(N);
354
393
  const s = shake256.create({}).update(seed);
@@ -372,10 +411,11 @@ function getDilithium(opts: DilithiumOpts) {
372
411
  maskBit = 0;
373
412
  }
374
413
  }
375
- return pre;
414
+ return pre as TRet<Poly>;
376
415
  };
377
416
 
378
- const polyPowerRound = (p: Poly) => {
417
+ const polyPowerRound = (p_: TArg<Poly>) => {
418
+ const p = p_ as Poly;
379
419
  const res0 = newPoly(N);
380
420
  const res1 = newPoly(N);
381
421
  for (let i = 0; i < p.length; i++) {
@@ -385,13 +425,17 @@ function getDilithium(opts: DilithiumOpts) {
385
425
  }
386
426
  return { r0: res0, r1: res1 };
387
427
  };
388
- const polyUseHint = (u: Poly, h: Poly): Poly => {
428
+ const polyUseHint = (u_: TArg<Poly>, h_: TArg<Poly>): TRet<Poly> => {
429
+ const u = u_ as Poly;
430
+ const h = h_ as Poly;
389
431
  // In-place on `u`: verification only needs the recovered high bits, so reuse the
390
432
  // temporary `wApprox` buffer instead of allocating another polynomial.
391
433
  for (let i = 0; i < N; i++) u[i] = UseHint(h[i], u[i]);
392
- return u;
434
+ return u as TRet<Poly>;
393
435
  };
394
- const polyMakeHint = (a: Poly, b: Poly) => {
436
+ const polyMakeHint = (a_: TArg<Poly>, b_: TArg<Poly>) => {
437
+ const a = a_ as Poly;
438
+ const b = b_ as Poly;
395
439
  const v = newPoly(N);
396
440
  let cnt = 0;
397
441
  for (let i = 0; i < N; i++) {
@@ -405,16 +449,16 @@ function getDilithium(opts: DilithiumOpts) {
405
449
  const signRandBytes = 32;
406
450
  const seedCoder = splitCoder('seed', 32, 64, 32);
407
451
  // API & argument positions are exactly as in FIPS204.
408
- const internal: DSAInternal = {
409
- info: { type: 'internal-ml-dsa' },
410
- lengths: {
452
+ const internal: TRet<DSAInternal> = Object.freeze({
453
+ info: Object.freeze({ type: 'internal-ml-dsa' }),
454
+ lengths: Object.freeze({
411
455
  secretKey: secretCoder.bytesLen,
412
456
  publicKey: publicCoder.bytesLen,
413
457
  seed: 32,
414
458
  signature: sigCoder.bytesLen,
415
459
  signRand: signRandBytes,
416
- },
417
- keygen: (seed?: Uint8Array) => {
460
+ }),
461
+ keygen: (seed?: TArg<Uint8Array>) => {
418
462
  // H(𝜉||IntegerToBytes(𝑘, 1)||IntegerToBytes(ℓ, 1), 128) 2: ▷ expand seed
419
463
  const seedDst = new Uint8Array(32 + 2);
420
464
  const randSeed = seed === undefined;
@@ -462,9 +506,12 @@ function getDilithium(opts: DilithiumOpts) {
462
506
  // DSA44: { calls: 24, xofs: 24 }, DSA65: { calls: 41, xofs: 41 },
463
507
  // DSA87: { calls: 71, xofs: 71 }
464
508
  cleanBytes(rho, rhoPrime, K_, s1, s2, s1Hat, t, t0, t1, tr, seedDst);
465
- return { publicKey, secretKey };
509
+ return {
510
+ publicKey: publicKey as TRet<Uint8Array>,
511
+ secretKey: secretKey as TRet<Uint8Array>,
512
+ };
466
513
  },
467
- getPublicKey: (secretKey: Uint8Array) => {
514
+ getPublicKey: (secretKey: TArg<Uint8Array>): TRet<Uint8Array> => {
468
515
  // (ρ, K,tr, s1, s2, t0) ← skDecode(sk)
469
516
  const [rho, _K, _tr, s1, s2, _t0] = secretCoder.decode(secretKey);
470
517
  const xof = XOF128(rho);
@@ -487,14 +534,39 @@ function getDilithium(opts: DilithiumOpts) {
487
534
  return publicCoder.encode([rho, t1]);
488
535
  },
489
536
  // NOTE: random is optional.
490
- sign: (msg: Uint8Array, secretKey: Uint8Array, opts: SigOpts & DSAInternalOpts = {}) => {
537
+ sign: (
538
+ msg: TArg<Uint8Array>,
539
+ secretKey: TArg<Uint8Array>,
540
+ opts: TArg<SigOpts & DSAInternalOpts> = {}
541
+ ): TRet<Uint8Array> => {
491
542
  validateSigOpts(opts);
492
543
  validateInternalOpts(opts);
493
- let { extraEntropy: random, externalMu = false } = opts;
544
+ const { extraEntropy: random, externalMu = false } = opts;
545
+ // FIPS 204 external-mu mode expects the 64-byte message representative µ = H(tr || M).
546
+ if (externalMu) abytes(msg, CRH_BYTES, 'mu');
547
+ // Prepare entropy before touching decoded secrets: randomBytes() may throw, and an RNG
548
+ // failure must not leave expanded secret-polynomial copies behind.
549
+ const ownRnd = random === false || random === undefined;
550
+ const rnd =
551
+ random === false
552
+ ? new Uint8Array(32)
553
+ : random === undefined
554
+ ? randomBytes(signRandBytes)
555
+ : (random as Uint8Array);
556
+ abytes(rnd, 32, 'extraEntropy');
494
557
  // This part can be pre-cached per secretKey, but there is only minor performance improvement,
495
558
  // since we re-use a lot of variables to computation.
496
559
  // (ρ, K,tr, s1, s2, t0) ← skDecode(sk)
497
- const [rho, _K, tr, s1, s2, t0] = secretCoder.decode(secretKey);
560
+ const decoded = (() => {
561
+ try {
562
+ return secretCoder.decode(secretKey);
563
+ } catch (error) {
564
+ // A malformed key must not strand entropy owned by the library.
565
+ if (ownRnd) cleanBytes(rnd);
566
+ throw error;
567
+ }
568
+ })();
569
+ const [rho, _K, tr, s1, s2, t0] = decoded;
498
570
  // Cache matrix to avoid re-compute later
499
571
  const A: Poly[][] = []; // A ← ExpandA(ρ)
500
572
  const xof = XOF128(rho);
@@ -516,21 +588,14 @@ function getDilithium(opts: DilithiumOpts) {
516
588
  // ▷ Compute message representative µ
517
589
  shake256.create({ dkLen: CRH_BYTES }).update(tr).update(msg).digest();
518
590
 
519
- // Compute private random seed
520
- const rnd =
521
- random === false
522
- ? new Uint8Array(32)
523
- : random === undefined
524
- ? randomBytes(signRandBytes)
525
- : random;
526
- abytes(rnd, 32, 'extraEntropy');
527
591
  const rhoprime = shake256
528
592
  .create({ dkLen: CRH_BYTES })
529
593
  .update(_K)
530
594
  .update(rnd)
531
595
  .update(mu)
532
596
  .digest(); // ρ′← H(K||rnd||µ, 512)
533
-
597
+ // Only wipe entropy we generated; caller-provided extraEntropy stays caller-owned.
598
+ if (ownRnd) cleanBytes(rnd);
534
599
  abytes(rhoprime, CRH_BYTES);
535
600
  const x256 = XOF256(rhoprime, ZCoder.bytesLen);
536
601
  // Rejection sampling loop
@@ -588,19 +653,21 @@ function getDilithium(opts: DilithiumOpts) {
588
653
  // so only wipe the internally derived digest form here;
589
654
  // zeroizing caller memory would break the caller's own reuse / verify path.
590
655
  if (!externalMu) cleanBytes(mu);
591
- return res;
656
+ return res as TRet<Uint8Array>;
592
657
  }
593
658
  // @ts-ignore
594
659
  throw new Error('Unreachable code path reached, report this error');
595
660
  },
596
661
  verify: (
597
- sig: Uint8Array,
598
- msg: Uint8Array,
599
- publicKey: Uint8Array,
600
- opts: DSAInternalOpts = {}
662
+ sig: TArg<Uint8Array>,
663
+ msg: TArg<Uint8Array>,
664
+ publicKey: TArg<Uint8Array>,
665
+ opts: TArg<DSAInternalOpts> = {}
601
666
  ) => {
602
667
  validateInternalOpts(opts);
603
668
  const { externalMu = false } = opts;
669
+ // FIPS 204 external-mu mode expects the 64-byte message representative µ = H(tr || M).
670
+ if (externalMu) abytes(msg, CRH_BYTES, 'mu');
604
671
  // ML-DSA.Verify(pk, M, σ): Verifes a signature σ for a message M.
605
672
  const [rho, t1] = publicCoder.decode(publicKey); // (ρ, t1) ← pkDecode(pk)
606
673
  const tr = shake256(publicKey, { dkLen: TR_BYTES }); // 6: tr ← H(BytesToBits(pk), 512)
@@ -649,51 +716,91 @@ function getDilithium(opts: DilithiumOpts) {
649
716
  for (const t of z) if (polyChknorm(t, GAMMA1 - BETA)) return false;
650
717
  return equalBytes(cTilde, c2);
651
718
  },
652
- };
653
- return {
654
- info: { type: 'ml-dsa' },
719
+ });
720
+ return Object.freeze({
721
+ info: Object.freeze({ type: 'ml-dsa' }),
655
722
  internal,
656
723
  securityLevel: securityLevel,
657
724
  keygen: internal.keygen,
658
725
  lengths: internal.lengths,
659
726
  getPublicKey: internal.getPublicKey,
660
- sign: (msg: Uint8Array, secretKey: Uint8Array, opts: SigOpts = {}) => {
727
+ sign: (
728
+ msg: TArg<Uint8Array>,
729
+ secretKey: TArg<Uint8Array>,
730
+ opts: TArg<SigOpts> = {}
731
+ ): TRet<Uint8Array> => {
661
732
  validateSigOpts(opts);
662
733
  const M = getMessage(msg, opts.context);
663
734
  const res = internal.sign(M, secretKey, opts);
664
735
  cleanBytes(M);
665
- return res;
736
+ return res as TRet<Uint8Array>;
666
737
  },
667
- verify: (sig: Uint8Array, msg: Uint8Array, publicKey: Uint8Array, opts: VerOpts = {}) => {
738
+ verify: (
739
+ sig: TArg<Uint8Array>,
740
+ msg: TArg<Uint8Array>,
741
+ publicKey: TArg<Uint8Array>,
742
+ opts: TArg<VerOpts> = {}
743
+ ) => {
668
744
  validateVerOpts(opts);
745
+ abytes(sig, undefined, 'signature');
669
746
  return internal.verify(sig, getMessage(msg, opts.context), publicKey);
670
747
  },
671
- prehash: (hash: CHash) => {
672
- checkHash(hash, securityLevel);
673
- return {
674
- info: { type: 'hashml-dsa' },
748
+ prehash: (hash: TArg<CHash>): TRet<Signer> => {
749
+ checkHash(hash as CHash, securityLevel);
750
+ const rawHash = hash as CHash;
751
+ return Object.freeze({
752
+ info: Object.freeze({ type: 'hashml-dsa' }),
675
753
  securityLevel: securityLevel,
676
754
  lengths: internal.lengths,
677
755
  keygen: internal.keygen,
678
756
  getPublicKey: internal.getPublicKey,
679
- sign: (msg: Uint8Array, secretKey: Uint8Array, opts: SigOpts = {}) => {
757
+ sign: (
758
+ msg: TArg<Uint8Array>,
759
+ secretKey: TArg<Uint8Array>,
760
+ opts: TArg<SigOpts> = {}
761
+ ): TRet<Uint8Array> => {
680
762
  validateSigOpts(opts);
681
- const M = getMessagePrehash(hash, msg, opts.context);
763
+ const M = getMessagePrehash(rawHash, msg, opts.context);
682
764
  const res = internal.sign(M, secretKey, opts);
683
765
  cleanBytes(M);
684
- return res;
766
+ return res as TRet<Uint8Array>;
685
767
  },
686
- verify: (sig: Uint8Array, msg: Uint8Array, publicKey: Uint8Array, opts: VerOpts = {}) => {
768
+ verify: (
769
+ sig: TArg<Uint8Array>,
770
+ msg: TArg<Uint8Array>,
771
+ publicKey: TArg<Uint8Array>,
772
+ opts: TArg<VerOpts> = {}
773
+ ) => {
687
774
  validateVerOpts(opts);
688
- return internal.verify(sig, getMessagePrehash(hash, msg, opts.context), publicKey);
775
+ abytes(sig, undefined, 'signature');
776
+ return internal.verify(sig, getMessagePrehash(rawHash, msg, opts.context), publicKey);
689
777
  },
690
- };
778
+ });
691
779
  },
692
- };
780
+ });
693
781
  }
694
782
 
695
- /** ML-DSA-44 for 128-bit security level. Not recommended after 2030, as per ASD. */
696
- export const ml_dsa44: DSA = /* @__PURE__ */ (() =>
783
+ /**
784
+ * ML-DSA-44 for 128-bit security level. Not recommended after 2030, as per ASD.
785
+ * @example
786
+ * Generate deterministic ML-DSA-44 keys, sign one message, and verify the signature.
787
+ * ```ts
788
+ * import { sha256 } from '@noble/hashes/sha2.js';
789
+ * import { ml_dsa44 } from '@noble/post-quantum/ml-dsa.js';
790
+ * const seed = new Uint8Array(ml_dsa44.lengths.seed!);
791
+ * const { secretKey, publicKey } = ml_dsa44.keygen(seed);
792
+ * const msg = new TextEncoder().encode('hello noble');
793
+ * const sig = ml_dsa44.sign(msg, secretKey);
794
+ * const isValid = ml_dsa44.verify(sig, msg, publicKey);
795
+ * const recovered = ml_dsa44.getPublicKey(secretKey);
796
+ * const context = new Uint8Array([1, 2, 3]);
797
+ * const prehash = ml_dsa44.prehash(sha256);
798
+ * const preSig = prehash.sign(msg, secretKey, { context });
799
+ * const preValid = prehash.verify(preSig, msg, publicKey, { context });
800
+ * const internalSig = ml_dsa44.internal.sign(msg, secretKey);
801
+ * ```
802
+ */
803
+ export const ml_dsa44: TRet<DSA> = /* @__PURE__ */ (() =>
697
804
  getDilithium({
698
805
  ...PARAMS[2],
699
806
  CRH_BYTES: 64,
@@ -705,7 +812,7 @@ export const ml_dsa44: DSA = /* @__PURE__ */ (() =>
705
812
  }))();
706
813
 
707
814
  /** ML-DSA-65 for 192-bit security level. Not recommended after 2030, as per ASD. */
708
- export const ml_dsa65: DSA = /* @__PURE__ */ (() =>
815
+ export const ml_dsa65: TRet<DSA> = /* @__PURE__ */ (() =>
709
816
  getDilithium({
710
817
  ...PARAMS[3],
711
818
  CRH_BYTES: 64,
@@ -717,7 +824,7 @@ export const ml_dsa65: DSA = /* @__PURE__ */ (() =>
717
824
  }))();
718
825
 
719
826
  /** ML-DSA-87 for 256-bit security level. OK after 2030, as per ASD. */
720
- export const ml_dsa87: DSA = /* @__PURE__ */ (() =>
827
+ export const ml_dsa87: TRet<DSA> = /* @__PURE__ */ (() =>
721
828
  getDilithium({
722
829
  ...PARAMS[5],
723
830
  CRH_BYTES: 64,