@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.
- 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
|
});
|