@noble/post-quantum 0.1.0 → 0.2.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.
Files changed (49) hide show
  1. package/README.md +158 -180
  2. package/_crystals.d.ts +0 -1
  3. package/_crystals.d.ts.map +1 -1
  4. package/_crystals.js +1 -31
  5. package/_crystals.js.map +1 -1
  6. package/esm/_crystals.d.ts +33 -0
  7. package/esm/_crystals.d.ts.map +1 -0
  8. package/esm/_crystals.js +0 -30
  9. package/esm/_crystals.js.map +1 -1
  10. package/esm/index.d.ts +2 -0
  11. package/esm/index.d.ts.map +1 -0
  12. package/esm/ml-dsa.d.ts +44 -0
  13. package/esm/ml-dsa.d.ts.map +1 -0
  14. package/esm/ml-dsa.js +67 -88
  15. package/esm/ml-dsa.js.map +1 -1
  16. package/esm/ml-kem.d.ts +55 -0
  17. package/esm/ml-kem.d.ts.map +1 -0
  18. package/esm/ml-kem.js +26 -83
  19. package/esm/ml-kem.js.map +1 -1
  20. package/esm/slh-dsa.d.ts +46 -0
  21. package/esm/slh-dsa.d.ts.map +1 -0
  22. package/esm/slh-dsa.js +27 -111
  23. package/esm/slh-dsa.js.map +1 -1
  24. package/esm/utils.d.ts +38 -0
  25. package/esm/utils.d.ts.map +1 -0
  26. package/esm/utils.js +2 -1
  27. package/esm/utils.js.map +1 -1
  28. package/ml-dsa.d.ts +27 -20
  29. package/ml-dsa.d.ts.map +1 -1
  30. package/ml-dsa.js +66 -87
  31. package/ml-dsa.js.map +1 -1
  32. package/ml-kem.d.ts +1 -80
  33. package/ml-kem.d.ts.map +1 -1
  34. package/ml-kem.js +26 -83
  35. package/ml-kem.js.map +1 -1
  36. package/package.json +14 -22
  37. package/slh-dsa.d.ts +0 -24
  38. package/slh-dsa.d.ts.map +1 -1
  39. package/slh-dsa.js +27 -111
  40. package/slh-dsa.js.map +1 -1
  41. package/src/_crystals.ts +0 -33
  42. package/src/ml-dsa.ts +75 -92
  43. package/src/ml-kem.ts +28 -87
  44. package/src/slh-dsa.ts +27 -121
  45. package/src/utils.ts +2 -1
  46. package/utils.d.ts +2 -2
  47. package/utils.d.ts.map +1 -1
  48. package/utils.js +7 -6
  49. package/utils.js.map +1 -1
package/src/ml-dsa.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /*! noble-post-quantum - MIT License (c) 2024 Paul Miller (paulmillr.com) */
2
2
  import { shake256 } from '@noble/hashes/sha3';
