@noble/post-quantum 0.5.4 → 0.6.0
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 +42 -11
- package/_crystals.d.ts +84 -0
- package/_crystals.d.ts.map +1 -1
- package/_crystals.js +64 -3
- package/_crystals.js.map +1 -1
- package/falcon.d.ts +84 -0
- package/falcon.d.ts.map +1 -0
- package/falcon.js +2378 -0
- package/falcon.js.map +1 -0
- package/hybrid.d.ts +171 -1
- package/hybrid.d.ts.map +1 -1
- package/hybrid.js +369 -54
- package/hybrid.js.map +1 -1
- package/ml-dsa.d.ts +22 -1
- package/ml-dsa.d.ts.map +1 -1
- package/ml-dsa.js +101 -51
- package/ml-dsa.js.map +1 -1
- package/ml-kem.d.ts +27 -3
- package/ml-kem.d.ts.map +1 -1
- package/ml-kem.js +154 -52
- package/ml-kem.js.map +1 -1
- package/package.json +12 -5
- package/slh-dsa.d.ts +116 -13
- package/slh-dsa.d.ts.map +1 -1
- package/slh-dsa.js +134 -35
- package/slh-dsa.js.map +1 -1
- package/src/_crystals.ts +101 -7
- package/src/falcon.ts +2470 -0
- package/src/hybrid.ts +393 -71
- package/src/ml-dsa.ts +144 -74
- package/src/ml-kem.ts +168 -54
- package/src/slh-dsa.ts +203 -44
- package/src/utils.ts +320 -15
- package/utils.d.ts +283 -4
- package/utils.d.ts.map +1 -1
- package/utils.js +245 -14
- package/utils.js.map +1 -1
package/src/ml-kem.ts
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
*/
|
|
22
22
|
/*! noble-post-quantum - MIT License (c) 2024 Paul Miller (paulmillr.com) */
|
|
23
23
|
import { sha3_256, sha3_512, shake256 } from '@noble/hashes/sha3.js';
|
|
24
|
-
import { type CHash, u32 } from '@noble/hashes/utils.js';
|
|
24
|
+
import { type CHash, swap32IfBE, u32 } from '@noble/hashes/utils.js';
|
|
25
25
|
import { genCrystals, type XOF, XOF128 } from './_crystals.ts';
|
|
26
26
|
import {
|
|
27
27
|
abytes,
|
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
type Coder,
|
|
30
30
|
copyBytes,
|
|
31
31
|
equalBytes,
|
|
32
|
+
getMask,
|
|
32
33
|
type KEM,
|
|
33
34
|
randomBytes,
|
|
34
35
|
splitCoder,
|
|
@@ -41,7 +42,10 @@ const N = 256; // Kyber (not FIPS-203) supports different lengths, but all std m
|
|
|
41
42
|
const Q = 3329; // 13*(2**8)+1, modulo prime
|
|
42
43
|
const F = 3303; // 3303 ≡ 128**(−1) mod q (FIPS-203)
|
|
43
44
|
const ROOT_OF_UNITY = 17; // ζ = 17 ∈ Zq is a primitive 256-th root of unity modulo Q. ζ**128 ≡−1
|
|
44
|
-
|
|
45
|
+
// treeshake: keep genCrystals behind the object so PARAMS-only bundles can drop it entirely.
|
|
46
|
+
// Shared CRYSTALS helper in the ML-KEM branch: Kyber mode, 7-bit bit-reversal,
|
|
47
|
+
// and Uint16Array polys because current coefficients stay reduced modulo q.
|
|
48
|
+
const crystals = /* @__PURE__ */ genCrystals({
|
|
45
49
|
N,
|
|
46
50
|
Q,
|
|
47
51
|
F,
|
|
@@ -52,67 +56,100 @@ const { mod, nttZetas, NTT, bitsCoder } = genCrystals({
|
|
|
52
56
|
});
|
|
53
57
|
|
|
54
58
|
/** FIPS 203: 7. Parameter Sets */
|
|
59
|
+
/** Public ML-KEM parameter-set description. */
|
|
55
60
|
export type KEMParam = {
|
|
61
|
+
/** Polynomial size. */
|
|
56
62
|
N: number;
|
|
63
|
+
/** Module rank. */
|
|
57
64
|
K: number;
|
|
65
|
+
/** Prime modulus. */
|
|
58
66
|
Q: number;
|
|
67
|
+
/** CBD parameter used for secret-key noise. */
|
|
59
68
|
ETA1: number;
|
|
69
|
+
/** CBD parameter used for error noise. */
|
|
60
70
|
ETA2: number;
|
|
71
|
+
/** Compression width for the `u` vector. */
|
|
61
72
|
du: number;
|
|
73
|
+
/** Compression width for the `v` polynomial. */
|
|
62
74
|
dv: number;
|
|
75
|
+
/** Required strength of the randomness source in bits. */
|
|
63
76
|
RBGstrength: number;
|
|
64
77
|
};
|
|
65
78
|
/** Internal params of ML-KEM versions */
|
|
66
79
|
// prettier-ignore
|
|
67
|
-
|
|
80
|
+
/** Built-in ML-KEM parameter presets keyed by the public export names
|
|
81
|
+
* `ml_kem512` / `ml_kem768` / `ml_kem1024`.
|
|
82
|
+
* `RBGstrength` is Table 2's required randomness-source strength in bits,
|
|
83
|
+
* not a generic security label.
|
|
84
|
+
*/
|
|
85
|
+
export const PARAMS: Record<string, KEMParam> = /* @__PURE__ */ (() => ({
|
|
68
86
|
512: { N, Q, K: 2, ETA1: 3, ETA2: 2, du: 10, dv: 4, RBGstrength: 128 },
|
|
69
87
|
768: { N, Q, K: 3, ETA1: 2, ETA2: 2, du: 10, dv: 4, RBGstrength: 192 },
|
|
70
88
|
1024:{ N, Q, K: 4, ETA1: 2, ETA2: 2, du: 11, dv: 5, RBGstrength: 256 },
|
|
71
|
-
} as const;
|
|
89
|
+
} as const))();
|
|
72
90
|
|
|
73
91
|
// FIPS-203: compress/decompress
|
|
74
92
|
const compress = (d: number): Coder<number, number> => {
|
|
75
|
-
//
|
|
76
|
-
|
|
77
|
-
//
|
|
93
|
+
// d=12 is the ByteEncode12/ByteDecode12 path, not lossy compression.
|
|
94
|
+
// ByteDecode12 interprets each 12-bit word modulo q; without that reduction the public-key
|
|
95
|
+
// modulus check in encapsulate() becomes a no-op for malformed coefficients like 4095.
|
|
96
|
+
if (d >= 12) return { encode: (i: number) => i, decode: (i: number) => (i >= Q ? i - Q : i) };
|
|
78
97
|
// Comments map to python implementation in RFC (draft-cfrg-schwabe-kyber)
|
|
79
98
|
// const round = (i: number) => Math.floor(i + 0.5) | 0;
|
|
80
99
|
const a = 2 ** (d - 1);
|
|
81
100
|
return {
|
|
82
|
-
//
|
|
101
|
+
// This only matches standalone Compress_d after bitsCoder masks the result into Z_(2^d).
|
|
83
102
|
encode: (i: number) => ((i << d) + Q / 2) / Q,
|
|
84
103
|
// const decompress = (i: number) => round((Q / 2 ** d) * i);
|
|
85
104
|
decode: (i: number) => (i * Q + a) >>> d,
|
|
86
105
|
};
|
|
87
106
|
};
|
|
88
107
|
|
|
108
|
+
// Raw ByteEncode_d / ByteDecode_d from FIPS 203 operate on d-bit words directly.
|
|
109
|
+
// That differs from `polyCoder(d)` for d<12, where noble folds packing together with the lossy
|
|
110
|
+
// ciphertext compression step used by u/v. Tests that exercise the spec's raw packing surface need
|
|
111
|
+
// this exact non-lossy variant instead.
|
|
112
|
+
const byteCoder = (d: number) =>
|
|
113
|
+
crystals.bitsCoder(
|
|
114
|
+
d,
|
|
115
|
+
d === 12
|
|
116
|
+
? { encode: (i: number) => i, decode: (i: number) => (i >= Q ? i - Q : i) }
|
|
117
|
+
: { encode: (i: number) => i, decode: (i: number) => i }
|
|
118
|
+
);
|
|
119
|
+
|
|
89
120
|
// NOTE: we merge encoding and compress because it is faster, also both require same d param
|
|
90
|
-
//
|
|
121
|
+
// d=12 is the ByteEncode12/ByteDecode12 path rather than compression, and caller-side
|
|
122
|
+
// public-key modulus checks route through this helper's decode/encode roundtrip.
|
|
123
|
+
// Converts between bytes and d-bits compressed representation.
|
|
124
|
+
// Kinda like convertRadix2 from @scure/base.
|
|
91
125
|
// decode(encode(t)) == t, but there is loss of information on encode(decode(t))
|
|
92
|
-
const polyCoder = (d: number) => bitsCoder(d, compress(d));
|
|
126
|
+
const polyCoder = (d: number) => (d === 12 ? byteCoder(12) : crystals.bitsCoder(d, compress(d)));
|
|
93
127
|
|
|
94
128
|
// Poly is mod Q, so 12 bits
|
|
95
129
|
type Poly = Uint16Array<any>;
|
|
96
130
|
|
|
97
131
|
function polyAdd(a: Poly, b: Poly) {
|
|
98
|
-
|
|
132
|
+
// Mutates `a` in place; callers must pass two N=256 polynomials.
|
|
133
|
+
for (let i = 0; i < N; i++) a[i] = crystals.mod(a[i] + b[i]); // a += b
|
|
99
134
|
}
|
|
100
135
|
function polySub(a: Poly, b: Poly) {
|
|
101
|
-
|
|
136
|
+
// Mutates `a` in place; callers must pass two N=256 polynomials.
|
|
137
|
+
for (let i = 0; i < N; i++) a[i] = crystals.mod(a[i] - b[i]); // a -= b
|
|
102
138
|
}
|
|
103
139
|
|
|
104
140
|
// FIPS-203: Computes the product of two degree-one polynomials with respect to a quadratic modulus
|
|
105
141
|
function BaseCaseMultiply(a0: number, a1: number, b0: number, b1: number, zeta: number) {
|
|
106
|
-
|
|
107
|
-
const
|
|
142
|
+
// `zeta` here is Algorithm 11's γ = ζ^(2BitRev_7(i)+1).
|
|
143
|
+
const c0 = crystals.mod(a1 * b1 * zeta + a0 * b0);
|
|
144
|
+
const c1 = crystals.mod(a0 * b1 + a1 * b0);
|
|
108
145
|
return { c0, c1 };
|
|
109
146
|
}
|
|
110
147
|
|
|
111
|
-
// FIPS-203: Computes the product (in the ring Tq) of two NTT representations.
|
|
112
|
-
//
|
|
148
|
+
// FIPS-203: Computes the product (in the ring Tq) of two NTT representations.
|
|
149
|
+
// Works in place on `f`; `g` is read-only and both inputs must already be in NTT form.
|
|
113
150
|
function MultiplyNTTs(f: Poly, g: Poly): Poly {
|
|
114
151
|
for (let i = 0; i < N / 2; i++) {
|
|
115
|
-
let z = nttZetas[64 + (i >> 1)];
|
|
152
|
+
let z = crystals.nttZetas[64 + (i >> 1)];
|
|
116
153
|
if (i & 1) z = -z;
|
|
117
154
|
const { c0, c1 } = BaseCaseMultiply(f[2 * i + 0], f[2 * i + 1], g[2 * i + 0], g[2 * i + 1], z);
|
|
118
155
|
f[2 * i + 0] = c0;
|
|
@@ -136,6 +173,8 @@ type KyberOpts = KEMParam & {
|
|
|
136
173
|
|
|
137
174
|
// Return poly in NTT representation
|
|
138
175
|
function SampleNTT(xof: XofGet) {
|
|
176
|
+
// The reader must already bind the Algorithm 7 seed||j||i bytes
|
|
177
|
+
// and return block lengths divisible by 3.
|
|
139
178
|
const r: Poly = new Uint16Array(N);
|
|
140
179
|
for (let j = 0; j < N; ) {
|
|
141
180
|
const b = xof();
|
|
@@ -151,11 +190,14 @@ function SampleNTT(xof: XofGet) {
|
|
|
151
190
|
}
|
|
152
191
|
|
|
153
192
|
// Sampling from the centered binomial distribution
|
|
154
|
-
// Returns poly with small coefficients (noise/errors)
|
|
155
|
-
|
|
156
|
-
|
|
193
|
+
// Returns poly with small coefficients (noise/errors) stored modulo q in ordinary coefficient form.
|
|
194
|
+
// Current callers only use Table 2 eta values {2,3} and PRF outputs of exactly 64*eta bytes.
|
|
195
|
+
const sampleCBDBytes = (buf: Uint8Array, eta: number): Poly => {
|
|
157
196
|
const r: Poly = new Uint16Array(N);
|
|
197
|
+
// CBD consumes the PRF bitstream in little-endian byte order; normalize the word view on BE,
|
|
198
|
+
// then swap it back so callers still observe `buf` as read-only.
|
|
158
199
|
const b32 = u32(buf);
|
|
200
|
+
swap32IfBE(b32);
|
|
159
201
|
let len = 0;
|
|
160
202
|
for (let i = 0, p = 0, bb = 0, t0 = 0; i < b32.length; i++) {
|
|
161
203
|
let b = b32[i];
|
|
@@ -167,18 +209,25 @@ function sampleCBD(PRF: PRF, seed: Uint8Array, nonce: number, eta: number): Poly
|
|
|
167
209
|
t0 = bb;
|
|
168
210
|
bb = 0;
|
|
169
211
|
} else if (len === 2 * eta) {
|
|
170
|
-
r[p++] = mod(t0 - bb);
|
|
212
|
+
r[p++] = crystals.mod(t0 - bb);
|
|
171
213
|
bb = 0;
|
|
172
214
|
len = 0;
|
|
173
215
|
}
|
|
174
216
|
}
|
|
175
217
|
}
|
|
218
|
+
swap32IfBE(b32);
|
|
176
219
|
if (len) throw new Error(`sampleCBD: leftover bits: ${len}`);
|
|
177
220
|
return r;
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
function sampleCBD(PRF: PRF, seed: Uint8Array, nonce: number, eta: number): Poly {
|
|
224
|
+
return sampleCBDBytes(PRF((eta * N) / 4, seed, nonce), eta);
|
|
178
225
|
}
|
|
179
226
|
|
|
180
227
|
// K-PKE
|
|
181
|
-
//
|
|
228
|
+
// Internal ML-KEM subroutine only: exact 32-byte `seed` / `msg` inputs
|
|
229
|
+
// come from Algorithms 13-15, and the helper mutates decoded temporary
|
|
230
|
+
// polynomials in place while leaving caller byte arrays unchanged.
|
|
182
231
|
const genKPKE = (opts: KyberOpts) => {
|
|
183
232
|
const { K, PRF, XOF, HASH512, ETA1, ETA2, du, dv } = opts;
|
|
184
233
|
const poly1 = polyCoder(1);
|
|
@@ -199,18 +248,21 @@ const genKPKE = (opts: KyberOpts) => {
|
|
|
199
248
|
abytes(seed, 32, 'seed');
|
|
200
249
|
const seedDst = new Uint8Array(33);
|
|
201
250
|
seedDst.set(seed);
|
|
251
|
+
// FIPS 203 Algorithm 13 appends the parameter-set byte `k`
|
|
252
|
+
// before `G(d || k)`, so expanding the same 32-byte seed
|
|
253
|
+
// under a different ML-KEM parameter set yields unrelated keys.
|
|
202
254
|
seedDst[32] = K;
|
|
203
255
|
const seedHash = HASH512(seedDst);
|
|
204
256
|
|
|
205
257
|
const [rho, sigma] = seedCoder.decode(seedHash);
|
|
206
258
|
const sHat: Poly[] = [];
|
|
207
259
|
const tHat: Poly[] = [];
|
|
208
|
-
for (let i = 0; i < K; i++) sHat.push(NTT.encode(sampleCBD(PRF, sigma, i, ETA1)));
|
|
260
|
+
for (let i = 0; i < K; i++) sHat.push(crystals.NTT.encode(sampleCBD(PRF, sigma, i, ETA1)));
|
|
209
261
|
const x = XOF(rho);
|
|
210
262
|
for (let i = 0; i < K; i++) {
|
|
211
|
-
const e = NTT.encode(sampleCBD(PRF, sigma, K + i, ETA1));
|
|
263
|
+
const e = crystals.NTT.encode(sampleCBD(PRF, sigma, K + i, ETA1));
|
|
212
264
|
for (let j = 0; j < K; j++) {
|
|
213
|
-
const aji = SampleNTT(x.get(j, i)); // A[
|
|
265
|
+
const aji = SampleNTT(x.get(j, i)); // A[i][j], inplace
|
|
214
266
|
polyAdd(e, MultiplyNTTs(aji, sHat[j]));
|
|
215
267
|
}
|
|
216
268
|
tHat.push(e); // t ← A ◦ s + e
|
|
@@ -226,7 +278,7 @@ const genKPKE = (opts: KyberOpts) => {
|
|
|
226
278
|
encrypt: (publicKey: Uint8Array, msg: Uint8Array, seed: Uint8Array) => {
|
|
227
279
|
const [tHat, rho] = publicCoder.decode(publicKey);
|
|
228
280
|
const rHat = [];
|
|
229
|
-
for (let i = 0; i < K; i++) rHat.push(NTT.encode(sampleCBD(PRF, seed, i, ETA1)));
|
|
281
|
+
for (let i = 0; i < K; i++) rHat.push(crystals.NTT.encode(sampleCBD(PRF, seed, i, ETA1)));
|
|
230
282
|
const x = XOF(rho);
|
|
231
283
|
const tmp2 = new Uint16Array(N);
|
|
232
284
|
const u = [];
|
|
@@ -234,17 +286,17 @@ const genKPKE = (opts: KyberOpts) => {
|
|
|
234
286
|
const e1 = sampleCBD(PRF, seed, K + i, ETA2);
|
|
235
287
|
const tmp = new Uint16Array(N);
|
|
236
288
|
for (let j = 0; j < K; j++) {
|
|
237
|
-
const aij = SampleNTT(x.get(i, j)); // A[
|
|
289
|
+
const aij = SampleNTT(x.get(i, j)); // A[j][i], inplace transpose access
|
|
238
290
|
polyAdd(tmp, MultiplyNTTs(aij, rHat[j])); // t += aij * rHat[j]
|
|
239
291
|
}
|
|
240
|
-
polyAdd(e1, NTT.decode(tmp)); // e1 += tmp
|
|
292
|
+
polyAdd(e1, crystals.NTT.decode(tmp)); // e1 += tmp
|
|
241
293
|
u.push(e1);
|
|
242
294
|
polyAdd(tmp2, MultiplyNTTs(tHat[i], rHat[i])); // t2 += tHat[i] * rHat[i]
|
|
243
295
|
cleanBytes(tmp);
|
|
244
296
|
}
|
|
245
297
|
x.clean();
|
|
246
298
|
const e2 = sampleCBD(PRF, seed, 2 * K, ETA2);
|
|
247
|
-
polyAdd(e2, NTT.decode(tmp2)); // e2 += tmp2
|
|
299
|
+
polyAdd(e2, crystals.NTT.decode(tmp2)); // e2 += tmp2
|
|
248
300
|
const v = poly1.decode(msg); // encode plaintext m into polynomial v
|
|
249
301
|
polyAdd(v, e2); // v += e2
|
|
250
302
|
cleanBytes(tHat, rHat, tmp2, e2);
|
|
@@ -254,14 +306,24 @@ const genKPKE = (opts: KyberOpts) => {
|
|
|
254
306
|
const [u, v] = cipherCoder.decode(cipherText);
|
|
255
307
|
const sk = secretCoder.decode(privateKey); // s ← ByteDecode_12(dkPKE)
|
|
256
308
|
const tmp = new Uint16Array(N);
|
|
257
|
-
|
|
258
|
-
|
|
309
|
+
// tmp += sk[i] * u[i]
|
|
310
|
+
for (let i = 0; i < K; i++) polyAdd(tmp, MultiplyNTTs(sk[i], crystals.NTT.encode(u[i])));
|
|
311
|
+
polySub(v, crystals.NTT.decode(tmp)); // w = v' - tmp
|
|
259
312
|
cleanBytes(tmp, sk, u);
|
|
260
313
|
return poly1.encode(v);
|
|
261
314
|
},
|
|
262
315
|
};
|
|
263
316
|
};
|
|
264
317
|
|
|
318
|
+
/**
|
|
319
|
+
* Public ML-KEM wrapper over the internal K-PKE subroutine.
|
|
320
|
+
* `keygen(seed)` and `encapsulate(publicKey, msg)` are deterministic/test-oriented hooks that map
|
|
321
|
+
* more directly to Algorithms 16-17 than to the pure no-input / random-internal Algorithms 19-20.
|
|
322
|
+
* decapsulate() tries to follow the Algorithms 18/21 implicit-reject structure as closely as
|
|
323
|
+
* practical here by re-encrypting, comparing ciphertexts, returning `Khat` on match or `Kbar` on
|
|
324
|
+
* mismatch, and zeroizing the non-returned shared-secret candidate; JS/JIT still provides no
|
|
325
|
+
* constant-time guarantees for that path.
|
|
326
|
+
*/
|
|
265
327
|
function createKyber(opts: KyberOpts) {
|
|
266
328
|
const KPKE = genKPKE(opts);
|
|
267
329
|
const { HASH256, HASH512, KDF } = opts;
|
|
@@ -297,7 +359,8 @@ function createKyber(opts: KyberOpts) {
|
|
|
297
359
|
|
|
298
360
|
// FIPS-203 includes additional verification check for modulus
|
|
299
361
|
const eke = publicKey.subarray(0, 384 * opts.K);
|
|
300
|
-
|
|
362
|
+
// Copy because of inplace encoding
|
|
363
|
+
const ek = KPKESecretCoder.encode(KPKESecretCoder.decode(copyBytes(eke)));
|
|
301
364
|
// (Modulus check.) Perform the computation ek ← ByteEncode12(ByteDecode12(eke)).
|
|
302
365
|
// If ek = ̸ eke, the input is invalid. (See Section 4.2.1.)
|
|
303
366
|
if (!equalBytes(ek, eke)) {
|
|
@@ -305,7 +368,8 @@ function createKyber(opts: KyberOpts) {
|
|
|
305
368
|
throw new Error('ML-KEM.encapsulate: wrong publicKey modulus');
|
|
306
369
|
}
|
|
307
370
|
cleanBytes(ek);
|
|
308
|
-
|
|
371
|
+
// derive randomness
|
|
372
|
+
const kr = HASH512.create().update(msg).update(HASH256(publicKey)).digest();
|
|
309
373
|
const cipherText = KPKE.encrypt(publicKey, msg, kr.subarray(32, 64));
|
|
310
374
|
cleanBytes(kr.subarray(32));
|
|
311
375
|
return { cipherText, sharedSecret: kr.subarray(0, 32) };
|
|
@@ -322,10 +386,13 @@ function createKyber(opts: KyberOpts) {
|
|
|
322
386
|
throw new Error('invalid secretKey: hash check failed');
|
|
323
387
|
const [sk, publicKey, publicKeyHash, z] = secretCoder.decode(secretKey);
|
|
324
388
|
const msg = KPKE.decrypt(cipherText, sk);
|
|
325
|
-
|
|
389
|
+
// derive randomness, Khat, rHat = G(mHat || h)
|
|
390
|
+
const kr = HASH512.create().update(msg).update(publicKeyHash).digest();
|
|
326
391
|
const Khat = kr.subarray(0, 32);
|
|
327
|
-
|
|
328
|
-
const
|
|
392
|
+
// re-encrypt using the derived randomness
|
|
393
|
+
const cipherText2 = KPKE.encrypt(publicKey, msg, kr.subarray(32, 64));
|
|
394
|
+
// if ciphertexts do not match, “implicitly reject”
|
|
395
|
+
const isValid = equalBytes(cipherText, cipherText2);
|
|
329
396
|
const Kbar = KDF.create({ dkLen: 32 }).update(z).update(cipherText).digest();
|
|
330
397
|
cleanBytes(msg, cipherText2, !isValid ? Khat : Kbar);
|
|
331
398
|
return isValid ? Khat : Kbar;
|
|
@@ -333,6 +400,8 @@ function createKyber(opts: KyberOpts) {
|
|
|
333
400
|
};
|
|
334
401
|
}
|
|
335
402
|
|
|
403
|
+
// FIPS 203's PRF_eta binding: current callers use only 32-byte keys, one-byte nonces,
|
|
404
|
+
// and dkLen values {128, 192}; out-of-range nonce numbers still wrap modulo 256 here.
|
|
336
405
|
function shakePRF(dkLen: number, key: Uint8Array, nonce: number) {
|
|
337
406
|
return shake256
|
|
338
407
|
.create({ dkLen })
|
|
@@ -341,28 +410,73 @@ function shakePRF(dkLen: number, key: Uint8Array, nonce: number) {
|
|
|
341
410
|
.digest();
|
|
342
411
|
}
|
|
343
412
|
|
|
344
|
-
|
|
413
|
+
// Fixed ML-KEM hash/XOF bindings. `KDF` here is the spec's fixed 32-byte `J` call,
|
|
414
|
+
// and swapping any field changes the scheme rather than tuning an internal dependency.
|
|
415
|
+
const opts = /* @__PURE__ */ (() => ({
|
|
345
416
|
HASH256: sha3_256,
|
|
346
417
|
HASH512: sha3_512,
|
|
347
418
|
KDF: shake256,
|
|
348
419
|
XOF: XOF128,
|
|
349
420
|
PRF: shakePRF,
|
|
350
|
-
};
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
421
|
+
}))();
|
|
422
|
+
// Parameter-set instantiation step for the spec's "ML-KEM-x" names; current correctness relies
|
|
423
|
+
// on the internal PARAMS rows rather than local validation of arbitrary KEMParam objects.
|
|
424
|
+
const mk = (params: KEMParam) =>
|
|
425
|
+
createKyber({
|
|
426
|
+
...opts,
|
|
427
|
+
...params,
|
|
428
|
+
});
|
|
357
429
|
|
|
358
|
-
/**
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
430
|
+
/**
|
|
431
|
+
* ML-KEM-512: Table 2 row `k=2, η1=3, η2=2, du=10, dv=4`; Table 3 sizes `800/1632/768/32`.
|
|
432
|
+
* The ASD lifecycle note here is external policy guidance, not a FIPS 203 requirement.
|
|
433
|
+
*/
|
|
434
|
+
export const ml_kem512: KEM = /* @__PURE__ */ (() => mk(PARAMS[512]))();
|
|
435
|
+
/**
|
|
436
|
+
* ML-KEM-768: Table 2 row `k=3, η1=2, η2=2, du=10, dv=4`; Table 3 sizes `1184/2400/1088/32`.
|
|
437
|
+
* The ASD lifecycle note here is external policy guidance, not a FIPS 203 requirement.
|
|
438
|
+
*/
|
|
439
|
+
export const ml_kem768: KEM = /* @__PURE__ */ (() => mk(PARAMS[768]))();
|
|
440
|
+
/**
|
|
441
|
+
* ML-KEM-1024: Table 2 row `k=4, η1=2, η2=2, du=11, dv=5`; Table 3 sizes `1568/3168/1568/32`.
|
|
442
|
+
* The ASD lifecycle note here is external policy guidance, not a FIPS 203 requirement.
|
|
443
|
+
*/
|
|
444
|
+
export const ml_kem1024: KEM = /* @__PURE__ */ (() => mk(PARAMS[1024]))();
|
|
363
445
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
});
|
|
446
|
+
// NOTE: for tests only, don't use. This keeps the exact internal ML-KEM math surfaces available
|
|
447
|
+
// without re-implementing them in separate test code.
|
|
448
|
+
export const __tests: any = /* @__PURE__ */ (() => ({
|
|
449
|
+
Compress_d: (x: number, d: number) => {
|
|
450
|
+
if (d < 1 || d > 11) throw new Error(`Compress_d: expected d in [1..11], got ${d}`);
|
|
451
|
+
return compress(d).encode(x) & getMask(d);
|
|
452
|
+
},
|
|
453
|
+
Decompress_d: (y: number, d: number) => {
|
|
454
|
+
if (d < 1 || d > 11) throw new Error(`Decompress_d: expected d in [1..11], got ${d}`);
|
|
455
|
+
return compress(d).decode(y);
|
|
456
|
+
},
|
|
457
|
+
ByteEncode_d: (F: Uint16Array, d: number) => {
|
|
458
|
+
if (d < 1 || d > 12) throw new Error(`ByteEncode_d: expected d in [1..12], got ${d}`);
|
|
459
|
+
return byteCoder(d).encode(F);
|
|
460
|
+
},
|
|
461
|
+
ByteDecode_d: (B: Uint8Array, d: number) => {
|
|
462
|
+
if (d < 1 || d > 12) throw new Error(`ByteDecode_d: expected d in [1..12], got ${d}`);
|
|
463
|
+
return byteCoder(d).decode(B);
|
|
464
|
+
},
|
|
465
|
+
NTT: (f: Uint16Array) => crystals.NTT.encode(Uint16Array.from(f)),
|
|
466
|
+
NTT_inv: (fHat: Uint16Array) => crystals.NTT.decode(Uint16Array.from(fHat)),
|
|
467
|
+
MultiplyNTTs: (fHat: Uint16Array, gHat: Uint16Array) =>
|
|
468
|
+
MultiplyNTTs(Uint16Array.from(fHat), Uint16Array.from(gHat)),
|
|
469
|
+
SamplePolyCBD: (B: Uint8Array, eta: number) => {
|
|
470
|
+
abytes(B, 64 * eta, 'B');
|
|
471
|
+
return sampleCBDBytes(B, eta);
|
|
472
|
+
},
|
|
473
|
+
SampleNTT: (B: Uint8Array) => {
|
|
474
|
+
abytes(B, 34, 'B');
|
|
475
|
+
const xof = XOF128(B.subarray(0, 32));
|
|
476
|
+
try {
|
|
477
|
+
return SampleNTT(xof.get(B[32], B[33]));
|
|
478
|
+
} finally {
|
|
479
|
+
xof.clean();
|
|
480
|
+
}
|
|
481
|
+
},
|
|
482
|
+
}))();
|