@noble/post-quantum 0.1.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
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
  });