@noble/post-quantum 0.6.0 → 0.6.1

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/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,20 @@ 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 & { internal: TRet<DSAInternal> };
62
68
 
63
69
  // Constants
64
70
  // FIPS 204 fixes ML-DSA over R = Z[X]/(X^256 + 1), so every polynomial has 256 coefficients.
@@ -106,15 +112,22 @@ export type DSAParam = {
106
112
  * This is only the Table 1 subset used directly here: `BETA = TAU * ETA` is derived later,
107
113
  * while `C_TILDE_BYTES`, `TR_BYTES`, `CRH_BYTES`, and `securityLevel` live in the preset wrappers.
108
114
  */
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))();
115
+ export const PARAMS: Record<string, DSAParam> = /* @__PURE__ */ (() =>
116
+ Object.freeze({
117
+ 2: Object.freeze({
118
+ K: 4, L: 4, D, GAMMA1: 2 ** 17, GAMMA2: GAMMA2_1, TAU: 39, ETA: 2, OMEGA: 80
119
+ }),
120
+ 3: Object.freeze({
121
+ K: 6, L: 5, D, GAMMA1: 2 ** 19, GAMMA2: GAMMA2_2, TAU: 49, ETA: 4, OMEGA: 55
122
+ }),
123
+ 5: Object.freeze({
124
+ K: 8, L: 7, D, GAMMA1: 2 ** 19, GAMMA2: GAMMA2_2, TAU: 60, ETA: 2, OMEGA: 75
125
+ }),
126
+ } as const))();
114
127
 
115
128
  // NOTE: there is a lot cases where negative numbers used (with smod instead of mod).
116
129
  type Poly = Int32Array;
117
- const newPoly = (n: number): Int32Array => new Int32Array(n);
130
+ const newPoly = (n: number): TRet<Int32Array> => new Int32Array(n) as TRet<Int32Array>;
118
131
 
119
132
  // Shared CRYSTALS helper in the ML-DSA branch: non-Kyber mode, 8-bit bit-reversal,
120
133
  // and Int32Array polys because ordinary-form coefficients can be negative / centered.
@@ -141,41 +154,50 @@ const polyCoder = (d: number, compress: IdNum = id, verify: IdNum = id) =>
141
154
  });
142
155
 
143
156
  // Mutates `a` in place; callers must pass same-length polynomials.
