@noble/post-quantum 0.6.1 → 0.7.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 +191 -104
- package/_crystals.d.ts +8 -3
- package/_crystals.js +38 -10
- package/falcon.d.ts +1 -2
- package/falcon.js +202 -115
- package/hybrid.d.ts +38 -28
- package/hybrid.js +215 -86
- package/index.d.ts +0 -1
- package/index.js +1 -2
- package/ml-dsa.d.ts +31 -5
- package/ml-dsa.js +118 -34
- package/ml-kem.d.ts +45 -4
- package/ml-kem.js +206 -64
- package/package.json +16 -20
- package/slh-dsa.d.ts +23 -3
- package/slh-dsa.js +127 -60
- package/src/_crystals.ts +45 -11
- package/src/falcon.ts +206 -121
- package/src/hybrid.ts +217 -83
- package/src/index.ts +1 -1
- package/src/ml-dsa.ts +140 -43
- package/src/ml-kem.ts +239 -66
- package/src/slh-dsa.ts +149 -69
- package/src/utils.ts +186 -25
- package/src/webcrypto.ts +322 -0
- package/utils.d.ts +54 -4
- package/utils.js +167 -28
- package/webcrypto.d.ts +91 -0
- package/webcrypto.js +213 -0
- 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
|
@@ -84,24 +84,32 @@ 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
|
+
// Coefficients always stay reduced in [0, Q) here (samplers, NTT and coders all reduce),
|
|
88
|
+
// so one conditional correction replaces the generic mod().
|
|
87
89
|
function polyAdd(a_, b_) {
|
|
88
90
|
const a = a_;
|
|
89
91
|
const b = b_;
|
|
90
92
|
// Mutates `a` in place; callers must pass two N=256 polynomials.
|
|
91
|
-
for (let i = 0; i < N; i++)
|
|
92
|
-
|
|
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
|
+
}
|
|
93
97
|
}
|
|
94
98
|
function polySub(a_, b_) {
|
|
95
99
|
const a = a_;
|
|
96
100
|
const b = b_;
|
|
97
101
|
// Mutates `a` in place; callers must pass two N=256 polynomials.
|
|
98
|
-
for (let i = 0; i < N; i++)
|
|
99
|
-
|
|
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
|
+
}
|
|
100
106
|
}
|
|
101
107
|
// FIPS-203: Computes the product of two degree-one polynomials with respect to a quadratic modulus
|
|
102
108
|
function BaseCaseMultiply(a0, a1, b0, b1, zeta) {
|
|
103
109
|
// `zeta` here is Algorithm 11's γ = ζ^(2BitRev_7(i)+1).
|
|
104
|
-
|
|
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);
|
|
105
113
|
const c1 = crystals.mod(a0 * b1 + a1 * b0);
|
|
106
114
|
return { c0, c1 };
|
|
107
115
|
}
|
|
@@ -191,6 +199,34 @@ const genKPKE = (opts_) => {
|
|
|
191
199
|
const secretCoder = vecCoder(polyCoder(12), K);
|
|
192
200
|
const cipherCoder = splitCoder('ciphertext', vecCoder(polyU, K), polyV);
|
|
193
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
|
+
};
|
|
194
230
|
return {
|
|
195
231
|
secretCoder,
|
|
196
232
|
lengths: {
|
|
@@ -231,31 +267,26 @@ const genKPKE = (opts_) => {
|
|
|
231
267
|
},
|
|
232
268
|
encrypt: (publicKey, msg, seed) => {
|
|
233
269
|
const [tHat, rho] = publicCoder.decode(publicKey);
|
|
234
|
-
const rHat = [];
|
|
235
|
-
for (let i = 0; i < K; i++)
|
|
236
|
-
rHat.push(crystals.NTT.encode(sampleCBD(PRF, seed, i, ETA1)));
|
|
237
270
|
const x = XOF(rho);
|
|
238
|
-
const
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
271
|
+
const res = encryptCore(tHat, (i, j) => SampleNTT(x.get(i, j)), msg, seed);
|
|
272
|
+
x.clean();
|
|
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)));
|
|
252
285
|
x.clean();
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
cleanBytes(tHat, rHat, tmp2, e2);
|
|
258
|
-
return cipherCoder.encode([u, v]);
|
|
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
|
+
};
|
|
259
290
|
},
|
|
260
291
|
decrypt: (cipherText, privateKey) => {
|
|
261
292
|
const [u, v] = cipherCoder.decode(cipherText);
|
|
@@ -265,8 +296,11 @@ const genKPKE = (opts_) => {
|
|
|
265
296
|
for (let i = 0; i < K; i++)
|
|
266
297
|
polyAdd(tmp, MultiplyNTTs(sk[i], crystals.NTT.encode(u[i])));
|
|
267
298
|
polySub(v, crystals.NTT.decode(tmp)); // w = v' - tmp
|
|
268
|
-
|
|
269
|
-
|
|
299
|
+
// `v` now holds w, from which the plaintext is just a 1-bit threshold away, so wipe it too.
|
|
300
|
+
// encode() allocates its own buffer, so the returned bytes do not alias `v`.
|
|
301
|
+
const res = poly1.encode(v);
|
|
302
|
+
cleanBytes(tmp, sk, u, v);
|
|
303
|
+
return res;
|
|
270
304
|
},
|
|
271
305
|
};
|
|
272
306
|
};
|
|
@@ -274,6 +308,11 @@ const genKPKE = (opts_) => {
|
|
|
274
308
|
* Public ML-KEM wrapper over the internal K-PKE subroutine.
|
|
275
309
|
* `keygen(seed)` and `encapsulate(publicKey, msg)` are deterministic/test-oriented hooks that map
|
|
276
310
|
* more directly to Algorithms 16-17 than to the pure no-input / random-internal Algorithms 19-20.
|
|
311
|
+
* `encapsulate`'s optional `msg` is the 32-byte message randomness `m` of Algorithm 17, the
|
|
312
|
+
* pre-image the shared secret is derived from, NOT a plaintext to encrypt: ML-KEM is a key
|
|
313
|
+
* encapsulation mechanism, not a cipher. Omit it to draw fresh randomness; pass it only to
|
|
314
|
+
* reproduce a known-answer vector, and only as 32 uniformly random bytes, since a low-entropy or
|
|
315
|
+
* reused value makes the shared secret predictable. The same holds for `keygen`'s optional `seed`.
|
|
277
316
|
* decapsulate() tries to follow the Algorithms 18/21 implicit-reject structure as closely as
|
|
278
317
|
* practical here by re-encrypting, comparing ciphertexts, returning `Khat` on match or `Kbar` on
|
|
279
318
|
* mismatch, and zeroizing the non-returned shared-secret candidate; JS/JIT still provides no
|
|
@@ -287,6 +326,18 @@ function createKyber(opts) {
|
|
|
287
326
|
const secretCoder = splitCoder('secretKey', lengths.secretKey, lengths.publicKey, 32, 32);
|
|
288
327
|
const msgLen = 32;
|
|
289
328
|
const seedLen = 64;
|
|
329
|
+
// FIPS-203 includes additional verification check for modulus
|
|
330
|
+
const validateModulus = (publicKey, fn) => {
|
|
331
|
+
const eke = publicKey.subarray(0, 384 * rawOpts.K);
|
|
332
|
+
// Copy because of inplace encoding
|
|
333
|
+
const ek = KPKESecretCoder.encode(KPKESecretCoder.decode(copyBytes(eke)));
|
|
334
|
+
// (Modulus check.) Perform the computation ek ← ByteEncode12(ByteDecode12(eke)).
|
|
335
|
+
// If ek = ̸ eke, the input is invalid. (See Section 4.2.1.)
|
|
336
|
+
const ok = equalBytes(ek, eke);
|
|
337
|
+
cleanBytes(ek);
|
|
338
|
+
if (!ok)
|
|
339
|
+
throw new Error(`ML-KEM.${fn}: wrong publicKey modulus`);
|
|
340
|
+
};
|
|
290
341
|
const kemLengths = Object.freeze({
|
|
291
342
|
...lengths,
|
|
292
343
|
seed: 64,
|
|
@@ -297,44 +348,65 @@ function createKyber(opts) {
|
|
|
297
348
|
return Object.freeze({
|
|
298
349
|
info: Object.freeze({ type: 'ml-kem' }),
|
|
299
350
|
lengths: kemLengths,
|
|
300
|
-
keygen: (seed
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
const
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
351
|
+
keygen: (seed) => {
|
|
352
|
+
// A generated seed carries z (the implicit-rejection secret) and must be wiped once the
|
|
353
|
+
// secret key holds a copy, matching ml-dsa / slh-dsa / falcon keygen. A caller-supplied
|
|
354
|
+
// seed is the caller's to manage (and the immutability test requires it stay untouched).
|
|
355
|
+
const ownSeed = seed === undefined;
|
|
356
|
+
const s = ownSeed ? randomBytes(seedLen) : seed;
|
|
357
|
+
let sk;
|
|
358
|
+
let publicKeyHash;
|
|
359
|
+
try {
|
|
360
|
+
abytes(s, seedLen, 'seed');
|
|
361
|
+
const keys = KPKE.keygen(s.subarray(0, 32));
|
|
362
|
+
const publicKey = keys.publicKey;
|
|
363
|
+
sk = keys.secretKey;
|
|
364
|
+
publicKeyHash = HASH256(publicKey);
|
|
365
|
+
// (dkPKE||ek||H(ek)||z)
|
|
366
|
+
const secretKey = secretCoder.encode([sk, publicKey, publicKeyHash, s.subarray(32)]);
|
|
367
|
+
return {
|
|
368
|
+
publicKey: publicKey,
|
|
369
|
+
secretKey: secretKey,
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
finally {
|
|
373
|
+
if (sk !== undefined)
|
|
374
|
+
cleanBytes(sk);
|
|
375
|
+
if (publicKeyHash !== undefined)
|
|
376
|
+
cleanBytes(publicKeyHash);
|
|
377
|
+
if (ownSeed)
|
|
378
|
+
cleanBytes(s);
|
|
379
|
+
}
|
|
311
380
|
},
|
|
312
381
|
getPublicKey: (secretKey) => {
|
|
313
382
|
const [_sk, publicKey, _publicKeyHash, _z] = secretCoder.decode(secretKey);
|
|
314
383
|
return Uint8Array.from(publicKey);
|
|
315
384
|
},
|
|
316
|
-
encapsulate: (publicKey, msg
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
//
|
|
320
|
-
const
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
385
|
+
encapsulate: (publicKey, msg) => {
|
|
386
|
+
// A generated message is the preimage of the shared secret (K = G(m || H(ek))[0:32]) and
|
|
387
|
+
// must be wiped. A caller-supplied message is the deterministic-randomness hook and the
|
|
388
|
+
// caller's to manage (the immutability test requires it stay untouched).
|
|
389
|
+
const ownMsg = msg === undefined;
|
|
390
|
+
const m = ownMsg ? randomBytes(msgLen) : msg;
|
|
391
|
+
let kr;
|
|
392
|
+
try {
|
|
393
|
+
abytes(publicKey, lengths.publicKey, 'publicKey');
|
|
394
|
+
abytes(m, msgLen, 'message');
|
|
395
|
+
validateModulus(publicKey, 'encapsulate');
|
|
396
|
+
// derive randomness
|
|
397
|
+
kr = HASH512.create().update(m).update(HASH256(publicKey)).digest();
|
|
398
|
+
const cipherText = KPKE.encrypt(publicKey, m, kr.subarray(32, 64));
|
|
399
|
+
return {
|
|
400
|
+
cipherText: cipherText,
|
|
401
|
+
sharedSecret: kr.subarray(0, 32),
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
finally {
|
|
405
|
+
if (kr !== undefined)
|
|
406
|
+
cleanBytes(kr.subarray(32));
|
|
407
|
+
if (ownMsg)
|
|
408
|
+
cleanBytes(m);
|
|
328
409
|
}
|
|
329
|
-
cleanBytes(ek);
|
|
330
|
-
// derive randomness
|
|
331
|
-
const kr = HASH512.create().update(msg).update(HASH256(publicKey)).digest();
|
|
332
|
-
const cipherText = KPKE.encrypt(publicKey, msg, kr.subarray(32, 64));
|
|
333
|
-
cleanBytes(kr.subarray(32));
|
|
334
|
-
return {
|
|
335
|
-
cipherText: cipherText,
|
|
336
|
-
sharedSecret: kr.subarray(0, 32),
|
|
337
|
-
};
|
|
338
410
|
},
|
|
339
411
|
decapsulate: (cipherText, secretKey) => {
|
|
340
412
|
abytes(secretKey, secretCoder.bytesLen, 'secretKey'); // 768*k + 96
|
|
@@ -356,9 +428,69 @@ function createKyber(opts) {
|
|
|
356
428
|
// if ciphertexts do not match, “implicitly reject”
|
|
357
429
|
const isValid = equalBytes(cipherText, cipherText2);
|
|
358
430
|
const Kbar = KDF.create({ dkLen: 32 }).update(z).update(cipherText).digest();
|
|
359
|
-
|
|
431
|
+
// kr[32:64] is the derived K-PKE encryption randomness: wipe it like encapsulate() does.
|
|
432
|
+
cleanBytes(msg, cipherText2, kr.subarray(32), !isValid ? Khat : Kbar);
|
|
360
433
|
return (isValid ? Khat : Kbar);
|
|
361
434
|
},
|
|
435
|
+
/**
|
|
436
|
+
* Experimental prototype: pre-expand a public key so repeated encapsulate/decapsulate
|
|
437
|
+
* against the same key skip re-validation, H(ek), t̂ decoding and the K² SampleNTT
|
|
438
|
+
* XOF expansions of Â. Only public data is cached; see {@link KEMPrepared}.
|
|
439
|
+
*/
|
|
440
|
+
prepare: (publicKey) => {
|
|
441
|
+
abytes(publicKey, lengths.publicKey, 'publicKey');
|
|
442
|
+
validateModulus(publicKey, 'prepare');
|
|
443
|
+
const ek = copyBytes(publicKey); // detach from the caller before caching
|
|
444
|
+
const publicKeyHash = HASH256(ek);
|
|
445
|
+
const cached = KPKE.prepare(ek);
|
|
446
|
+
return Object.freeze({
|
|
447
|
+
publicKey: ek,
|
|
448
|
+
encapsulate: (msg) => {
|
|
449
|
+
// As in the non-prepared encapsulate: a generated message is the shared-secret
|
|
450
|
+
// preimage and is wiped; a caller-supplied one is left untouched.
|
|
451
|
+
const ownMsg = msg === undefined;
|
|
452
|
+
const m = ownMsg ? randomBytes(msgLen) : msg;
|
|
453
|
+
let kr;
|
|
454
|
+
try {
|
|
455
|
+
abytes(m, msgLen, 'message');
|
|
456
|
+
kr = HASH512.create().update(m).update(publicKeyHash).digest();
|
|
457
|
+
const cipherText = cached.encrypt(m, kr.subarray(32, 64));
|
|
458
|
+
return {
|
|
459
|
+
cipherText: cipherText,
|
|
460
|
+
sharedSecret: kr.subarray(0, 32),
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
finally {
|
|
464
|
+
if (kr !== undefined)
|
|
465
|
+
cleanBytes(kr.subarray(32));
|
|
466
|
+
if (ownMsg)
|
|
467
|
+
cleanBytes(m);
|
|
468
|
+
}
|
|
469
|
+
},
|
|
470
|
+
decapsulate: (cipherText, secretKey) => {
|
|
471
|
+
abytes(secretKey, secretCoder.bytesLen, 'secretKey');
|
|
472
|
+
abytes(cipherText, lengths.cipherText, 'cipherText');
|
|
473
|
+
const [sk, ekEmbedded, storedHash, z] = secretCoder.decode(secretKey);
|
|
474
|
+
// Under KEMPrepared's read-only publicKey contract, bind dk to the prepared key.
|
|
475
|
+
// Together with publicKeyHash = H(ek) computed in prepare(), this is equivalent to (and
|
|
476
|
+
// stronger than) FIPS 203 §7.3's `H(dk[384k : 768k+32]) == dk[768k+32 : 768k+64]`.
|
|
477
|
+
if (!equalBytes(ekEmbedded, ek) || !equalBytes(storedHash, publicKeyHash))
|
|
478
|
+
throw new Error('ML-KEM.decapsulate: secretKey does not match prepared publicKey');
|
|
479
|
+
const msg = KPKE.decrypt(cipherText, sk);
|
|
480
|
+
// derive randomness, Khat, rHat = G(mHat || h)
|
|
481
|
+
const kr = HASH512.create().update(msg).update(publicKeyHash).digest();
|
|
482
|
+
const Khat = kr.subarray(0, 32);
|
|
483
|
+
// re-encrypt using the derived randomness and cached Â/t̂
|
|
484
|
+
const cipherText2 = cached.encrypt(msg, kr.subarray(32, 64));
|
|
485
|
+
// if ciphertexts do not match, “implicitly reject”
|
|
486
|
+
const isValid = equalBytes(cipherText, cipherText2);
|
|
487
|
+
const Kbar = KDF.create({ dkLen: 32 }).update(z).update(cipherText).digest();
|
|
488
|
+
cleanBytes(msg, cipherText2, kr.subarray(32), !isValid ? Khat : Kbar);
|
|
489
|
+
return (isValid ? Khat : Kbar);
|
|
490
|
+
},
|
|
491
|
+
clean: cached.clean,
|
|
492
|
+
});
|
|
493
|
+
},
|
|
362
494
|
});
|
|
363
495
|
}
|
|
364
496
|
// FIPS 203's PRF_eta binding: current callers use only 32-byte keys, one-byte nonces,
|
|
@@ -388,6 +520,17 @@ const mk = (params) => createKyber({
|
|
|
388
520
|
/**
|
|
389
521
|
* ML-KEM-512: Table 2 row `k=2, η1=3, η2=2, du=10, dv=4`; Table 3 sizes `800/1632/768/32`.
|
|
390
522
|
* The ASD lifecycle note here is external policy guidance, not a FIPS 203 requirement.
|
|
523
|
+
* @example
|
|
524
|
+
* Generate deterministic ML-KEM-512 keys, encapsulate a shared secret, and decapsulate it.
|
|
525
|
+
* ```ts
|
|
526
|
+
* import { ml_kem512 } from '@noble/post-quantum/ml-kem.js';
|
|
527
|
+
* const seed = new Uint8Array(ml_kem512.lengths.seed!);
|
|
528
|
+
* const { secretKey, publicKey } = ml_kem512.keygen(seed);
|
|
529
|
+
* const msg = new Uint8Array(ml_kem512.lengths.msgRand!);
|
|
530
|
+
* const { cipherText, sharedSecret } = ml_kem512.encapsulate(publicKey, msg);
|
|
531
|
+
* const recovered = ml_kem512.decapsulate(cipherText, secretKey);
|
|
532
|
+
* const publicKey2 = ml_kem512.getPublicKey(secretKey);
|
|
533
|
+
* ```
|
|
391
534
|
*/
|
|
392
535
|
export const ml_kem512 = /* @__PURE__ */ (() => mk(PARAMS[512]))();
|
|
393
536
|
/**
|
|
@@ -441,4 +584,3 @@ export const __tests = /* @__PURE__ */ (() => Object.freeze({
|
|
|
441
584
|
}
|
|
442
585
|
},
|
|
443
586
|
}))();
|
|
444
|
-
//# sourceMappingURL=ml-kem.js.map
|
package/package.json
CHANGED
|
@@ -1,40 +1,35 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@noble/post-quantum",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.1",
|
|
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": "
|
|
14
|
-
"@noble/curves": "
|
|
15
|
-
"@noble/hashes": "
|
|
11
|
+
"@noble/ciphers": "2.4.0",
|
|
12
|
+
"@noble/curves": "2.4.0",
|
|
13
|
+
"@noble/hashes": "2.4.0"
|
|
16
14
|
},
|
|
17
15
|
"devDependencies": {
|
|
18
|
-
"@paulmillr/jsbt": "0.
|
|
19
|
-
"
|
|
16
|
+
"@paulmillr/jsbt": "0.7.1",
|
|
17
|
+
"bismar": "0.1.8",
|
|
18
|
+
"@types/node": "26.2.0",
|
|
20
19
|
"fast-check": "4.2.0",
|
|
21
|
-
"prettier": "3.6
|
|
22
|
-
"typescript": "6.0.
|
|
20
|
+
"prettier": "3.9.6",
|
|
21
|
+
"typescript": "6.0.3"
|
|
23
22
|
},
|
|
24
23
|
"scripts": {
|
|
25
|
-
"
|
|
24
|
+
"benchmark": "node benchmark/pq.ts",
|
|
25
|
+
"benchmark:size": "bismar -bsm",
|
|
26
26
|
"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",
|
|
27
|
+
"check": "jsbt-check",
|
|
28
|
+
"build:clean": "rm *.{js,d.ts} 2> /dev/null",
|
|
33
29
|
"format": "prettier --write 'src/**/*.{js,ts}' 'test/**/*.{js,ts,mjs}'",
|
|
34
|
-
"test": "node test/index.ts",
|
|
30
|
+
"test": "node --no-warnings test/index.ts",
|
|
35
31
|
"test:bun": "bun test/index.ts",
|
|
36
32
|
"test:deno": "deno --allow-env --allow-read test/index.ts",
|
|
37
|
-
"test:node20": "cd test; npx tsc; node compiled/test/index.js",
|
|
38
33
|
"test:slow": "SLOW_TESTS=1 node test/index.ts"
|
|
39
34
|
},
|
|
40
35
|
"exports": {
|
|
@@ -45,7 +40,8 @@
|
|
|
45
40
|
"./ml-dsa.js": "./ml-dsa.js",
|
|
46
41
|
"./ml-kem.js": "./ml-kem.js",
|
|
47
42
|
"./slh-dsa.js": "./slh-dsa.js",
|
|
48
|
-
"./utils.js": "./utils.js"
|
|
43
|
+
"./utils.js": "./utils.js",
|
|
44
|
+
"./webcrypto.js": "./webcrypto.js"
|
|
49
45
|
},
|
|
50
46
|
"engines": {
|
|
51
47
|
"node": ">= 20.19.0"
|
package/slh-dsa.d.ts
CHANGED
|
@@ -40,12 +40,16 @@ 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
54
|
PRFaddr: (addr: TArg<ADRS>) => TRet<Uint8Array>;
|
|
51
55
|
/**
|
|
@@ -131,6 +135,23 @@ export declare const slh_dsa_shake_256s: TRet<SphincsSigner>;
|
|
|
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
156
|
export declare const slh_dsa_sha2_128f: TRet<SphincsSigner>;
|
|
136
157
|
/**
|
|
@@ -163,4 +184,3 @@ export declare const slh_dsa_sha2_256f: TRet<SphincsSigner>;
|
|
|
163
184
|
* Also exposes `.prehash(...)`.
|
|
164
185
|
*/
|
|
165
186
|
export declare const slh_dsa_sha2_256s: TRet<SphincsSigner>;
|
|
166
|
-
//# sourceMappingURL=slh-dsa.d.ts.map
|