3
- import { genCrystals, XOF, XOF128, XOF256, XOF_AES } from './_crystals.js';
3
+ import { genCrystals, XOF, XOF128, XOF256 } from './_crystals.js';
4
4
  import {
5
5
  BytesCoderLen,
6
6
  Signer,
@@ -10,6 +10,7 @@ import {
10
10
  randomBytes,
11
11
  splitCoder,
12
12
  vecCoder,
13
+ concatBytes,
13
14
  } from './utils.js';
14
15
 
15
16
  /*
@@ -18,11 +19,6 @@ Lattice-based digital signature algorithm. See
18
19
  [repo](https://github.com/pq-crystals/dilithium).
19
20
  Dilithium has similar internals to Kyber, but their keys and params are different.
20
21
 
21
- Three versions are provided:
22
-
23
- 1. Dilithium v3.0, v3.0 AES
24
- 2. Dilithium v3.1, v3.1 AES
25
- 3. ML-DSA aka [FIPS-204](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.204.ipd.pdf)
26
22
  */
27
23
 
28
24
  // Constants
@@ -70,10 +66,13 @@ const { mod, smod, NTT, bitsCoder } = genCrystals({
70
66
  brvBits: 8,
71
67
  });
72
68
 
73
- const polyCoder = (d: number, compress?: (n: number) => number) =>
69
+ const id = <T>(n: T): T => n;
70
+ type IdNum = (n: number) => number;
71
+
72
+ const polyCoder = (d: number, compress: IdNum = id, verify: IdNum = id) =>
74
73
  bitsCoder(d, {
75
- encode: (i: number) => (compress ? compress(i) : i),
76
- decode: (i: number) => (compress ? compress(i) : i),
74
+ encode: (i: number) => compress(verify(i)),
75
+ decode: (i: number) => verify(compress(i)),
77
76
  });
78
77
 
79
78
  const polyAdd = (a: Poly, b: Poly) => {
@@ -122,6 +121,8 @@ function RejNTTPoly(xof: XofGet) {
122
121
  return r;
123
122
  }
124
123
 
124
+ const EMPTY = new Uint8Array(0);
125
+
125
126
  type DilithiumOpts = {
126
127
  K: number;
127
128
  L: number;
@@ -135,13 +136,11 @@ type DilithiumOpts = {
135
136
  TR_BYTES: number;
136
137
  XOF128: XOF;
137
138
  XOF256: XOF;
138
- FIPS204?: boolean;
139
- V31?: boolean;
140
139
  };
141
140
 
142
- function getDilithium(opts: DilithiumOpts): Signer {
141
+ function getDilithium(opts: DilithiumOpts) {
143
142
  const { K, L, GAMMA1, GAMMA2, TAU, ETA, OMEGA } = opts;
144
- const { FIPS204, V31, CRH_BYTES, TR_BYTES, C_TILDE_BYTES, XOF128, XOF256 } = opts;
143
+ const { CRH_BYTES, TR_BYTES, C_TILDE_BYTES, XOF128, XOF256 } = opts;
145
144
 
146
145
  if (![2, 4].includes(ETA)) throw new Error('Wrong ETA');
147
146
  if (![1 << 17, 1 << 19].includes(GAMMA1)) throw new Error('Wrong GAMMA1');
@@ -171,6 +170,8 @@ function getDilithium(opts: DilithiumOpts): Signer {
171
170
  // But they return different results! However, decompose is same.
172
171
  // So, either there is a bug in Dilithium ref implementation or in FIPS204.
173
172
  // For now, lets use dilithium one, so test vectors can be passed.
173
+ // See
174
+ // https://github.com/GiacomoPope/dilithium-py?tab=readme-ov-file#optimising-decomposition-and-making-hints
174
175
  return res0;
175
176
  };
176
177
 
@@ -219,7 +220,15 @@ function getDilithium(opts: DilithiumOpts): Signer {
219
220
  },
220
221
  };
221
222
 
222
- const ETACoder = polyCoder(ETA === 2 ? 3 : 4, (i: number) => ETA - i);
223
+ const ETACoder = polyCoder(
224
+ ETA === 2 ? 3 : 4,
225
+ (i: number) => ETA - i,
226
+ (i: number) => {
227
+ if (!(-ETA <= i && i <= ETA))
228
+ throw new Error(`malformed key s1/s3 ${i} outside of ETA range [${-ETA}, ${ETA}]`);
229
+ return i;
230
+ }
231
+ );
223
232
  const T0Coder = polyCoder(13, (i: number) => (1 << (D - 1)) - i);
224
233
  const T1Coder = polyCoder(10);
225
234
  // Requires smod. Need to fix!
@@ -262,7 +271,7 @@ function getDilithium(opts: DilithiumOpts): Signer {
262
271
  const SampleInBall = (seed: Uint8Array) => {
263
272
  // Samples a polynomial c ∈ Rq with coeffcients from {−1, 0, 1} and Hamming weight τ
264
273
  const pre = newPoly(N);
265
- const s = shake256.create({}).update(seed.slice(0, 32));
274
+ const s = shake256.create({}).update(seed);
266
275
  const buf = new Uint8Array(shake256.blockLen);
267
276
  s.xofInto(buf);
268
277
  const masks = buf.slice(0, 8);
@@ -309,15 +318,21 @@ function getDilithium(opts: DilithiumOpts): Signer {
309
318
  return { v, cnt };
310
319
  };
311
320
 
312
- const signRandBytes = FIPS204 ? 32 : CRH_BYTES;
313
- const seedCoder = splitCoder(32, V31 ? 64 : 32, 32);
314
- const seedXOF = V31 ? XOF256 : XOF128;
321
+ const signRandBytes = 32;
322
+ const seedCoder = splitCoder(32, 64, 32);
315
323
  // API & argument positions are exactly as in FIPS204.
316
- return {
324
+ const internal: Signer = {
317
325
  signRandBytes,
318
326
  keygen: (seed = randomBytes(32)) => {
319
- const [rho, rhoPrime, K_] = seedCoder.decode(shake256(seed, { dkLen: seedCoder.bytesLen }));
320
- const xofPrime = seedXOF(rhoPrime);
327
+ // H(𝜉||IntegerToBytes(𝑘, 1)||IntegerToBytes(ℓ, 1), 128) 2: expand seed
328
+ const seedDst = new Uint8Array(32 + 2);
329
+ seedDst.set(seed);
330
+ seedDst[32] = K;
331
+ seedDst[33] = L;
332
+ const [rho, rhoPrime, K_] = seedCoder.decode(
333
+ shake256(seedDst, { dkLen: seedCoder.bytesLen })
334
+ );
335
+ const xofPrime = XOF256(rhoPrime);
321
336
  const s1 = [];
322
337
  for (let i = 0; i < L; i++) s1.push(RejBoundedPoly(xofPrime.get(i & 0xff, (i >> 8) & 0xff)));
323
338
  const s2 = [];
@@ -348,7 +363,7 @@ function getDilithium(opts: DilithiumOpts): Signer {
348
363
  // STATS
349
364
  // Kyber512: { calls: 4, xofs: 12 }, Kyber768: { calls: 9, xofs: 27 }, Kyber1024: { calls: 16, xofs: 48 }
350
365
  // DSA44: { calls: 24, xofs: 24 }, DSA65: { calls: 41, xofs: 41 }, DSA87: { calls: 71, xofs: 71 }
351
- cleanBytes(rho, rhoPrime, K_, s1, s2, s1Hat, t, t0, t1, tr);
366
+ cleanBytes(rho, rhoPrime, K_, s1, s2, s1Hat, t, t0, t1, tr, seedDst);
352
367
  return { publicKey, secretKey };
353
368
  },
354
369
  // NOTE: random is optional.
@@ -372,16 +387,17 @@ function getDilithium(opts: DilithiumOpts): Signer {
372
387
  }
373
388
  // This part is per msg
374
389
  const mu = shake256.create({ dkLen: CRH_BYTES }).update(tr).update(msg).digest(); // 6: µ ← H(tr||M, 512) ▷ Compute message representative µ
375
- let rhoprime; // Compute private random seed
376
- if (FIPS204) {
377
- const rnd = random ? random : new Uint8Array(32);
378
- ensureBytes(rnd);
379
- rhoprime = shake256.create({ dkLen: CRH_BYTES }).update(_K).update(rnd).update(mu).digest(); // ρ′← H(K||rnd||µ, 512)
380
- } else {
381
- rhoprime = random
382
- ? random
383
- : shake256.create({ dkLen: CRH_BYTES }).update(_K).update(mu).digest();
384
- }
390
+
391
+ // Compute private random seed
392
+ const rnd = random ? random : new Uint8Array(32);
393
+ ensureBytes(rnd);
394
+ const rhoprime = shake256
395
+ .create({ dkLen: CRH_BYTES })
396
+ .update(_K)
397
+ .update(rnd)
398
+ .update(mu)
399
+ .digest(); // ρ′← H(K||rnd||µ, 512)
400
+
385
401
  ensureBytes(rhoprime, CRH_BYTES);
386
402
  const x256 = XOF256(rhoprime, ZCoder.bytesLen);
387
403
  // Rejection sampling loop
@@ -407,7 +423,7 @@ function getDilithium(opts: DilithiumOpts): Signer {
407
423
  .update(W1Vec.encode(w1))
408
424
  .digest();
409
425
  // Verifer’s challenge
410
- const cHat = NTT.encode(SampleInBall(cTilde.subarray(0, 32))); // c ← SampleInBall(c˜1); cˆ ← NTT(c)
426
+ const cHat = NTT.encode(SampleInBall(cTilde)); // c ← SampleInBall(c˜1); cˆ ← NTT(c)
411
427
  // ⟨⟨cs1⟩⟩ ← NTT−1(cˆ◦ sˆ1)
412
428
  const cs1 = s1.map((i) => MultiplyNTTs(i, cHat));
413
429
  for (let i = 0; i < L; i++) {
@@ -450,7 +466,7 @@ function getDilithium(opts: DilithiumOpts): Signer {
450
466
  for (let i = 0; i < L; i++) if (polyChknorm(z[i], GAMMA1 - BETA)) return false;
451
467
  const mu = shake256.create({ dkLen: CRH_BYTES }).update(tr).update(msg).digest(); // 7: µ ← H(tr||M, 512)
452
468
  // Compute verifer’s challenge from c˜
453
- const c = NTT.encode(SampleInBall(cTilde.subarray(0, 32))); // c ← SampleInBall(c˜1)
469
+ const c = NTT.encode(SampleInBall(cTilde)); // c ← SampleInBall(c˜1)
454
470
  const zNtt = z.map((i) => i.slice()); // zNtt = NTT(z)
455
471
  for (let i = 0; i < L; i++) NTT.encode(zNtt[i]);
456
472
  const wTick1 = [];
@@ -474,66 +490,39 @@ function getDilithium(opts: DilithiumOpts): Signer {
474
490
  .update(mu)
475
491
  .update(W1Vec.encode(wTick1))
476
492
  .digest();
477
- if (FIPS204) {
478
- // Additional checks in FIPS-204:
479
- // [[ ||z||∞ < γ1 − β ]] and [[c ˜ = c˜′]] and [[number of 1’s in h is ≤ ω]]
480
- for (const t of h) {
481
- const sum = t.reduce((acc, i) => acc + i, 0);
482
- if (!(sum <= OMEGA)) return false;
483
- }
484
- for (const t of z) if (polyChknorm(t, GAMMA1 - BETA)) return false;
493
+ // Additional checks in FIPS-204:
494
+ // [[ ||z||∞ < γ1 − β ]] and [[c ˜ = c˜′]] and [[number of 1’s in h is ≤ ω]]
495
+ for (const t of h) {
496
+ const sum = t.reduce((acc, i) => acc + i, 0);
497
+ if (!(sum <= OMEGA)) return false;
485
498
  }
499
+ for (const t of z) if (polyChknorm(t, GAMMA1 - BETA)) return false;
486
500
  return equalBytes(cTilde, c2);
487
501
  },
488
502
  };
489
- }
490
-
491
- function getDilithiumVersions(cfg: Partial<DilithiumOpts>) {
503
+ const getMessage = (msg: Uint8Array, ctx = EMPTY) => {
504
+ ensureBytes(msg);
505
+ ensureBytes(ctx);
506
+ if (ctx.length > 255) throw new Error('context should be less than 255 bytes');
507
+ return concatBytes(new Uint8Array([0, ctx.length]), ctx, msg);
508
+ };
509
+ // TODO: no hash-dsa vectors for now, so we don't implement it yet
492
510
  return {
493
- dilithium2: getDilithium({ ...PARAMS[2], ...cfg } as DilithiumOpts),
494
- dilithium3: getDilithium({ ...PARAMS[3], ...cfg } as DilithiumOpts),
495
- dilithium5: getDilithium({ ...PARAMS[5], ...cfg } as DilithiumOpts),
511
+ internal,
512
+ keygen: internal.keygen,
513
+ signRandBytes: internal.signRandBytes,
514
+ sign: (secretKey: Uint8Array, msg: Uint8Array, ctx = EMPTY, random?: Uint8Array) => {
515
+ const M = getMessage(msg, ctx);
516
+ const res = internal.sign(secretKey, M, random);
517
+ M.fill(0);
518
+ return res;
519
+ },
520
+ verify: (publicKey: Uint8Array, msg: Uint8Array, sig: Uint8Array, ctx = EMPTY) => {
521
+ return internal.verify(publicKey, getMessage(msg, ctx), sig);
522
+ },
496
523
  };
497
524
  }
498
525
 
499
- // v30 is NIST round 3 submission, for original vectors and benchmarking.
500
- // v31 is kyber: more secure than v30.
501
- // ml-dsa is NIST FIPS 204, but it is still a draft and may change.
502
-
503
- export const dilithium_v30 = /* @__PURE__ */ getDilithiumVersions({
504
- CRH_BYTES: 48,
505
- TR_BYTES: 48,
506
- C_TILDE_BYTES: 32,
507
- XOF128,
508
- XOF256,
509
- });
510
-
511
- export const dilithium_v31 = /* @__PURE__ */ getDilithiumVersions({
512
- CRH_BYTES: 64,
513
- TR_BYTES: 32,
514
- C_TILDE_BYTES: 32,
515
- XOF128,
516
- XOF256,
517
- V31: true,
518
- });
519
-
520
- export const dilithium_v30_aes = /* @__PURE__ */ getDilithiumVersions({
521
- CRH_BYTES: 48,
522
- TR_BYTES: 48,
523
- C_TILDE_BYTES: 32,
524
- XOF128: XOF_AES,
525
- XOF256: XOF_AES,
526
- });
527
-
528
- export const dilithium_v31_aes = /* @__PURE__ */ getDilithiumVersions({
529
- CRH_BYTES: 64,
530
- TR_BYTES: 32,
531
- C_TILDE_BYTES: 32,
532
- XOF128: XOF_AES,
533
- XOF256: XOF_AES,
534
- V31: true,
535
- });
536
-
537
526
  // ML-DSA
538
527
  export const ml_dsa44 = /* @__PURE__ */ getDilithium({
539
528
  ...PARAMS[2],
@@ -542,8 +531,6 @@ export const ml_dsa44 = /* @__PURE__ */ getDilithium({
542
531
  C_TILDE_BYTES: 32,
543
532
  XOF128,
544
533
  XOF256,
545
- V31: true,
546
- FIPS204: true,
547
534
  });
548
535
 
549
536
  export const ml_dsa65 = /* @__PURE__ */ getDilithium({
@@ -553,8 +540,6 @@ export const ml_dsa65 = /* @__PURE__ */ getDilithium({
553
540
  C_TILDE_BYTES: 48,
554
541
  XOF128,
555
542
  XOF256,
556
- V31: true,
557
- FIPS204: true,
558
543
  });
559
544
 
560
545
  export const ml_dsa87 = /* @__PURE__ */ getDilithium({
@@ -564,6 +549,4 @@ export const ml_dsa87 = /* @__PURE__ */ getDilithium({
564
549
  C_TILDE_BYTES: 64,
565
550
  XOF128,
566
551
  XOF256,
567
- V31: true,
568
- FIPS204: true,
569
552
  });
package/src/ml-kem.ts CHANGED
@@ -1,9 +1,7 @@
1
1
  /*! noble-post-quantum - MIT License (c) 2024 Paul Miller (paulmillr.com) */
2
- import { ctr } from '@noble/ciphers/aes';
3
- import { sha256, sha512 } from '@noble/hashes/sha2';
4
2
  import { sha3_256, sha3_512, shake256 } from '@noble/hashes/sha3';
5
3
  import { u32, wrapConstructor, wrapConstructorWithOpts } from '@noble/hashes/utils';
6
- import { genCrystals, XOF, XOF_AES, XOF128 } from './_crystals.js';
4
+ import { genCrystals, XOF, XOF128 } from './_crystals.js';
7
5
  import {
8
6
  Coder,
9
7
  cleanBytes,
@@ -34,16 +32,11 @@ There are some concerns with regards to security: see
34
32
  [djb blog](https://blog.cr.yp.to/20231003-countcorrectly.html) and
35
33
  [mailing list](https://groups.google.com/a/list.nist.gov/g/pqc-forum/c/W2VOzy0wz_E).
36
34
 
37
- Three versions are provided:
38
-
39
- 1. Kyber
40
- 2. Kyber-90s, using algorithms from 1990s
41
- 3. ML-KEM aka [FIPS-203](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.203.ipd.pdf)
42
35
  */
43
36
 
44
37
  const N = 256; // Kyber (not FIPS-203) supports different lengths, but all std modes were using 256
45
38
  const Q = 3329; // 13*(2**8)+1, modulo prime
46
- const F = 3303; // 3303 ≡ 128−1 mod q (FIPS-203)
39
+ const F = 3303; // 3303 ≡ 128**(−1) mod q (FIPS-203)
47
40
  const ROOT_OF_UNITY = 17; // ζ = 17 ∈ Zq is a primitive 256-th root of unity modulo Q. ζ**128 ≡−1
48
41
  const { mod, nttZetas, NTT, bitsCoder } = genCrystals({
49
42
  N,
@@ -136,7 +129,6 @@ type KyberOpts = ParameterSet & {
136
129
  KDF: Hash | HashWOpts;
137
130
  XOF: XOF; // (seed: Uint8Array, len: number, x: number, y: number) => Uint8Array;
138
131
  PRF: PRF;
139
- FIPS203?: boolean;
140
132
  };
141
133
 
142
134
  // Return poly in NTT representation
@@ -185,7 +177,7 @@ function sampleCBD(PRF: PRF, seed: Uint8Array, nonce: number, eta: number): Poly
185
177
  // K-PKE
186
178
  // As per FIPS-203, it doesn't perform any input validation and can't be used in standalone fashion.
187
179
  const genKPKE = (opts: KyberOpts) => {
188
- const { K, PRF, XOF, HASH512, ETA1, ETA2, du, dv, FIPS203 } = opts;
180
+ const { K, PRF, XOF, HASH512, ETA1, ETA2, du, dv } = opts;
189
181
  const poly1 = polyCoder(1);
190
182
  const polyV = polyCoder(dv);
191
183
  const polyU = polyCoder(du);
@@ -199,7 +191,12 @@ const genKPKE = (opts: KyberOpts) => {
199
191
  publicKeyLen: publicCoder.bytesLen,
200
192
  cipherTextLen: cipherCoder.bytesLen,
201
193
  keygen: (seed: Uint8Array) => {
202
- const [rho, sigma] = seedCoder.decode(HASH512(seed));
194
+ const seedDst = new Uint8Array(33);
195
+ seedDst.set(seed);
196
+ seedDst[32] = K;
197
+ const seedHash = HASH512(seedDst);
198
+
199
+ const [rho, sigma] = seedCoder.decode(seedHash);
203
200
  const sHat: Poly[] = [];
204
201
  const tHat: Poly[] = [];
205
202
  for (let i = 0; i < K; i++) sHat.push(NTT.encode(sampleCBD(PRF, sigma, i, ETA1)));
@@ -207,7 +204,7 @@ const genKPKE = (opts: KyberOpts) => {
207
204
  for (let i = 0; i < K; i++) {
208
205
  const e = NTT.encode(sampleCBD(PRF, sigma, K + i, ETA1));
209
206
  for (let j = 0; j < K; j++) {
210
- const aji = SampleNTT(FIPS203 ? x.get(i, j) : x.get(j, i)); // A[j][i], inplace
207
+ const aji = SampleNTT(x.get(j, i)); // A[j][i], inplace
211
208
  polyAdd(e, MultiplyNTTs(aji, sHat[j]));
212
209
  }
213
210
  tHat.push(e); // t ← A ◦ s + e
@@ -217,7 +214,7 @@ const genKPKE = (opts: KyberOpts) => {
217
214
  publicKey: publicCoder.encode([tHat, rho]),
218
215
  secretKey: secretCoder.encode(sHat),
219
216
  };
220
- cleanBytes(rho, sigma, sHat, tHat);
217
+ cleanBytes(rho, sigma, sHat, tHat, seedDst, seedHash);
221
218
  return res;
222
219
  },
223
220
  encrypt: (publicKey: Uint8Array, msg: Uint8Array, seed: Uint8Array) => {
@@ -231,7 +228,7 @@ const genKPKE = (opts: KyberOpts) => {
231
228
  const e1 = sampleCBD(PRF, seed, K + i, ETA2);
232
229
  const tmp = new Uint16Array(N);
233
230
  for (let j = 0; j < K; j++) {
234
- const aij = SampleNTT(FIPS203 ? x.get(j, i) : x.get(i, j)); // A[i][j], inplace
231
+ const aij = SampleNTT(x.get(i, j)); // A[i][j], inplace
235
232
  polyAdd(tmp, MultiplyNTTs(aij, rHat[j])); // t += aij * rHat[j]
236
233
  }
237
234
  polyAdd(e1, NTT.decode(tmp)); // e1 += tmp
@@ -261,7 +258,7 @@ const genKPKE = (opts: KyberOpts) => {
261
258
 
262
259
  function createKyber(opts: KyberOpts) {
263
260
  const KPKE = genKPKE(opts);
264
- const { HASH256, HASH512, KDF, FIPS203 } = opts;
261
+ const { HASH256, HASH512, KDF } = opts;
265
262
  const { secretCoder: KPKESecretCoder, cipherTextLen } = KPKE;
266
263
  const publicKeyLen = KPKE.publicKeyLen; // 384*K+32
267
264
  const secretCoder = splitCoder(KPKE.secretKeyLen, KPKE.publicKeyLen, 32, 32);
@@ -282,29 +279,21 @@ function createKyber(opts: KyberOpts) {
282
279
  encapsulate: (publicKey: Uint8Array, msg = randomBytes(32)) => {
283
280
  ensureBytes(publicKey, publicKeyLen);
284
281
  ensureBytes(msg, msgLen);
285
- if (!FIPS203) msg = HASH256(msg); // NOTE: ML-KEM doesn't have this step!
286
- else {
287
- // FIPS-203 includes additional verification check for modulus
288
- const eke = publicKey.subarray(0, 384 * opts.K);
289
- const ek = KPKESecretCoder.encode(KPKESecretCoder.decode(eke.slice())); // Copy because of inplace encoding
290
- // (Modulus check.) Perform the computation ek ByteEncode12(ByteDecode12(eke)).
291
- // If ek = ̸ eke, the input is invalid. (See Section 4.2.1.)
292
- if (!equalBytes(ek, eke)) {
293
- cleanBytes(ek);
294
- throw new Error('ML-KEM.encapsulate: wrong publicKey modulus');
295
- }
282
+
283
+ // FIPS-203 includes additional verification check for modulus
284
+ const eke = publicKey.subarray(0, 384 * opts.K);
285
+ const ek = KPKESecretCoder.encode(KPKESecretCoder.decode(eke.slice())); // Copy because of inplace encoding
286
+ // (Modulus check.) Perform the computation ek ByteEncode12(ByteDecode12(eke)).
287
+ // If ek = ̸ eke, the input is invalid. (See Section 4.2.1.)
288
+ if (!equalBytes(ek, eke)) {
296
289
  cleanBytes(ek);
290
+ throw new Error('ML-KEM.encapsulate: wrong publicKey modulus');
297
291
  }
292
+ cleanBytes(ek);
298
293
  const kr = HASH512.create().update(msg).update(HASH256(publicKey)).digest(); // derive randomness
299
294
  const cipherText = KPKE.encrypt(publicKey, msg, kr.subarray(32, 64));
300
- if (FIPS203) return { cipherText, sharedSecret: kr.subarray(0, 32) };
301
- const cipherTextHash = HASH256(cipherText);
302
- const sharedSecret = KDF.create({})
303
- .update(kr.subarray(0, 32))
304
- .update(cipherTextHash)
305
- .digest();
306
- cleanBytes(kr, cipherTextHash);
307
- return { cipherText, sharedSecret };
295
+ kr.subarray(32).fill(0);
296
+ return { cipherText, sharedSecret: kr.subarray(0, 32) };
308
297
  },
309
298
  decapsulate: (cipherText: Uint8Array, secretKey: Uint8Array) => {
310
299
  ensureBytes(secretKey, secretKeyLen); // 768*k + 96
@@ -315,43 +304,13 @@ function createKyber(opts: KyberOpts) {
315
304
  const Khat = kr.subarray(0, 32);
316
305
  const cipherText2 = KPKE.encrypt(publicKey, msg, kr.subarray(32, 64)); // re-encrypt using the derived randomness
317
306
  const isValid = equalBytes(cipherText, cipherText2); // if ciphertexts do not match, “implicitly reject”
318
- if (FIPS203) {
319
- const Kbar = KDF.create({ dkLen: 32 }).update(z).update(cipherText).digest();
320
- cleanBytes(msg, cipherText2, !isValid ? Khat : Kbar);
321
- return isValid ? Khat : Kbar;
322
- }
323
- const cipherTextHash = HASH256(cipherText);
324
- const sharedSecret = KDF.create({ dkLen: 32 })
325
- .update(isValid ? Khat : z)
326
- .update(cipherTextHash)
327
- .digest();
328
- cleanBytes(msg, cipherTextHash, cipherText2, Khat, z);
329
- return sharedSecret;
307
+ const Kbar = KDF.create({ dkLen: 32 }).update(z).update(cipherText).digest();
308
+ cleanBytes(msg, cipherText2, !isValid ? Khat : Kbar);
309
+ return isValid ? Khat : Kbar;
330
310
  },
331
311
  };
332
312
  }
333
313
 
334
- function PRF(l: number, key: Uint8Array, nonce: number) {
335
- const _nonce = new Uint8Array(16);
336
- _nonce[0] = nonce;
337
- return ctr(key, _nonce).encrypt(new Uint8Array(l));
338
- }
339
-
340
- const opts90s = { HASH256: sha256, HASH512: sha512, KDF: sha256, XOF: XOF_AES, PRF };
341
-
342
- export const kyber512_90s = /* @__PURE__ */ createKyber({
343
- ...opts90s,
344
- ...PARAMS[512],
345
- });
346
- export const kyber768_90s = /* @__PURE__ */ createKyber({
347
- ...opts90s,
348
- ...PARAMS[768],
349
- });
350
- export const kyber1024_90s = /* @__PURE__ */ createKyber({
351
- ...opts90s,
352
- ...PARAMS[1024],
353
- });
354
-
355
314
  function shakePRF(dkLen: number, key: Uint8Array, nonce: number) {
356
315
  return shake256
357
316
  .create({ dkLen })
@@ -368,36 +327,18 @@ const opts = {
368
327
  PRF: shakePRF,
369
328
  };
370
329
 
371
- export const kyber512 = /* @__PURE__ */ createKyber({
372
- ...opts,
373
- ...PARAMS[512],
374
- });
375
- export const kyber768 = /* @__PURE__ */ createKyber({
376
- ...opts,
377
- ...PARAMS[768],
378
- });
379
- export const kyber1024 = /* @__PURE__ */ createKyber({
380
- ...opts,
381
- ...PARAMS[1024],
382
- });
383
-
384
330
  /**
385
- * FIPS-203 (draft) ML-KEM.
386
- * Unsafe: we can't cross-verify, because there are no test vectors or other implementations.
331
+ * FIPS-203 ML-KEM.
387
332
  */
388
-
389
333
  export const ml_kem512 = /* @__PURE__ */ createKyber({
390
334
  ...opts,
391
335
  ...PARAMS[512],
392
- FIPS203: true,
393
336
  });
394
337
  export const ml_kem768 = /* @__PURE__ */ createKyber({
395
338
  ...opts,
396
339
  ...PARAMS[768],
397
- FIPS203: true,
398
340
  });
399
341
  export const ml_kem1024 = /* @__PURE__ */ createKyber({
400
342
  ...opts,
401
343
  ...PARAMS[1024],
402
- FIPS203: true,
403
344
  });