144
- const polyAdd = (a: Poly, b: Poly) => {
157
+ const polyAdd = (a_: TArg<Poly>, b_: TArg<Poly>): TRet<Poly> => {
158
+ const a = a_ as Poly;
159
+ const b = b_ as Poly;
145
160
  for (let i = 0; i < a.length; i++) a[i] = crystals.mod(a[i] + b[i]);
146
- return a;
161
+ return a as TRet<Poly>;
147
162
  };
148
163
  // Mutates `a` in place; callers must pass same-length polynomials.
149
- const polySub = (a: Poly, b: Poly): Poly => {
164
+ const polySub = (a_: TArg<Poly>, b_: TArg<Poly>): TRet<Poly> => {
165
+ const a = a_ as Poly;
166
+ const b = b_ as Poly;
150
167
  for (let i = 0; i < a.length; i++) a[i] = crystals.mod(a[i] - b[i]);
151
- return a;
168
+ return a as TRet<Poly>;
152
169
  };
153
170
 
154
171
  // Mutates `p` in place and assumes it is a decoded `t1`-range polynomial.
155
- const polyShiftl = (p: Poly): Poly => {
172
+ const polyShiftl = (p_: TArg<Poly>): TRet<Poly> => {
173
+ const p = p_ as Poly;
156
174
  for (let i = 0; i < N; i++) p[i] <<= D;
157
- return p;
175
+ return p as TRet<Poly>;
158
176
  };
159
177
 
160
- const polyChknorm = (p: Poly, B: number): boolean => {
178
+ const polyChknorm = (p_: TArg<Poly>, B: number): boolean => {
179
+ const p = p_ as Poly;
161
180
  // FIPS 204 Algorithms 7 and 8 express the same centered-norm check with explicit inequalities.
162
181
  for (let i = 0; i < N; i++) if (Math.abs(crystals.smod(p[i])) >= B) return true;
163
182
  return false;
164
183
  };
165
184
 
166
185
  // Both inputs must already be in NTT / `T_q` form.
167
- const MultiplyNTTs = (a: Poly, b: Poly): Poly => {
186
+ const MultiplyNTTs = (a_: TArg<Poly>, b_: TArg<Poly>): TRet<Poly> => {
187
+ const a = a_ as Poly;
188
+ const b = b_ as Poly;
168
189
  // NOTE: we don't use montgomery reduction in code, since it requires 64 bit ints,
169
190
  // which is not available in JS. mod(a[i] * b[i]) is ok, since Q is 23 bit,
170
191
  // which means a[i] * b[i] is 46 bit, which is safe to use in JS. (number is 53 bits).
171
192
  // Barrett reduction is slower than mod :(
172
193
  const c = newPoly(N);
173
194
  for (let i = 0; i < a.length; i++) c[i] = crystals.mod(a[i] * b[i]);
174
- return c;
195
+ return c as TRet<Poly>;
175
196
  };
176
197
 
177
198
  // Return poly in NTT representation
178
- function RejNTTPoly(xof: XofGet) {
199
+ function RejNTTPoly(xof_: TArg<XofGet>): TRet<Poly> {
200
+ const xof = xof_ as XofGet;
179
201
  // Samples a polynomial ∈ Tq. xof() must return byte lengths divisible by 3.
180
202
  const r = newPoly(N);
181
203
  // NOTE: we can represent 3xu24 as 4xu32, but it doesn't improve perf :(
@@ -188,7 +210,7 @@ function RejNTTPoly(xof: XofGet) {
188
210
  if (t < Q) r[j++] = t;
189
211
  }
190
212
  }
191
- return r;
213
+ return r as TRet<Poly>;
192
214
  }
193
215
 
194
216
  type DilithiumOpts = {
@@ -209,7 +231,8 @@ type DilithiumOpts = {
209
231
 
210
232
  // Instantiate one ML-DSA parameter set from the Table 1 lattice constants plus the
211
233
  // Table 2 byte lengths / hash-width choices used by the public wrappers below.
212
- function getDilithium(opts: DilithiumOpts) {
234
+ function getDilithium(opts_: TArg<DilithiumOpts>): TRet<DSA> {
235
+ const opts = opts_ as DilithiumOpts;
213
236
  const { K, L, GAMMA1, GAMMA2, TAU, ETA, OMEGA } = opts;
214
237
  const { CRH_BYTES, TR_BYTES, C_TILDE_BYTES, XOF128, XOF256, securityLevel } = opts;
215
238
 
@@ -270,30 +293,31 @@ function getDilithium(opts: DilithiumOpts) {
270
293
 
271
294
  const hintCoder: BytesCoderLen<Poly[] | false> = {
272
295
  bytesLen: OMEGA + K,
273
- encode: (h: Poly[] | false) => {
296
+ encode: (h_: TArg<Poly[] | false>): TRet<Uint8Array> => {
297
+ const h = h_ as Poly[] | false;
274
298
  if (h === false) throw new Error('hint.encode: hint is false'); // should never happen
275
299
  const res = new Uint8Array(OMEGA + K);
276
300
  for (let i = 0, k = 0; i < K; i++) {
277
301
  for (let j = 0; j < N; j++) if (h[i][j] !== 0) res[k++] = j;
278
302
  res[OMEGA + i] = k;
279
303
  }
280
- return res;
304
+ return res as TRet<Uint8Array>;
281
305
  },
282
- decode: (buf: Uint8Array) => {
306
+ decode: (buf: TArg<Uint8Array>): TRet<Poly[] | false> => {
283
307
  const h = [];
284
308
  let k = 0;
285
309
  for (let i = 0; i < K; i++) {
286
310
  const hi = newPoly(N);
287
- if (buf[OMEGA + i] < k || buf[OMEGA + i] > OMEGA) return false;
311
+ if (buf[OMEGA + i] < k || buf[OMEGA + i] > OMEGA) return false as TRet<false>;
288
312
  for (let j = k; j < buf[OMEGA + i]; j++) {
289
- if (j > k && buf[j] <= buf[j - 1]) return false;
313
+ if (j > k && buf[j] <= buf[j - 1]) return false as TRet<false>;
290
314
  hi[buf[j]] = 1;
291
315
  }
292
316
  k = buf[OMEGA + i];
293
317
  h.push(hi);
294
318
  }
295
- for (let j = k; j < OMEGA; j++) if (buf[j] !== 0) return false;
296
- return h;
319
+ for (let j = k; j < OMEGA; j++) if (buf[j] !== 0) return false as TRet<false>;
320
+ return h as TRet<Poly[]>;
297
321
  },
298
322
  };
299
323
 
@@ -332,7 +356,8 @@ function getDilithium(opts: DilithiumOpts) {
332
356
  // Return poly in ordinary representation.
333
357
  // This helper returns ordinary-form `[-ETA, ETA]` coefficients for ExpandS; callers apply
334
358
  // `NTT.encode()` later when needed.
335
- function RejBoundedPoly(xof: XofGet) {
359
+ function RejBoundedPoly(xof_: TArg<XofGet>): TRet<Poly> {
360
+ const xof = xof_ as XofGet;
336
361
  // Samples an element a ∈ Rq with coeffcients in [−η, η] computed via rejection sampling from ρ.
337
362
  const r: Poly = newPoly(N);
338
363
  for (let j = 0; j < N; ) {
@@ -345,10 +370,10 @@ function getDilithium(opts: DilithiumOpts) {
345
370
  if (j < N && d2 !== false) r[j++] = d2;
346
371
  }
347
372
  }
348
- return r;
373
+ return r as TRet<Poly>;
349
374
  }
350
375
 
351
- const SampleInBall = (seed: Uint8Array) => {
376
+ const SampleInBall = (seed: TArg<Uint8Array>): TRet<Poly> => {
352
377
  // Samples a polynomial c ∈ Rq with coeffcients from {−1, 0, 1} and Hamming weight τ
353
378
  const pre = newPoly(N);
354
379
  const s = shake256.create({}).update(seed);
@@ -372,10 +397,11 @@ function getDilithium(opts: DilithiumOpts) {
372
397
  maskBit = 0;
373
398
  }
374
399
  }
375
- return pre;
400
+ return pre as TRet<Poly>;
376
401
  };
377
402
 
378
- const polyPowerRound = (p: Poly) => {
403
+ const polyPowerRound = (p_: TArg<Poly>) => {
404
+ const p = p_ as Poly;
379
405
  const res0 = newPoly(N);
380
406
  const res1 = newPoly(N);
381
407
  for (let i = 0; i < p.length; i++) {
@@ -385,13 +411,17 @@ function getDilithium(opts: DilithiumOpts) {
385
411
  }
386
412
  return { r0: res0, r1: res1 };
387
413
  };
388
- const polyUseHint = (u: Poly, h: Poly): Poly => {
414
+ const polyUseHint = (u_: TArg<Poly>, h_: TArg<Poly>): TRet<Poly> => {
415
+ const u = u_ as Poly;
416
+ const h = h_ as Poly;
389
417
  // In-place on `u`: verification only needs the recovered high bits, so reuse the
390
418
  // temporary `wApprox` buffer instead of allocating another polynomial.
391
419
  for (let i = 0; i < N; i++) u[i] = UseHint(h[i], u[i]);
392
- return u;
420
+ return u as TRet<Poly>;
393
421
  };
394
- const polyMakeHint = (a: Poly, b: Poly) => {
422
+ const polyMakeHint = (a_: TArg<Poly>, b_: TArg<Poly>) => {
423
+ const a = a_ as Poly;
424
+ const b = b_ as Poly;
395
425
  const v = newPoly(N);
396
426
  let cnt = 0;
397
427
  for (let i = 0; i < N; i++) {
@@ -405,16 +435,16 @@ function getDilithium(opts: DilithiumOpts) {
405
435
  const signRandBytes = 32;
406
436
  const seedCoder = splitCoder('seed', 32, 64, 32);
407
437
  // API & argument positions are exactly as in FIPS204.
408
- const internal: DSAInternal = {
409
- info: { type: 'internal-ml-dsa' },
410
- lengths: {
438
+ const internal: TRet<DSAInternal> = Object.freeze({
439
+ info: Object.freeze({ type: 'internal-ml-dsa' }),
440
+ lengths: Object.freeze({
411
441
  secretKey: secretCoder.bytesLen,
412
442
  publicKey: publicCoder.bytesLen,
413
443
  seed: 32,
414
444
  signature: sigCoder.bytesLen,
415
445
  signRand: signRandBytes,
416
- },
417
- keygen: (seed?: Uint8Array) => {
446
+ }),
447
+ keygen: (seed?: TArg<Uint8Array>) => {
418
448
  // H(𝜉||IntegerToBytes(𝑘, 1)||IntegerToBytes(ℓ, 1), 128) 2: ▷ expand seed
419
449
  const seedDst = new Uint8Array(32 + 2);
420
450
  const randSeed = seed === undefined;
@@ -462,9 +492,12 @@ function getDilithium(opts: DilithiumOpts) {
462
492
  // DSA44: { calls: 24, xofs: 24 }, DSA65: { calls: 41, xofs: 41 },
463
493
  // DSA87: { calls: 71, xofs: 71 }
464
494
  cleanBytes(rho, rhoPrime, K_, s1, s2, s1Hat, t, t0, t1, tr, seedDst);
465
- return { publicKey, secretKey };
495
+ return {
496
+ publicKey: publicKey as TRet<Uint8Array>,
497
+ secretKey: secretKey as TRet<Uint8Array>,
498
+ };
466
499
  },
467
- getPublicKey: (secretKey: Uint8Array) => {
500
+ getPublicKey: (secretKey: TArg<Uint8Array>): TRet<Uint8Array> => {
468
501
  // (ρ, K,tr, s1, s2, t0) ← skDecode(sk)
469
502
  const [rho, _K, _tr, s1, s2, _t0] = secretCoder.decode(secretKey);
470
503
  const xof = XOF128(rho);
@@ -487,7 +520,11 @@ function getDilithium(opts: DilithiumOpts) {
487
520
  return publicCoder.encode([rho, t1]);
488
521
  },
489
522
  // NOTE: random is optional.
490
- sign: (msg: Uint8Array, secretKey: Uint8Array, opts: SigOpts & DSAInternalOpts = {}) => {
523
+ sign: (
524
+ msg: TArg<Uint8Array>,
525
+ secretKey: TArg<Uint8Array>,
526
+ opts: TArg<SigOpts & DSAInternalOpts> = {}
527
+ ): TRet<Uint8Array> => {
491
528
  validateSigOpts(opts);
492
529
  validateInternalOpts(opts);
493
530
  let { extraEntropy: random, externalMu = false } = opts;
@@ -588,16 +625,16 @@ function getDilithium(opts: DilithiumOpts) {
588
625
  // so only wipe the internally derived digest form here;
589
626
  // zeroizing caller memory would break the caller's own reuse / verify path.
590
627
  if (!externalMu) cleanBytes(mu);
591
- return res;
628
+ return res as TRet<Uint8Array>;
592
629
  }
593
630
  // @ts-ignore
594
631
  throw new Error('Unreachable code path reached, report this error');
595
632
  },
596
633
  verify: (
597
- sig: Uint8Array,
598
- msg: Uint8Array,
599
- publicKey: Uint8Array,
600
- opts: DSAInternalOpts = {}
634
+ sig: TArg<Uint8Array>,
635
+ msg: TArg<Uint8Array>,
636
+ publicKey: TArg<Uint8Array>,
637
+ opts: TArg<DSAInternalOpts> = {}
601
638
  ) => {
602
639
  validateInternalOpts(opts);
603
640
  const { externalMu = false } = opts;
@@ -649,51 +686,69 @@ function getDilithium(opts: DilithiumOpts) {
649
686
  for (const t of z) if (polyChknorm(t, GAMMA1 - BETA)) return false;
650
687
  return equalBytes(cTilde, c2);
651
688
  },
652
- };
653
- return {
654
- info: { type: 'ml-dsa' },
689
+ });
690
+ return Object.freeze({
691
+ info: Object.freeze({ type: 'ml-dsa' }),
655
692
  internal,
656
693
  securityLevel: securityLevel,
657
694
  keygen: internal.keygen,
658
695
  lengths: internal.lengths,
659
696
  getPublicKey: internal.getPublicKey,
660
- sign: (msg: Uint8Array, secretKey: Uint8Array, opts: SigOpts = {}) => {
697
+ sign: (
698
+ msg: TArg<Uint8Array>,
699
+ secretKey: TArg<Uint8Array>,
700
+ opts: TArg<SigOpts> = {}
701
+ ): TRet<Uint8Array> => {
661
702
  validateSigOpts(opts);
662
703
  const M = getMessage(msg, opts.context);
663
704
  const res = internal.sign(M, secretKey, opts);
664
705
  cleanBytes(M);
665
- return res;
706
+ return res as TRet<Uint8Array>;
666
707
  },
667
- verify: (sig: Uint8Array, msg: Uint8Array, publicKey: Uint8Array, opts: VerOpts = {}) => {
708
+ verify: (
709
+ sig: TArg<Uint8Array>,
710
+ msg: TArg<Uint8Array>,
711
+ publicKey: TArg<Uint8Array>,
712
+ opts: TArg<VerOpts> = {}
713
+ ) => {
668
714
  validateVerOpts(opts);
669
715
  return internal.verify(sig, getMessage(msg, opts.context), publicKey);
670
716
  },
671
717
  prehash: (hash: CHash) => {
672
718
  checkHash(hash, securityLevel);
673
- return {
674
- info: { type: 'hashml-dsa' },
719
+ return Object.freeze({
720
+ info: Object.freeze({ type: 'hashml-dsa' }),
675
721
  securityLevel: securityLevel,
676
722
  lengths: internal.lengths,
677
723
  keygen: internal.keygen,
678
724
  getPublicKey: internal.getPublicKey,
679
- sign: (msg: Uint8Array, secretKey: Uint8Array, opts: SigOpts = {}) => {
725
+ sign: (
726
+ msg: TArg<Uint8Array>,
727
+ secretKey: TArg<Uint8Array>,
728
+ opts: TArg<SigOpts> = {}
729
+ ): TRet<Uint8Array> => {
680
730
  validateSigOpts(opts);
681
731
  const M = getMessagePrehash(hash, msg, opts.context);
682
732
  const res = internal.sign(M, secretKey, opts);
683
733
  cleanBytes(M);
684
- return res;
734
+ return res as TRet<Uint8Array>;
685
735
  },
686
- verify: (sig: Uint8Array, msg: Uint8Array, publicKey: Uint8Array, opts: VerOpts = {}) => {
736
+ verify: (
737
+ sig: TArg<Uint8Array>,
738
+ msg: TArg<Uint8Array>,
739
+ publicKey: TArg<Uint8Array>,
740
+ opts: TArg<VerOpts> = {}
741
+ ) => {
687
742
  validateVerOpts(opts);
688
743
  return internal.verify(sig, getMessagePrehash(hash, msg, opts.context), publicKey);
689
744
  },
690
- };
745
+ });
691
746
  },
692
- };
747
+ });
693
748
  }
694
749
 
695
750
  /** ML-DSA-44 for 128-bit security level. Not recommended after 2030, as per ASD. */
696
- export const ml_dsa44: DSA = /* @__PURE__ */ (() =>
751
+ export const ml_dsa44: TRet<DSA> = /* @__PURE__ */ (() =>
697
752
  getDilithium({
698
753
  ...PARAMS[2],
699
754
  CRH_BYTES: 64,
@@ -705,7 +760,7 @@ export const ml_dsa44: DSA = /* @__PURE__ */ (() =>
705
760
  }))();
706
761
 
707
762
  /** ML-DSA-65 for 192-bit security level. Not recommended after 2030, as per ASD. */
708
- export const ml_dsa65: DSA = /* @__PURE__ */ (() =>
763
+ export const ml_dsa65: TRet<DSA> = /* @__PURE__ */ (() =>
709
764
  getDilithium({
710
765
  ...PARAMS[3],
711
766
  CRH_BYTES: 64,
@@ -717,7 +772,7 @@ export const ml_dsa65: DSA = /* @__PURE__ */ (() =>
717
772
  }))();
718
773
 
719
774
  /** ML-DSA-87 for 256-bit security level. OK after 2030, as per ASD. */
720
- export const ml_dsa87: DSA = /* @__PURE__ */ (() =>
775
+ export const ml_dsa87: TRet<DSA> = /* @__PURE__ */ (() =>
721
776
  getDilithium({
722
777
  ...PARAMS[5],
723
778
  CRH_BYTES: 64,