@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/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
- function polyAdd(a, b) {
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
- a[i] = crystals.mod(a[i] + b[i]); // a += b
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(a, b) {
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
- a[i] = crystals.mod(a[i] - b[i]); // a -= b
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
- const c0 = crystals.mod(a1 * b1 * zeta + a0 * b0);
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(f, g) {
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(xof) {
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(PRF, seed, nonce, eta) {
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 = (opts) => {
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 tmp2 = new Uint16Array(N);
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
- const e2 = sampleCBD(PRF, seed, 2 * K, ETA2);
245
- polyAdd(e2, crystals.NTT.decode(tmp2)); // e2 += tmp2
246
- const v = poly1.decode(msg); // encode plaintext m into polynomial v
247
- polyAdd(v, e2); // v += e2
248
- cleanBytes(tHat, rHat, tmp2, e2);
249
- return cipherCoder.encode([u, v]);
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 KPKE = genKPKE(opts);
275
- const { HASH256, HASH512, KDF } = opts;
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
- return {
281
- info: { type: 'ml-kem' },
282
- lengths: {
283
- ...lengths,
284
- seed: 64,
285
- msg: msgLen,
286
- msgRand: msgLen,
287
- secretKey: secretCoder.bytesLen,
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 { publicKey, secretKey };
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
- // FIPS-203 includes additional verification check for modulus
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 { cipherText, sharedSecret: kr.subarray(0, 32) };
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
- cleanBytes(msg, cipherText2, !isValid ? Khat : Kbar);
343
- return isValid ? Khat : Kbar;
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.6.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.0.0",
14
- "@noble/curves": "~2.0.0",
15
- "@noble/hashes": "~2.0.0"
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.0",
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
- "bench": "node test/benchmark.ts",
23
+ "benchmark": "node benchmark/pq.ts",
24
+ "benchmark:size": "npx bismar@0.1 -s",
26
25
  "build": "tsc",
27
- "build:release": "npx --no @paulmillr/jsbt esbuild test/build",
28
- "check": "npm run check:readme && npm run check:treeshake && npm run check:jsdoc",
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 --experimental-strip-types --no-warnings test/index.ts",
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, random: Uint8Array, msg: Uint8Array) => 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, pk: Uint8Array, m: Uint8Array, outLen: number) => 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, addr: ADRS) => 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, addr: ADRS) => 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, sk_seed?: Uint8Array) => Context;
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>;