@noble/post-quantum 0.6.0 → 0.7.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 +106 -80
- package/_crystals.d.ts +23 -16
- package/_crystals.js +50 -12
- package/falcon.d.ts +7 -8
- package/falcon.js +109 -74
- package/hybrid.d.ts +21 -32
- package/hybrid.js +157 -78
- package/index.d.ts +0 -1
- package/index.js +8 -1
- package/ml-dsa.d.ts +35 -9
- package/ml-dsa.js +116 -43
- package/ml-kem.d.ts +46 -5
- package/ml-kem.js +175 -67
- package/package.json +10 -18
- package/slh-dsa.d.ts +44 -24
- package/slh-dsa.js +150 -84
- package/src/_crystals.ts +86 -35
- package/src/falcon.ts +230 -169
- package/src/hybrid.ts +243 -129
- package/src/index.ts +8 -0
- package/src/ml-dsa.ts +194 -87
- package/src/ml-kem.ts +283 -122
- package/src/slh-dsa.ts +310 -182
- package/src/utils.ts +244 -48
- package/utils.d.ts +99 -24
- package/utils.js +92 -24
- package/_crystals.d.ts.map +0 -1
- package/_crystals.js.map +0 -1
- package/falcon.d.ts.map +0 -1
- package/falcon.js.map +0 -1
- package/hybrid.d.ts.map +0 -1
- package/hybrid.js.map +0 -1
- package/index.d.ts.map +0 -1
- package/index.js.map +0 -1
- package/ml-dsa.d.ts.map +0 -1
- package/ml-dsa.js.map +0 -1
- package/ml-kem.d.ts.map +0 -1
- package/ml-kem.js.map +0 -1
- package/slh-dsa.d.ts.map +0 -1
- package/slh-dsa.js.map +0 -1
- package/utils.d.ts.map +0 -1
- package/utils.js.map +0 -1
package/ml-kem.js
CHANGED
|
@@ -48,10 +48,10 @@ const crystals = /* @__PURE__ */ genCrystals({
|
|
|
48
48
|
* `RBGstrength` is Table 2's required randomness-source strength in bits,
|
|
49
49
|
* not a generic security label.
|
|
50
50
|
*/
|
|
51
|
-
export const PARAMS = /* @__PURE__ */ (() => ({
|
|
52
|
-
512: { N, Q, K: 2, ETA1: 3, ETA2: 2, du: 10, dv: 4, RBGstrength: 128 },
|
|
53
|
-
768: { N, Q, K: 3, ETA1: 2, ETA2: 2, du: 10, dv: 4, RBGstrength: 192 },
|
|
54
|
-
1024: { N, Q, K: 4, ETA1: 2, ETA2: 2, du: 11, dv: 5, RBGstrength: 256 },
|
|
51
|
+
export const PARAMS = /* @__PURE__ */ (() => Object.freeze({
|
|
52
|
+
512: Object.freeze({ N, Q, K: 2, ETA1: 3, ETA2: 2, du: 10, dv: 4, RBGstrength: 128 }),
|
|
53
|
+
768: Object.freeze({ N, Q, K: 3, ETA1: 2, ETA2: 2, du: 10, dv: 4, RBGstrength: 192 }),
|
|
54
|
+
1024: Object.freeze({ N, Q, K: 4, ETA1: 2, ETA2: 2, du: 11, dv: 5, RBGstrength: 256 }),
|
|
55
55
|
}))();
|
|
56
56
|
// FIPS-203: compress/decompress
|
|
57
57
|
const compress = (d) => {
|
|
@@ -84,26 +84,40 @@ const byteCoder = (d) => crystals.bitsCoder(d, d === 12
|
|
|
84
84
|
// Kinda like convertRadix2 from @scure/base.
|
|
85
85
|
// decode(encode(t)) == t, but there is loss of information on encode(decode(t))
|
|
86
86
|
const polyCoder = (d) => (d === 12 ? byteCoder(12) : crystals.bitsCoder(d, compress(d)));
|
|
87
|
-
|
|
87
|
+
// Coefficients always stay reduced in [0, Q) here (samplers, NTT and coders all reduce),
|
|
88
|
+
// so one conditional correction replaces the generic mod().
|
|
89
|
+
function polyAdd(a_, b_) {
|
|
90
|
+
const a = a_;
|
|
91
|
+
const b = b_;
|
|
88
92
|
// Mutates `a` in place; callers must pass two N=256 polynomials.
|
|
89
|
-
for (let i = 0; i < N; i++)
|
|
90
|
-
|
|
93
|
+
for (let i = 0; i < N; i++) {
|
|
94
|
+
const r = a[i] + b[i]; // a += b
|
|
95
|
+
a[i] = r >= Q ? r - Q : r;
|
|
96
|
+
}
|
|
91
97
|
}
|
|
92
|
-
function polySub(
|
|
98
|
+
function polySub(a_, b_) {
|
|
99
|
+
const a = a_;
|
|
100
|
+
const b = b_;
|
|
93
101
|
// Mutates `a` in place; callers must pass two N=256 polynomials.
|
|
94
|
-
for (let i = 0; i < N; i++)
|
|
95
|
-
|
|
102
|
+
for (let i = 0; i < N; i++) {
|
|
103
|
+
const r = a[i] - b[i]; // a -= b
|
|
104
|
+
a[i] = r < 0 ? r + Q : r;
|
|
105
|
+
}
|
|
96
106
|
}
|
|
97
107
|
// FIPS-203: Computes the product of two degree-one polynomials with respect to a quadratic modulus
|
|
98
108
|
function BaseCaseMultiply(a0, a1, b0, b1, zeta) {
|
|
99
109
|
// `zeta` here is Algorithm 11's γ = ζ^(2BitRev_7(i)+1).
|
|
100
|
-
|
|
110
|
+
// Reduce a1*b1 before multiplying by zeta: a1*b1*zeta would reach ~2^35, forcing JS engines
|
|
111
|
+
// into slow float fmod; with the extra reduction every intermediate fits int32.
|
|
112
|
+
const c0 = crystals.mod(crystals.mod(a1 * b1) * zeta + a0 * b0);
|
|
101
113
|
const c1 = crystals.mod(a0 * b1 + a1 * b0);
|
|
102
114
|
return { c0, c1 };
|
|
103
115
|
}
|
|
104
116
|
// FIPS-203: Computes the product (in the ring Tq) of two NTT representations.
|
|
105
117
|
// Works in place on `f`; `g` is read-only and both inputs must already be in NTT form.
|
|
106
|
-
function MultiplyNTTs(
|
|
118
|
+
function MultiplyNTTs(f_, g_) {
|
|
119
|
+
const f = f_;
|
|
120
|
+
const g = g_;
|
|
107
121
|
for (let i = 0; i < N / 2; i++) {
|
|
108
122
|
let z = crystals.nttZetas[64 + (i >> 1)];
|
|
109
123
|
if (i & 1)
|
|
@@ -115,7 +129,8 @@ function MultiplyNTTs(f, g) {
|
|
|
115
129
|
return f;
|
|
116
130
|
}
|
|
117
131
|
// Return poly in NTT representation
|
|
118
|
-
function SampleNTT(
|
|
132
|
+
function SampleNTT(xof_) {
|
|
133
|
+
const xof = xof_;
|
|
119
134
|
// The reader must already bind the Algorithm 7 seed||j||i bytes
|
|
120
135
|
// and return block lengths divisible by 3.
|
|
121
136
|
const r = new Uint16Array(N);
|
|
@@ -166,14 +181,16 @@ const sampleCBDBytes = (buf, eta) => {
|
|
|
166
181
|
throw new Error(`sampleCBD: leftover bits: ${len}`);
|
|
167
182
|
return r;
|
|
168
183
|
};
|
|
169
|
-
function sampleCBD(
|
|
184
|
+
function sampleCBD(PRF_, seed, nonce, eta) {
|
|
185
|
+
const PRF = PRF_;
|
|
170
186
|
return sampleCBDBytes(PRF((eta * N) / 4, seed, nonce), eta);
|
|
171
187
|
}
|
|
172
188
|
// K-PKE
|
|
173
189
|
// Internal ML-KEM subroutine only: exact 32-byte `seed` / `msg` inputs
|
|
174
190
|
// come from Algorithms 13-15, and the helper mutates decoded temporary
|
|
175
191
|
// polynomials in place while leaving caller byte arrays unchanged.
|
|
176
|
-
const genKPKE = (
|
|
192
|
+
const genKPKE = (opts_) => {
|
|
193
|
+
const opts = opts_;
|
|
177
194
|
const { K, PRF, XOF, HASH512, ETA1, ETA2, du, dv } = opts;
|
|
178
195
|
const poly1 = polyCoder(1);
|
|
179
196
|
const polyV = polyCoder(dv);
|
|
@@ -182,6 +199,34 @@ const genKPKE = (opts) => {
|
|
|
182
199
|
const secretCoder = vecCoder(polyCoder(12), K);
|
|
183
200
|
const cipherCoder = splitCoder('ciphertext', vecCoder(polyU, K), polyV);
|
|
184
201
|
const seedCoder = splitCoder('seed', 32, 32);
|
|
202
|
+
// Algorithm 14 (K-PKE.Encrypt) core, after ek parsing. `tHat` and every poly returned by
|
|
203
|
+
// `getA(i, j)` are treated as disposable scratch: they are mutated in place and wiped/dropped,
|
|
204
|
+
// so callers holding cached copies must pass fresh copies.
|
|
205
|
+
const encryptCore = (tHat, getA, msg, seed) => {
|
|
206
|
+
const rHat = [];
|
|
207
|
+
for (let i = 0; i < K; i++)
|
|
208
|
+
rHat.push(crystals.NTT.encode(sampleCBD(PRF, seed, i, ETA1)));
|
|
209
|
+
const tmp2 = new Uint16Array(N);
|
|
210
|
+
const u = [];
|
|
211
|
+
for (let i = 0; i < K; i++) {
|
|
212
|
+
const e1 = sampleCBD(PRF, seed, K + i, ETA2);
|
|
213
|
+
const tmp = new Uint16Array(N);
|
|
214
|
+
for (let j = 0; j < K; j++) {
|
|
215
|
+
const aij = getA(i, j); // A[j][i], inplace transpose access
|
|
216
|
+
polyAdd(tmp, MultiplyNTTs(aij, rHat[j])); // t += aij * rHat[j]
|
|
217
|
+
}
|
|
218
|
+
polyAdd(e1, crystals.NTT.decode(tmp)); // e1 += tmp
|
|
219
|
+
u.push(e1);
|
|
220
|
+
polyAdd(tmp2, MultiplyNTTs(tHat[i], rHat[i])); // t2 += tHat[i] * rHat[i]
|
|
221
|
+
cleanBytes(tmp);
|
|
222
|
+
}
|
|
223
|
+
const e2 = sampleCBD(PRF, seed, 2 * K, ETA2);
|
|
224
|
+
polyAdd(e2, crystals.NTT.decode(tmp2)); // e2 += tmp2
|
|
225
|
+
const v = poly1.decode(msg); // encode plaintext m into polynomial v
|
|
226
|
+
polyAdd(v, e2); // v += e2
|
|
227
|
+
cleanBytes(tHat, rHat, tmp2, e2);
|
|
228
|
+
return cipherCoder.encode([u, v]);
|
|
229
|
+
};
|
|
185
230
|
return {
|
|
186
231
|
secretCoder,
|
|
187
232
|
lengths: {
|
|
@@ -222,31 +267,26 @@ const genKPKE = (opts) => {
|
|
|
222
267
|
},
|
|
223
268
|
encrypt: (publicKey, msg, seed) => {
|
|
224
269
|
const [tHat, rho] = publicCoder.decode(publicKey);
|
|
225
|
-
const rHat = [];
|
|
226
|
-
for (let i = 0; i < K; i++)
|
|
227
|
-
rHat.push(crystals.NTT.encode(sampleCBD(PRF, seed, i, ETA1)));
|
|
228
270
|
const x = XOF(rho);
|
|
229
|
-
const
|
|
230
|
-
const u = [];
|
|
231
|
-
for (let i = 0; i < K; i++) {
|
|
232
|
-
const e1 = sampleCBD(PRF, seed, K + i, ETA2);
|
|
233
|
-
const tmp = new Uint16Array(N);
|
|
234
|
-
for (let j = 0; j < K; j++) {
|
|
235
|
-
const aij = SampleNTT(x.get(i, j)); // A[j][i], inplace transpose access
|
|
236
|
-
polyAdd(tmp, MultiplyNTTs(aij, rHat[j])); // t += aij * rHat[j]
|
|
237
|
-
}
|
|
238
|
-
polyAdd(e1, crystals.NTT.decode(tmp)); // e1 += tmp
|
|
239
|
-
u.push(e1);
|
|
240
|
-
polyAdd(tmp2, MultiplyNTTs(tHat[i], rHat[i])); // t2 += tHat[i] * rHat[i]
|
|
241
|
-
cleanBytes(tmp);
|
|
242
|
-
}
|
|
271
|
+
const res = encryptCore(tHat, (i, j) => SampleNTT(x.get(i, j)), msg, seed);
|
|
243
272
|
x.clean();
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
273
|
+
return res;
|
|
274
|
+
},
|
|
275
|
+
// Expands the full  matrix (public data derived from rho) once, so repeated encryptions
|
|
276
|
+
// against the same ek skip the K² SampleNTT XOF expansions. Cached polys are copied per
|
|
277
|
+
// call because encryptCore mutates its inputs in place.
|
|
278
|
+
prepare: (publicKey) => {
|
|
279
|
+
const [tHat, rho] = publicCoder.decode(publicKey);
|
|
280
|
+
const x = XOF(rho);
|
|
281
|
+
const A = [];
|
|
282
|
+
for (let i = 0; i < K; i++)
|
|
283
|
+
for (let j = 0; j < K; j++)
|
|
284
|
+
A.push(SampleNTT(x.get(i, j)));
|
|
285
|
+
x.clean();
|
|
286
|
+
return {
|
|
287
|
+
encrypt: (msg, seed) => encryptCore(tHat.map((p) => p.slice()), (i, j) => A[i * K + j].slice(), msg, seed),
|
|
288
|
+
clean: () => cleanBytes(tHat, A),
|
|
289
|
+
};
|
|
250
290
|
},
|
|
251
291
|
decrypt: (cipherText, privateKey) => {
|
|
252
292
|
const [u, v] = cipherCoder.decode(cipherText);
|
|
@@ -271,21 +311,35 @@ const genKPKE = (opts) => {
|
|
|
271
311
|
* constant-time guarantees for that path.
|
|
272
312
|
*/
|
|
273
313
|
function createKyber(opts) {
|
|
274
|
-
const
|
|
275
|
-
const
|
|
314
|
+
const rawOpts = opts;
|
|
315
|
+
const KPKE = genKPKE(rawOpts);
|
|
316
|
+
const { HASH256, HASH512, KDF } = rawOpts;
|
|
276
317
|
const { secretCoder: KPKESecretCoder, lengths } = KPKE;
|
|
277
318
|
const secretCoder = splitCoder('secretKey', lengths.secretKey, lengths.publicKey, 32, 32);
|
|
278
319
|
const msgLen = 32;
|
|
279
320
|
const seedLen = 64;
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
321
|
+
// FIPS-203 includes additional verification check for modulus
|
|
322
|
+
const validateModulus = (publicKey, fn) => {
|
|
323
|
+
const eke = publicKey.subarray(0, 384 * rawOpts.K);
|
|
324
|
+
// Copy because of inplace encoding
|
|
325
|
+
const ek = KPKESecretCoder.encode(KPKESecretCoder.decode(copyBytes(eke)));
|
|
326
|
+
// (Modulus check.) Perform the computation ek ← ByteEncode12(ByteDecode12(eke)).
|
|
327
|
+
// If ek = ̸ eke, the input is invalid. (See Section 4.2.1.)
|
|
328
|
+
const ok = equalBytes(ek, eke);
|
|
329
|
+
cleanBytes(ek);
|
|
330
|
+
if (!ok)
|
|
331
|
+
throw new Error(`ML-KEM.${fn}: wrong publicKey modulus`);
|
|
332
|
+
};
|
|
333
|
+
const kemLengths = Object.freeze({
|
|
334
|
+
...lengths,
|
|
335
|
+
seed: 64,
|
|
336
|
+
msg: msgLen,
|
|
337
|
+
msgRand: msgLen,
|
|
338
|
+
secretKey: secretCoder.bytesLen,
|
|
339
|
+
});
|
|
340
|
+
return Object.freeze({
|
|
341
|
+
info: Object.freeze({ type: 'ml-kem' }),
|
|
342
|
+
lengths: kemLengths,
|
|
289
343
|
keygen: (seed = randomBytes(seedLen)) => {
|
|
290
344
|
abytes(seed, seedLen, 'seed');
|
|
291
345
|
const { publicKey, secretKey: sk } = KPKE.keygen(seed.subarray(0, 32));
|
|
@@ -293,7 +347,10 @@ function createKyber(opts) {
|
|
|
293
347
|
// (dkPKE||ek||H(ek)||z)
|
|
294
348
|
const secretKey = secretCoder.encode([sk, publicKey, publicKeyHash, seed.subarray(32)]);
|
|
295
349
|
cleanBytes(sk, publicKeyHash);
|
|
296
|
-
return {
|
|
350
|
+
return {
|
|
351
|
+
publicKey: publicKey,
|
|
352
|
+
secretKey: secretKey,
|
|
353
|
+
};
|
|
297
354
|
},
|
|
298
355
|
getPublicKey: (secretKey) => {
|
|
299
356
|
const [_sk, publicKey, _publicKeyHash, _z] = secretCoder.decode(secretKey);
|
|
@@ -302,22 +359,15 @@ function createKyber(opts) {
|
|
|
302
359
|
encapsulate: (publicKey, msg = randomBytes(msgLen)) => {
|
|
303
360
|
abytes(publicKey, lengths.publicKey, 'publicKey');
|
|
304
361
|
abytes(msg, msgLen, 'message');
|
|
305
|
-
|
|
306
|
-
const eke = publicKey.subarray(0, 384 * opts.K);
|
|
307
|
-
// Copy because of inplace encoding
|
|
308
|
-
const ek = KPKESecretCoder.encode(KPKESecretCoder.decode(copyBytes(eke)));
|
|
309
|
-
// (Modulus check.) Perform the computation ek ← ByteEncode12(ByteDecode12(eke)).
|
|
310
|
-
// If ek = ̸ eke, the input is invalid. (See Section 4.2.1.)
|
|
311
|
-
if (!equalBytes(ek, eke)) {
|
|
312
|
-
cleanBytes(ek);
|
|
313
|
-
throw new Error('ML-KEM.encapsulate: wrong publicKey modulus');
|
|
314
|
-
}
|
|
315
|
-
cleanBytes(ek);
|
|
362
|
+
validateModulus(publicKey, 'encapsulate');
|
|
316
363
|
// derive randomness
|
|
317
364
|
const kr = HASH512.create().update(msg).update(HASH256(publicKey)).digest();
|
|
318
365
|
const cipherText = KPKE.encrypt(publicKey, msg, kr.subarray(32, 64));
|
|
319
366
|
cleanBytes(kr.subarray(32));
|
|
320
|
-
return {
|
|
367
|
+
return {
|
|
368
|
+
cipherText: cipherText,
|
|
369
|
+
sharedSecret: kr.subarray(0, 32),
|
|
370
|
+
};
|
|
321
371
|
},
|
|
322
372
|
decapsulate: (cipherText, secretKey) => {
|
|
323
373
|
abytes(secretKey, secretCoder.bytesLen, 'secretKey'); // 768*k + 96
|
|
@@ -339,10 +389,58 @@ function createKyber(opts) {
|
|
|
339
389
|
// if ciphertexts do not match, “implicitly reject”
|
|
340
390
|
const isValid = equalBytes(cipherText, cipherText2);
|
|
341
391
|
const Kbar = KDF.create({ dkLen: 32 }).update(z).update(cipherText).digest();
|
|
342
|
-
|
|
343
|
-
|
|
392
|
+
// kr[32:64] is the derived K-PKE encryption randomness: wipe it like encapsulate() does.
|
|
393
|
+
cleanBytes(msg, cipherText2, kr.subarray(32), !isValid ? Khat : Kbar);
|
|
394
|
+
return (isValid ? Khat : Kbar);
|
|
344
395
|
},
|
|
345
|
-
|
|
396
|
+
/**
|
|
397
|
+
* Experimental prototype: pre-expand a public key so repeated encapsulate/decapsulate
|
|
398
|
+
* against the same key skip re-validation, H(ek), t̂ decoding and the K² SampleNTT
|
|
399
|
+
* XOF expansions of Â. Only public data is cached; see {@link KEMPrepared}.
|
|
400
|
+
*/
|
|
401
|
+
prepare: (publicKey) => {
|
|
402
|
+
abytes(publicKey, lengths.publicKey, 'publicKey');
|
|
403
|
+
validateModulus(publicKey, 'prepare');
|
|
404
|
+
const ek = copyBytes(publicKey); // detach from the caller before caching
|
|
405
|
+
const publicKeyHash = HASH256(ek);
|
|
406
|
+
const cached = KPKE.prepare(ek);
|
|
407
|
+
return Object.freeze({
|
|
408
|
+
publicKey: ek,
|
|
409
|
+
encapsulate: (msg = randomBytes(msgLen)) => {
|
|
410
|
+
abytes(msg, msgLen, 'message');
|
|
411
|
+
const kr = HASH512.create().update(msg).update(publicKeyHash).digest();
|
|
412
|
+
const cipherText = cached.encrypt(msg, kr.subarray(32, 64));
|
|
413
|
+
cleanBytes(kr.subarray(32));
|
|
414
|
+
return {
|
|
415
|
+
cipherText: cipherText,
|
|
416
|
+
sharedSecret: kr.subarray(0, 32),
|
|
417
|
+
};
|
|
418
|
+
},
|
|
419
|
+
decapsulate: (cipherText, secretKey) => {
|
|
420
|
+
abytes(secretKey, secretCoder.bytesLen, 'secretKey');
|
|
421
|
+
abytes(cipherText, lengths.cipherText, 'cipherText');
|
|
422
|
+
const [sk, ekEmbedded, storedHash, z] = secretCoder.decode(secretKey);
|
|
423
|
+
// Under KEMPrepared's read-only publicKey contract, bind dk to the prepared key.
|
|
424
|
+
// Together with publicKeyHash = H(ek) computed in prepare(), this is equivalent to (and
|
|
425
|
+
// stronger than) FIPS 203 §7.3's `H(dk[384k : 768k+32]) == dk[768k+32 : 768k+64]`.
|
|
426
|
+
if (!equalBytes(ekEmbedded, ek) || !equalBytes(storedHash, publicKeyHash))
|
|
427
|
+
throw new Error('ML-KEM.decapsulate: secretKey does not match prepared publicKey');
|
|
428
|
+
const msg = KPKE.decrypt(cipherText, sk);
|
|
429
|
+
// derive randomness, Khat, rHat = G(mHat || h)
|
|
430
|
+
const kr = HASH512.create().update(msg).update(publicKeyHash).digest();
|
|
431
|
+
const Khat = kr.subarray(0, 32);
|
|
432
|
+
// re-encrypt using the derived randomness and cached Â/t̂
|
|
433
|
+
const cipherText2 = cached.encrypt(msg, kr.subarray(32, 64));
|
|
434
|
+
// if ciphertexts do not match, “implicitly reject”
|
|
435
|
+
const isValid = equalBytes(cipherText, cipherText2);
|
|
436
|
+
const Kbar = KDF.create({ dkLen: 32 }).update(z).update(cipherText).digest();
|
|
437
|
+
cleanBytes(msg, cipherText2, kr.subarray(32), !isValid ? Khat : Kbar);
|
|
438
|
+
return (isValid ? Khat : Kbar);
|
|
439
|
+
},
|
|
440
|
+
clean: cached.clean,
|
|
441
|
+
});
|
|
442
|
+
},
|
|
443
|
+
});
|
|
346
444
|
}
|
|
347
445
|
// FIPS 203's PRF_eta binding: current callers use only 32-byte keys, one-byte nonces,
|
|
348
446
|
// and dkLen values {128, 192}; out-of-range nonce numbers still wrap modulo 256 here.
|
|
@@ -371,6 +469,17 @@ const mk = (params) => createKyber({
|
|
|
371
469
|
/**
|
|
372
470
|
* ML-KEM-512: Table 2 row `k=2, η1=3, η2=2, du=10, dv=4`; Table 3 sizes `800/1632/768/32`.
|
|
373
471
|
* The ASD lifecycle note here is external policy guidance, not a FIPS 203 requirement.
|
|
472
|
+
* @example
|
|
473
|
+
* Generate deterministic ML-KEM-512 keys, encapsulate a shared secret, and decapsulate it.
|
|
474
|
+
* ```ts
|
|
475
|
+
* import { ml_kem512 } from '@noble/post-quantum/ml-kem.js';
|
|
476
|
+
* const seed = new Uint8Array(ml_kem512.lengths.seed!);
|
|
477
|
+
* const { secretKey, publicKey } = ml_kem512.keygen(seed);
|
|
478
|
+
* const msg = new Uint8Array(ml_kem512.lengths.msgRand!);
|
|
479
|
+
* const { cipherText, sharedSecret } = ml_kem512.encapsulate(publicKey, msg);
|
|
480
|
+
* const recovered = ml_kem512.decapsulate(cipherText, secretKey);
|
|
481
|
+
* const publicKey2 = ml_kem512.getPublicKey(secretKey);
|
|
482
|
+
* ```
|
|
374
483
|
*/
|
|
375
484
|
export const ml_kem512 = /* @__PURE__ */ (() => mk(PARAMS[512]))();
|
|
376
485
|
/**
|
|
@@ -385,7 +494,7 @@ export const ml_kem768 = /* @__PURE__ */ (() => mk(PARAMS[768]))();
|
|
|
385
494
|
export const ml_kem1024 = /* @__PURE__ */ (() => mk(PARAMS[1024]))();
|
|
386
495
|
// NOTE: for tests only, don't use. This keeps the exact internal ML-KEM math surfaces available
|
|
387
496
|
// without re-implementing them in separate test code.
|
|
388
|
-
export const __tests = /* @__PURE__ */ (() => ({
|
|
497
|
+
export const __tests = /* @__PURE__ */ (() => Object.freeze({
|
|
389
498
|
Compress_d: (x, d) => {
|
|
390
499
|
if (d < 1 || d > 11)
|
|
391
500
|
throw new Error(`Compress_d: expected d in [1..11], got ${d}`);
|
|
@@ -424,4 +533,3 @@ export const __tests = /* @__PURE__ */ (() => ({
|
|
|
424
533
|
}
|
|
425
534
|
},
|
|
426
535
|
}))();
|
|
427
|
-
//# sourceMappingURL=ml-kem.js.map
|
package/package.json
CHANGED
|
@@ -1,40 +1,32 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@noble/post-quantum",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Auditable & minimal JS implementation of post-quantum cryptography: FIPS 203, 204, 205, Falcon",
|
|
5
5
|
"files": [
|
|
6
6
|
"*.js",
|
|
7
|
-
"*.js.map",
|
|
8
7
|
"*.d.ts",
|
|
9
|
-
"*.d.ts.map",
|
|
10
8
|
"src"
|
|
11
9
|
],
|
|
12
10
|
"dependencies": {
|
|
13
|
-
"@noble/ciphers": "~2.
|
|
14
|
-
"@noble/curves": "~2.
|
|
15
|
-
"@noble/hashes": "~2.
|
|
11
|
+
"@noble/ciphers": "~2.3.0",
|
|
12
|
+
"@noble/curves": "~2.3.0",
|
|
13
|
+
"@noble/hashes": "~2.3.0"
|
|
16
14
|
},
|
|
17
15
|
"devDependencies": {
|
|
18
|
-
"@paulmillr/jsbt": "0.5
|
|
16
|
+
"@paulmillr/jsbt": "0.6.5",
|
|
19
17
|
"@types/node": "25.3.0",
|
|
20
18
|
"fast-check": "4.2.0",
|
|
21
19
|
"prettier": "3.6.2",
|
|
22
20
|
"typescript": "6.0.2"
|
|
23
21
|
},
|
|
24
22
|
"scripts": {
|
|
25
|
-
"
|
|
23
|
+
"benchmark": "node benchmark/pq.ts",
|
|
24
|
+
"benchmark:size": "npx bismar@0.1 -s",
|
|
26
25
|
"build": "tsc",
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
"check:readme": "npx --no @paulmillr/jsbt readme package.json",
|
|
30
|
-
"check:treeshake": "npx --no @paulmillr/jsbt treeshake package.json test/build/out-treeshake",
|
|
31
|
-
"check:jsdoc": "npx --no @paulmillr/jsbt tsdoc package.json",
|
|
32
|
-
"build:clean": "rm *.{js,js.map,d.ts,d.ts.map} 2> /dev/null",
|
|
26
|
+
"check": "jsbt-check",
|
|
27
|
+
"build:clean": "rm *.{js,d.ts} 2> /dev/null",
|
|
33
28
|
"format": "prettier --write 'src/**/*.{js,ts}' 'test/**/*.{js,ts,mjs}'",
|
|
34
|
-
"test": "node
|
|
35
|
-
"test:bun": "bun test/index.ts",
|
|
36
|
-
"test:deno": "deno --allow-env --allow-read test/index.ts",
|
|
37
|
-
"test:node20": "cd test; npx tsc; node compiled/test/index.js",
|
|
29
|
+
"test": "node test/index.ts",
|
|
38
30
|
"test:slow": "SLOW_TESTS=1 node test/index.ts"
|
|
39
31
|
},
|
|
40
32
|
"exports": {
|
package/slh-dsa.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type CHash } from '@noble/hashes/utils.js';
|
|
2
|
-
import { type Signer } from './utils.ts';
|
|
2
|
+
import { type Signer, type TArg, type TRet } from './utils.ts';
|
|
3
3
|
/**
|
|
4
4
|
* * N: Security parameter (in bytes). W: Winternitz parameter
|
|
5
5
|
* * H: Hypertree height. D: Hypertree layers
|
|
@@ -40,14 +40,18 @@ export type SphincsHashOpts = {
|
|
|
40
40
|
export declare const PARAMS: Record<string, SphincsOpts>;
|
|
41
41
|
/** Address byte array of size `ADDR_BYTES`. */
|
|
42
42
|
export type ADRS = Uint8Array;
|
|
43
|
-
/** Hash and tweakable-hash callbacks bound to one SLH-DSA keypair context.
|
|
43
|
+
/** Hash and tweakable-hash callbacks bound to one SLH-DSA keypair context.
|
|
44
|
+
* Buffer-aliasing contract: `PRFaddr`, `thash1` and `thashN` return views into per-context
|
|
45
|
+
* scratch buffers (one per lane), so callers must consume or copy a result before the next
|
|
46
|
+
* call on the same lane. `clean()` wipes the scratch buffers along with the hash states.
|
|
47
|
+
*/
|
|
44
48
|
export type Context = {
|
|
45
49
|
/**
|
|
46
50
|
* Derive a PRF output for one address.
|
|
47
51
|
* @param addr - Address bytes.
|
|
48
|
-
* @returns PRF output bytes.
|
|
52
|
+
* @returns PRF output bytes (scratch view; copy to retain).
|
|
49
53
|
*/
|
|
50
|
-
PRFaddr: (addr: ADRS) => Uint8Array
|
|
54
|
+
PRFaddr: (addr: TArg<ADRS>) => TRet<Uint8Array>;
|
|
51
55
|
/**
|
|
52
56
|
* Derive the randomized message hash prefix.
|
|
53
57
|
* @param skPRF - Secret PRF seed.
|
|
@@ -55,7 +59,7 @@ export type Context = {
|
|
|
55
59
|
* @param msg - Message bytes.
|
|
56
60
|
* @returns PRF output bytes.
|
|
57
61
|
*/
|
|
58
|
-
PRFmsg: (skPRF: Uint8Array
|
|
62
|
+
PRFmsg: (skPRF: TArg<Uint8Array>, random: TArg<Uint8Array>, msg: TArg<Uint8Array>) => TRet<Uint8Array>;
|
|
59
63
|
/**
|
|
60
64
|
* Hash one randomized message transcript.
|
|
61
65
|
* @param R - Randomized message prefix.
|
|
@@ -64,14 +68,14 @@ export type Context = {
|
|
|
64
68
|
* @param outLen - Output length in bytes.
|
|
65
69
|
* @returns Transcript hash bytes.
|
|
66
70
|
*/
|
|
67
|
-
Hmsg: (R: Uint8Array
|
|
71
|
+
Hmsg: (R: TArg<Uint8Array>, pk: TArg<Uint8Array>, m: TArg<Uint8Array>, outLen: number) => TRet<Uint8Array>;
|
|
68
72
|
/**
|
|
69
73
|
* Tweakable hash over one input block.
|
|
70
74
|
* @param input - Input block.
|
|
71
75
|
* @param addr - Address bytes.
|
|
72
76
|
* @returns Hash output bytes.
|
|
73
77
|
*/
|
|
74
|
-
thash1: (input: Uint8Array
|
|
78
|
+
thash1: (input: TArg<Uint8Array>, addr: TArg<ADRS>) => TRet<Uint8Array>;
|
|
75
79
|
/**
|
|
76
80
|
* Tweakable hash over multiple input blocks.
|
|
77
81
|
* @param blocks - Number of input blocks.
|
|
@@ -79,88 +83,104 @@ export type Context = {
|
|
|
79
83
|
* @param addr - Address bytes.
|
|
80
84
|
* @returns Hash output bytes.
|
|
81
85
|
*/
|
|
82
|
-
thashN: (blocks: number, input: Uint8Array
|
|
86
|
+
thashN: (blocks: number, input: TArg<Uint8Array>, addr: TArg<ADRS>) => TRet<Uint8Array>;
|
|
83
87
|
/** Wipe any buffered hash state for the current context. */
|
|
84
88
|
clean: () => void;
|
|
85
89
|
};
|
|
86
90
|
/** Factory that creates a context generator for one SLH-DSA parameter set. */
|
|
87
|
-
export type GetContext = (opts: SphincsOpts) => (pub_seed: Uint8Array
|
|
91
|
+
export type GetContext = (opts: SphincsOpts) => (pub_seed: TArg<Uint8Array>, sk_seed?: TArg<Uint8Array>) => TRet<Context>;
|
|
88
92
|
/** Public SLH-DSA signer with prehash customization. */
|
|
89
93
|
export type SphincsSigner = Signer & {
|
|
90
|
-
internal: Signer
|
|
94
|
+
internal: TRet<Signer>;
|
|
91
95
|
securityLevel: number;
|
|
92
|
-
prehash: (hash: CHash) => Signer
|
|
96
|
+
prehash: (hash: TArg<CHash>) => TRet<Signer>;
|
|
93
97
|
};
|
|
94
98
|
/**
|
|
95
99
|
* SLH-DSA-SHAKE-128f: Table 2 row `n=16, h=66, d=22, h'=3, a=6, k=33, lg w=4, m=34`;
|
|
96
100
|
* lengths `publicKey=32`, `secretKey=64`, `signature=17088`, `seed=48`, `signRand=16`.
|
|
97
101
|
* Also exposes `.prehash(...)`.
|
|
98
102
|
*/
|
|
99
|
-
export declare const slh_dsa_shake_128f: SphincsSigner
|
|
103
|
+
export declare const slh_dsa_shake_128f: TRet<SphincsSigner>;
|
|
100
104
|
/**
|
|
101
105
|
* SLH-DSA-SHAKE-128s: Table 2 row `n=16, h=63, d=7, h'=9, a=12, k=14, lg w=4, m=30`;
|
|
102
106
|
* lengths `publicKey=32`, `secretKey=64`, `signature=7856`, `seed=48`, `signRand=16`.
|
|
103
107
|
* Also exposes `.prehash(...)`.
|
|
104
108
|
*/
|
|
105
|
-
export declare const slh_dsa_shake_128s: SphincsSigner
|
|
109
|
+
export declare const slh_dsa_shake_128s: TRet<SphincsSigner>;
|
|
106
110
|
/**
|
|
107
111
|
* SLH-DSA-SHAKE-192f: Table 2 row `n=24, h=66, d=22, h'=3, a=8, k=33, lg w=4, m=42`;
|
|
108
112
|
* lengths `publicKey=48`, `secretKey=96`, `signature=35664`, `seed=72`, `signRand=24`.
|
|
109
113
|
* Also exposes `.prehash(...)`.
|
|
110
114
|
*/
|
|
111
|
-
export declare const slh_dsa_shake_192f: SphincsSigner
|
|
115
|
+
export declare const slh_dsa_shake_192f: TRet<SphincsSigner>;
|
|
112
116
|
/**
|
|
113
117
|
* SLH-DSA-SHAKE-192s: Table 2 row `n=24, h=63, d=7, h'=9, a=14, k=17, lg w=4, m=39`;
|
|
114
118
|
* lengths `publicKey=48`, `secretKey=96`, `signature=16224`, `seed=72`, `signRand=24`.
|
|
115
119
|
* Also exposes `.prehash(...)`.
|
|
116
120
|
*/
|
|
117
|
-
export declare const slh_dsa_shake_192s: SphincsSigner
|
|
121
|
+
export declare const slh_dsa_shake_192s: TRet<SphincsSigner>;
|
|
118
122
|
/**
|
|
119
123
|
* SLH-DSA-SHAKE-256f: Table 2 row `n=32, h=68, d=17, h'=4, a=9, k=35, lg w=4, m=49`;
|
|
120
124
|
* lengths `publicKey=64`, `secretKey=128`, `signature=49856`, `seed=96`, `signRand=32`.
|
|
121
125
|
* Also exposes `.prehash(...)`.
|
|
122
126
|
*/
|
|
123
|
-
export declare const slh_dsa_shake_256f: SphincsSigner
|
|
127
|
+
export declare const slh_dsa_shake_256f: TRet<SphincsSigner>;
|
|
124
128
|
/**
|
|
125
129
|
* SLH-DSA-SHAKE-256s: Table 2 row `n=32, h=64, d=8, h'=8, a=14, k=22, lg w=4, m=47`;
|
|
126
130
|
* lengths `publicKey=64`, `secretKey=128`, `signature=29792`, `seed=96`, `signRand=32`.
|
|
127
131
|
* Also exposes `.prehash(...)`.
|
|
128
132
|
*/
|
|
129
|
-
export declare const slh_dsa_shake_256s: SphincsSigner
|
|
133
|
+
export declare const slh_dsa_shake_256s: TRet<SphincsSigner>;
|
|
130
134
|
/**
|
|
131
135
|
* SLH-DSA-SHA2-128f: Table 2 row `n=16, h=66, d=22, h'=3, a=6, k=33, lg w=4, m=34`;
|
|
132
136
|
* lengths `publicKey=32`, `secretKey=64`, `signature=17088`, `seed=48`, `signRand=16`.
|
|
133
137
|
* Also exposes `.prehash(...)`.
|
|
138
|
+
* @example
|
|
139
|
+
* Generate deterministic SLH-DSA keys, sign one message, and verify the signature.
|
|
140
|
+
* ```ts
|
|
141
|
+
* import { sha256 } from '@noble/hashes/sha2.js';
|
|
142
|
+
* import { slh_dsa_sha2_128f } from '@noble/post-quantum/slh-dsa.js';
|
|
143
|
+
* const seed = new Uint8Array(slh_dsa_sha2_128f.lengths.seed!);
|
|
144
|
+
* const { secretKey, publicKey } = slh_dsa_sha2_128f.keygen(seed);
|
|
145
|
+
* const msg = new TextEncoder().encode('hello noble');
|
|
146
|
+
* const sig = slh_dsa_sha2_128f.sign(msg, secretKey);
|
|
147
|
+
* const isValid = slh_dsa_sha2_128f.verify(sig, msg, publicKey);
|
|
148
|
+
* const recovered = slh_dsa_sha2_128f.getPublicKey(secretKey);
|
|
149
|
+
* const context = new Uint8Array([1, 2, 3]);
|
|
150
|
+
* const prehash = slh_dsa_sha2_128f.prehash(sha256);
|
|
151
|
+
* const preSig = prehash.sign(msg, secretKey, { context });
|
|
152
|
+
* const preValid = prehash.verify(preSig, msg, publicKey, { context });
|
|
153
|
+
* const internalSig = slh_dsa_sha2_128f.internal.sign(msg, secretKey);
|
|
154
|
+
* ```
|
|
134
155
|
*/
|
|
135
|
-
export declare const slh_dsa_sha2_128f: SphincsSigner
|
|
156
|
+
export declare const slh_dsa_sha2_128f: TRet<SphincsSigner>;
|
|
136
157
|
/**
|
|
137
158
|
* SLH-DSA-SHA2-128s: Table 2 row `n=16, h=63, d=7, h'=9, a=12, k=14, lg w=4, m=30`;
|
|
138
159
|
* lengths `publicKey=32`, `secretKey=64`, `signature=7856`, `seed=48`, `signRand=16`.
|
|
139
160
|
* Also exposes `.prehash(...)`.
|
|
140
161
|
*/
|
|
141
|
-
export declare const slh_dsa_sha2_128s: SphincsSigner
|
|
162
|
+
export declare const slh_dsa_sha2_128s: TRet<SphincsSigner>;
|
|
142
163
|
/**
|
|
143
164
|
* SLH-DSA-SHA2-192f: Table 2 row `n=24, h=66, d=22, h'=3, a=8, k=33, lg w=4, m=42`;
|
|
144
165
|
* lengths `publicKey=48`, `secretKey=96`, `signature=35664`, `seed=72`, `signRand=24`.
|
|
145
166
|
* Also exposes `.prehash(...)`.
|
|
146
167
|
*/
|
|
147
|
-
export declare const slh_dsa_sha2_192f: SphincsSigner
|
|
168
|
+
export declare const slh_dsa_sha2_192f: TRet<SphincsSigner>;
|
|
148
169
|
/**
|
|
149
170
|
* SLH-DSA-SHA2-192s: Table 2 row `n=24, h=63, d=7, h'=9, a=14, k=17, lg w=4, m=39`;
|
|
150
171
|
* lengths `publicKey=48`, `secretKey=96`, `signature=16224`, `seed=72`, `signRand=24`.
|
|
151
172
|
* Also exposes `.prehash(...)`.
|
|
152
173
|
*/
|
|
153
|
-
export declare const slh_dsa_sha2_192s: SphincsSigner
|
|
174
|
+
export declare const slh_dsa_sha2_192s: TRet<SphincsSigner>;
|
|
154
175
|
/**
|
|
155
176
|
* SLH-DSA-SHA2-256f: Table 2 row `n=32, h=68, d=17, h'=4, a=9, k=35, lg w=4, m=49`;
|
|
156
177
|
* lengths `publicKey=64`, `secretKey=128`, `signature=49856`, `seed=96`, `signRand=32`.
|
|
157
178
|
* Also exposes `.prehash(...)`.
|
|
158
179
|
*/
|
|
159
|
-
export declare const slh_dsa_sha2_256f: SphincsSigner
|
|
180
|
+
export declare const slh_dsa_sha2_256f: TRet<SphincsSigner>;
|
|
160
181
|
/**
|
|
161
182
|
* SLH-DSA-SHA2-256s: Table 2 row `n=32, h=64, d=8, h'=8, a=14, k=22, lg w=4, m=47`;
|
|
162
183
|
* lengths `publicKey=64`, `secretKey=128`, `signature=29792`, `seed=96`, `signRand=32`.
|
|
163
184
|
* Also exposes `.prehash(...)`.
|
|
164
185
|
*/
|
|
165
|
-
export declare const slh_dsa_sha2_256s: SphincsSigner
|
|
166
|
-
//# sourceMappingURL=slh-dsa.d.ts.map
|
|
186
|
+
export declare const slh_dsa_sha2_256s: TRet<SphincsSigner>;
|