@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.
- package/README.md +158 -180
- package/_crystals.d.ts +0 -1
- package/_crystals.d.ts.map +1 -1
- package/_crystals.js +1 -31
- package/_crystals.js.map +1 -1
- package/esm/_crystals.d.ts +33 -0
- package/esm/_crystals.d.ts.map +1 -0
- package/esm/_crystals.js +0 -30
- package/esm/_crystals.js.map +1 -1
- package/esm/index.d.ts +2 -0
- package/esm/index.d.ts.map +1 -0
- package/esm/ml-dsa.d.ts +44 -0
- package/esm/ml-dsa.d.ts.map +1 -0
- package/esm/ml-dsa.js +67 -88
- package/esm/ml-dsa.js.map +1 -1
- package/esm/ml-kem.d.ts +55 -0
- package/esm/ml-kem.d.ts.map +1 -0
- package/esm/ml-kem.js +26 -83
- package/esm/ml-kem.js.map +1 -1
- package/esm/slh-dsa.d.ts +46 -0
- package/esm/slh-dsa.d.ts.map +1 -0
- package/esm/slh-dsa.js +27 -111
- package/esm/slh-dsa.js.map +1 -1
- package/esm/utils.d.ts +38 -0
- package/esm/utils.d.ts.map +1 -0
- package/esm/utils.js +2 -1
- package/esm/utils.js.map +1 -1
- package/ml-dsa.d.ts +27 -20
- package/ml-dsa.d.ts.map +1 -1
- package/ml-dsa.js +66 -87
- package/ml-dsa.js.map +1 -1
- package/ml-kem.d.ts +1 -80
- package/ml-kem.d.ts.map +1 -1
- package/ml-kem.js +26 -83
- package/ml-kem.js.map +1 -1
- package/package.json +14 -22
- package/slh-dsa.d.ts +0 -24
- package/slh-dsa.d.ts.map +1 -1
- package/slh-dsa.js +27 -111
- package/slh-dsa.js.map +1 -1
- package/src/_crystals.ts +0 -33
- package/src/ml-dsa.ts +75 -92
- package/src/ml-kem.ts +28 -87
- package/src/slh-dsa.ts +27 -121
- package/src/utils.ts +2 -1
- package/utils.d.ts +2 -2
- package/utils.d.ts.map +1 -1
- package/utils.js +7 -6
- 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
|
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
|
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) =>
|
76
|
-
decode: (i: number) => (compress
|
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)
|
141
|
+
function getDilithium(opts: DilithiumOpts) {
|
143
142
|
const { K, L, GAMMA1, GAMMA2, TAU, ETA, OMEGA } = opts;
|
144
|
-
const {
|
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(
|
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
|
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 =
|
313
|
-
const seedCoder = splitCoder(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
|
-
|
324
|
+
const internal: Signer = {
|
317
325
|
signRandBytes,
|
318
326
|
keygen: (seed = randomBytes(32)) => {
|
319
|
-
|
320
|
-
const
|
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
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
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
|
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
|
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
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
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
|
-
|
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
|
-
|
494
|
-
|
495
|
-
|
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,
|
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
|
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
|
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(
|
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(
|
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
|
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
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
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
|
-
|
301
|
-
|
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
|
-
|
319
|
-
|
320
|
-
|
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
|
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
|
});
|