@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/src/slh-dsa.ts CHANGED
@@ -28,15 +28,10 @@
28
28
  */
29
29
  /*! noble-post-quantum - MIT License (c) 2024 Paul Miller (paulmillr.com) */
30
30
  import { hmac } from '@noble/hashes/hmac.js';
31
+ import { bytesToNumberBE, numberToBytesBE } from '@noble/curves/utils.js';
31
32
  import { sha256, sha512 } from '@noble/hashes/sha2.js';
32
33
  import { shake256 } from '@noble/hashes/sha3.js';
33
- import {
34
- bytesToHex,
35
- concatBytes,
36
- createView,
37
- hexToBytes,
38
- type CHash,
39
- } from '@noble/hashes/utils.js';
34
+ import { concatBytes, createView, type CHash } from '@noble/hashes/utils.js';
40
35
  import {
41
36
  abytes,
42
37
  checkHash,
@@ -53,6 +48,8 @@ import {
53
48
  vecCoder,
54
49
  type Signer,
55
50
  type SigOpts,
51
+ type TArg,
52
+ type TRet,
56
53
  type VerOpts,
57
54
  } from './utils.ts';
58
55
 
@@ -96,14 +93,14 @@ export type SphincsHashOpts = {
96
93
  * stay derived at the export layer.
97
94
  */
98
95
  export const PARAMS: Record<string, SphincsOpts> = /* @__PURE__ */ (() =>
99
- ({
100
- '128f': { W: 16, N: 16, H: 66, D: 22, K: 33, A: 6, securityLevel: 128 },
101
- '128s': { W: 16, N: 16, H: 63, D: 7, K: 14, A: 12, securityLevel: 128 },
102
- '192f': { W: 16, N: 24, H: 66, D: 22, K: 33, A: 8, securityLevel: 192 },
103
- '192s': { W: 16, N: 24, H: 63, D: 7, K: 17, A: 14, securityLevel: 192 },
104
- '256f': { W: 16, N: 32, H: 68, D: 17, K: 35, A: 9, securityLevel: 256 },
105
- '256s': { W: 16, N: 32, H: 64, D: 8, K: 22, A: 14, securityLevel: 256 },
106
- }) as const)();
96
+ Object.freeze({
97
+ '128f': Object.freeze({ W: 16, N: 16, H: 66, D: 22, K: 33, A: 6, securityLevel: 128 }),
98
+ '128s': Object.freeze({ W: 16, N: 16, H: 63, D: 7, K: 14, A: 12, securityLevel: 128 }),
99
+ '192f': Object.freeze({ W: 16, N: 24, H: 66, D: 22, K: 33, A: 8, securityLevel: 192 }),
100
+ '192s': Object.freeze({ W: 16, N: 24, H: 63, D: 7, K: 17, A: 14, securityLevel: 192 }),
101
+ '256f': Object.freeze({ W: 16, N: 32, H: 68, D: 17, K: 35, A: 9, securityLevel: 256 }),
102
+ '256s': Object.freeze({ W: 16, N: 32, H: 64, D: 8, K: 22, A: 14, securityLevel: 256 }),
103
+ } as const))();
107
104
 
108
105
  // FIPS 205 `ADRS.setTypeAndClear(...)` selectors. Local names shorten the spec labels
109
106
  // (`WOTS_HASH` -> `WOTS`, `TREE` -> `HASHTREE`, `FORS_ROOTS` -> `FORSPK`), and `setAddr({ type })`
@@ -121,14 +118,18 @@ const AddressType = {
121
118
  /** Address byte array of size `ADDR_BYTES`. */
122
119
  export type ADRS = Uint8Array;
123
120
 
124
- /** Hash and tweakable-hash callbacks bound to one SLH-DSA keypair context. */
121
+ /** Hash and tweakable-hash callbacks bound to one SLH-DSA keypair context.
122
+ * Buffer-aliasing contract: `PRFaddr`, `thash1` and `thashN` return views into per-context
123
+ * scratch buffers (one per lane), so callers must consume or copy a result before the next
124
+ * call on the same lane. `clean()` wipes the scratch buffers along with the hash states.
125
+ */
125
126
  export type Context = {
126
127
  /**
127
128
  * Derive a PRF output for one address.
128
129
  * @param addr - Address bytes.
129
- * @returns PRF output bytes.
130
+ * @returns PRF output bytes (scratch view; copy to retain).
130
131
  */
131
- PRFaddr: (addr: ADRS) => Uint8Array;
132
+ PRFaddr: (addr: TArg<ADRS>) => TRet<Uint8Array>;
132
133
  /**
133
134
  * Derive the randomized message hash prefix.
134
135
  * @param skPRF - Secret PRF seed.
@@ -136,7 +137,11 @@ export type Context = {
136
137
  * @param msg - Message bytes.
137
138
  * @returns PRF output bytes.
138
139
  */
139
- PRFmsg: (skPRF: Uint8Array, random: Uint8Array, msg: Uint8Array) => Uint8Array;
140
+ PRFmsg: (
141
+ skPRF: TArg<Uint8Array>,
142
+ random: TArg<Uint8Array>,
143
+ msg: TArg<Uint8Array>
144
+ ) => TRet<Uint8Array>;
140
145
  /**
141
146
  * Hash one randomized message transcript.
142
147
  * @param R - Randomized message prefix.
@@ -145,14 +150,19 @@ export type Context = {
145
150
  * @param outLen - Output length in bytes.
146
151
  * @returns Transcript hash bytes.
147
152
  */
148
- Hmsg: (R: Uint8Array, pk: Uint8Array, m: Uint8Array, outLen: number) => Uint8Array;
153
+ Hmsg: (
154
+ R: TArg<Uint8Array>,
155
+ pk: TArg<Uint8Array>,
156
+ m: TArg<Uint8Array>,
157
+ outLen: number
158
+ ) => TRet<Uint8Array>;
149
159
  /**
150
160
  * Tweakable hash over one input block.
151
161
  * @param input - Input block.
152
162
  * @param addr - Address bytes.
153
163
  * @returns Hash output bytes.
154
164
  */
155
- thash1: (input: Uint8Array, addr: ADRS) => Uint8Array;
165
+ thash1: (input: TArg<Uint8Array>, addr: TArg<ADRS>) => TRet<Uint8Array>;
156
166
  /**
157
167
  * Tweakable hash over multiple input blocks.
158
168
  * @param blocks - Number of input blocks.
@@ -160,36 +170,21 @@ export type Context = {
160
170
  * @param addr - Address bytes.
161
171
  * @returns Hash output bytes.
162
172
  */
163
- thashN: (blocks: number, input: Uint8Array, addr: ADRS) => Uint8Array;
173
+ thashN: (blocks: number, input: TArg<Uint8Array>, addr: TArg<ADRS>) => TRet<Uint8Array>;
164
174
  /** Wipe any buffered hash state for the current context. */
165
175
  clean: () => void;
166
176
  };
167
177
  /** Factory that creates a context generator for one SLH-DSA parameter set. */
168
178
  export type GetContext = (
169
179
  opts: SphincsOpts
170
- ) => (pub_seed: Uint8Array, sk_seed?: Uint8Array) => Context;
171
-
172
- function hexToNumber(hex: string): bigint {
173
- if (typeof hex !== 'string') throw new Error('hex string expected, got ' + typeof hex);
174
- return BigInt(hex === '' ? '0' : '0x' + hex); // Big Endian
175
- }
176
-
177
- // BE: Big Endian, LE: Little Endian. This is the local FIPS 205 `toInt(...)` equivalent.
178
- function bytesToNumberBE(bytes: Uint8Array): bigint {
179
- return hexToNumber(bytesToHex(bytes));
180
- }
181
-
182
- // Local in-range FIPS 205 `toByte(x, n)` equivalent; callers must keep `n < 256^len`.
183
- function numberToBytesBE(n: number | bigint, len: number): Uint8Array {
184
- return hexToBytes(n.toString(16).padStart(len * 2, '0'));
185
- }
180
+ ) => (pub_seed: TArg<Uint8Array>, sk_seed?: TArg<Uint8Array>) => TRet<Context>;
186
181
 
187
182
  // Local FIPS 205 Algorithm 4 `base_2^b(...)` implementation. Bits are consumed in big-endian
188
183
  // order within each input byte, and callers must provide at least `ceil(outLen * b / 8)` bytes;
189
184
  // short inputs are not rejected and would zero-extend implicitly.
190
185
  const base2b = (outLen: number, b: number) => {
191
186
  const mask = getMask(b);
192
- return (bytes: Uint8Array) => {
187
+ return (bytes: TArg<Uint8Array>): TRet<Uint32Array> => {
193
188
  const baseB = new Uint32Array(outLen);
194
189
  for (let out = 0, pos = 0, bits = 0, total = 0; out < outLen; out++) {
195
190
  while (bits < b) {
@@ -199,19 +194,23 @@ const base2b = (outLen: number, b: number) => {
199
194
  bits -= b;
200
195
  baseB[out] = (total >>> bits) & mask;
201
196
  }
202
- return baseB;
197
+ return baseB as TRet<Uint32Array>;
203
198
  };
204
199
  };
205
200
 
201
+ const _1n = /* @__PURE__ */ BigInt(1);
202
+ const _8n = /* @__PURE__ */ BigInt(8);
203
+ const _0xffn = /* @__PURE__ */ BigInt(0xff);
204
+
206
205
  function getMaskBig(bits: number) {
207
- return (1n << BigInt(bits)) - 1n; // 4 -> 0b1111
206
+ return (_1n << BigInt(bits)) - _1n; // 4 -> 0b1111
208
207
  }
209
208
 
210
209
  /** Public SLH-DSA signer with prehash customization. */
211
210
  export type SphincsSigner = Signer & {
212
- internal: Signer;
211
+ internal: TRet<Signer>;
213
212
  securityLevel: number;
214
- prehash: (hash: CHash) => Signer;
213
+ prehash: (hash: TArg<CHash>) => TRet<Signer>;
215
214
  };
216
215
 
217
216
  /** One parameter/hash instantiation of the public SLH-DSA API.
@@ -219,7 +218,8 @@ export type SphincsSigner = Signer & {
219
218
  * and `getPublicKey(secretKey)` only extracts the embedded public key
220
219
  * instead of recomputing `PK.root`.
221
220
  */
222
- function gen(opts: SphincsOpts, hashOpts: SphincsHashOpts): SphincsSigner {
221
+ function gen(opts: SphincsOpts, hashOpts_: TArg<SphincsHashOpts>): TRet<SphincsSigner> {
222
+ const hashOpts = hashOpts_ as SphincsHashOpts;
223
223
  const { N, W, H, D, K, A, securityLevel: securityLevel } = opts;
224
224
  const getContext = hashOpts.getContext(opts);
225
225
  if (W !== 16) throw new Error('Unsupported Winternitz parameter');
@@ -256,7 +256,7 @@ function gen(opts: SphincsOpts, hashOpts: SphincsHashOpts): SphincsSigner {
256
256
  // `height` / `chain` and `index` / `hash` share the same spec words, so callers must use the
257
257
  // address-type-specific combinations instead of mixing both meanings in one call.
258
258
  const setAddr = (
259
- opts: {
259
+ opts: TArg<{
260
260
  type?: (typeof AddressType)[keyof typeof AddressType];
261
261
  height?: number;
262
262
  tree?: bigint;
@@ -267,21 +267,30 @@ function gen(opts: SphincsOpts, hashOpts: SphincsHashOpts): SphincsSigner {
267
267
  keypair?: number;
268
268
  subtreeAddr?: ADRS;
269
269
  keypairAddr?: ADRS;
270
- },
271
- addr: ADRS = new Uint8Array(ADDR_BYTES)
270
+ }>,
271
+ addr: TArg<ADRS> = new Uint8Array(ADDR_BYTES)
272
272
  ) => {
273
273
  const { type, height, tree, layer, index, chain, hash, keypair } = opts;
274
274
  const { subtreeAddr, keypairAddr } = opts;
275
- const v = createView(addr);
276
275
 
277
276
  if (height !== undefined) addr[OFFSET_CHAIN_ADDR] = height;
278
277
  if (layer !== undefined) addr[OFFSET_LAYER] = layer;
279
278
  if (type !== undefined) addr[OFFSET_TYPE] = type;
280
279
  if (chain !== undefined) addr[OFFSET_CHAIN_ADDR] = chain;
281
280
  if (hash !== undefined) addr[OFFSET_HASH_ADDR] = hash;
282
- if (index !== undefined) v.setUint32(OFFSET_TREE_INDEX, index, false);
281
+ // Manual big-endian writes: setAddr runs in the innermost WOTS/tree loops, and creating a
282
+ // DataView per call was a measurable share of sign() time.
283
+ if (index !== undefined) {
284
+ addr[OFFSET_TREE_INDEX + 0] = index >>> 24;
285
+ addr[OFFSET_TREE_INDEX + 1] = index >>> 16;
286
+ addr[OFFSET_TREE_INDEX + 2] = index >>> 8;
287
+ addr[OFFSET_TREE_INDEX + 3] = index;
288
+ }
283
289
  if (subtreeAddr) addr.set(subtreeAddr.subarray(0, OFFSET_TREE + 8));
284
- if (tree !== undefined) v.setBigUint64(OFFSET_TREE, tree, false);
290
+ if (tree !== undefined) {
291
+ let t = tree;
292
+ for (let i = 7; i >= 0; i--, t >>= _8n) addr[OFFSET_TREE + i] = Number(t & _0xffn);
293
+ }
285
294
  if (keypair !== undefined) {
286
295
  addr[OFFSET_KP_ADDR1] = keypair;
287
296
  if (TREE_HEIGHT > 8) addr[OFFSET_KP_ADDR2] = keypair >>> 8;
@@ -295,7 +304,7 @@ function gen(opts: SphincsOpts, hashOpts: SphincsHashOpts): SphincsSigner {
295
304
  };
296
305
 
297
306
  const chainCoder = base2b(WOTS_LEN2, WOTS_LOGW);
298
- const chainLengths = (msg: Uint8Array) => {
307
+ const chainLengths = (msg: TArg<Uint8Array>) => {
299
308
  const W1 = base2b(WOTS_LEN1, WOTS_LOGW)(msg);
300
309
  let csum = 0;
301
310
  for (let i = 0; i < W1.length; i++) csum += W - 1 - W1[i]; // ▷ Compute checksum
@@ -321,9 +330,15 @@ function gen(opts: SphincsOpts, hashOpts: SphincsHashOpts): SphincsSigner {
321
330
  );
322
331
  // `pkSeed` is the full public key byte string `PK.seed || PK.root`; after splitting `Hmsg`,
323
332
  // mask away any spare high bits so `idx_tree` / `idx_leaf` match the spec's final mod-2^k steps.
324
- const hashMessage = (R: Uint8Array, pkSeed: Uint8Array, msg: Uint8Array, context: Context) => {
333
+ const hashMessage = (
334
+ R: TArg<Uint8Array>,
335
+ pkSeed: TArg<Uint8Array>,
336
+ msg: TArg<Uint8Array>,
337
+ context: TArg<Context>
338
+ ) => {
339
+ const rawContext = context as Context;
325
340
  // digest ← Hmsg(R, PK.seed, PK.root, M)
326
- const digest = context.Hmsg(R, pkSeed, msg, hashMsgCoder.bytesLen);
341
+ const digest = rawContext.Hmsg(R, pkSeed, msg, hashMsgCoder.bytesLen);
327
342
  const [md, tmpIdxTree, tmpIdxLeaf] = hashMsgCoder.decode(digest);
328
343
  const tree = bytesToNumberBE(tmpIdxTree) & getMaskBig(TREE_BITS);
329
344
  const leafIdx = Number(bytesToNumberBE(tmpIdxLeaf)) & getMask(LEAF_BITS);
@@ -335,24 +350,33 @@ function gen(opts: SphincsOpts, hashOpts: SphincsHashOpts): SphincsSigner {
335
350
  // neighbor of the target leaf at that height.
336
351
  const treehash = <T>(
337
352
  height: number,
338
- fn: (leafIdx: number, addrOffset: number, context: Context, info: T) => Uint8Array
353
+ fn: TArg<(leafIdx: number, addrOffset: number, context: Context, info: T) => Uint8Array>
339
354
  ) =>
340
355
  function treehash_i(
341
- context: Context,
356
+ context: TArg<Context>,
342
357
  leafIdx: number,
343
358
  idxOffset: number,
344
- treeAddr: ADRS,
359
+ treeAddr: TArg<ADRS>,
345
360
  info: T
346
361
  ) {
362
+ const rawContext = context as Context;
363
+ const leafFn = fn as (
364
+ leafIdx: number,
365
+ addrOffset: number,
366
+ context: Context,
367
+ info: T
368
+ ) => Uint8Array;
347
369
  const maxIdx = (1 << height) - 1;
348
370
  const stack = new Uint8Array(height * N);
349
371
  const authPath = new Uint8Array(height * N);
372
+ // One node buffer per treehash call (not per leaf): both halves are fully overwritten at
373
+ // each use, and the returned root aliases cur1, which is never reused after return.
374
+ const current = new Uint8Array(2 * N);
375
+ const cur0 = current.subarray(0, N);
376
+ const cur1 = current.subarray(N);
350
377
  for (let idx = 0; ; idx++) {
351
- const current = new Uint8Array(2 * N);
352
- const cur0 = current.subarray(0, N);
353
- const cur1 = current.subarray(N);
354
378
  const addrOffset = idx + idxOffset;
355
- cur1.set(fn(leafIdx, addrOffset, context, info));
379
+ cur1.set(leafFn(leafIdx, addrOffset, rawContext, info));
356
380
  let h = 0;
357
381
  for (let i = idx, o = idxOffset, l = leafIdx; ; h++, i >>>= 1, l >>>= 1, o >>>= 1) {
358
382
  if (h === height) return { root: cur1, authPath }; // Returns from here
@@ -360,7 +384,7 @@ function gen(opts: SphincsOpts, hashOpts: SphincsHashOpts): SphincsSigner {
360
384
  if ((i & 1) === 0 && idx < maxIdx) break;
361
385
  setAddr({ height: h + 1, index: (i >> 1) + (o >> 1) }, treeAddr);
362
386
  cur0.set(stack.subarray(h * N).subarray(0, N));
363
- cur1.set(context.thashN(2, current, treeAddr));
387
+ cur1.set(rawContext.thashN(2, current, treeAddr));
364
388
  }
365
389
  stack.subarray(h * N).set(cur1); // stack.push(cur1)
366
390
  }
@@ -374,45 +398,53 @@ function gen(opts: SphincsOpts, hashOpts: SphincsHashOpts): SphincsSigner {
374
398
  leafAddr: ADRS;
375
399
  pkAddr: ADRS;
376
400
  };
377
- const wotsTreehash = treehash(TREE_HEIGHT, (leafIdx, addrOffset, context, info: LeafInfo) => {
378
- const wotsPk = new Uint8Array(WOTS_LEN * N);
379
- // `keygen()` passes `leafIdx = ~0 >>> 0`, so no real XMSS leaf matches and this suppresses
380
- // WOTS signature capture while still hashing every chain to its public-key endpoint.
381
- const wotsKmask = addrOffset === leafIdx ? 0 : ~0 >>> 0;
382
- setAddr({ keypair: addrOffset }, info.leafAddr);
383
- setAddr({ keypair: addrOffset }, info.pkAddr);
384
- for (let i = 0; i < WOTS_LEN; i++) {
385
- const wotsK = info.wotsSteps[i] | wotsKmask;
386
- const pk = wotsPk.subarray(i * N, (i + 1) * N);
387
- setAddr({ chain: i, hash: 0, type: AddressType.WOTSPRF }, info.leafAddr);
388
- pk.set(context.PRFaddr(info.leafAddr));
389
- setAddr({ type: AddressType.WOTS }, info.leafAddr);
390
- for (let k = 0; ; k++) {
391
- if (k === wotsK) info.wotsSig.subarray(i * N).set(pk); //wotsSig.push()
392
- if (k === W - 1) break;
393
- setAddr({ hash: k }, info.leafAddr);
394
- pk.set(context.thash1(pk, info.leafAddr));
401
+ const wotsTreehash = treehash(
402
+ TREE_HEIGHT,
403
+ (leafIdx: number, addrOffset: number, context: TArg<Context>, info: TArg<LeafInfo>) => {
404
+ const rawContext = context as Context;
405
+ const wotsPk = new Uint8Array(WOTS_LEN * N);
406
+ // `keygen()` passes `leafIdx = ~0 >>> 0`, so no real XMSS leaf matches and this suppresses
407
+ // WOTS signature capture while still hashing every chain to its public-key endpoint.
408
+ const wotsKmask = addrOffset === leafIdx ? 0 : ~0 >>> 0;
409
+ setAddr({ keypair: addrOffset }, info.leafAddr);
410
+ setAddr({ keypair: addrOffset }, info.pkAddr);
411
+ for (let i = 0; i < WOTS_LEN; i++) {
412
+ const wotsK = info.wotsSteps[i] | wotsKmask;
413
+ const pk = wotsPk.subarray(i * N, (i + 1) * N);
414
+ setAddr({ chain: i, hash: 0, type: AddressType.WOTSPRF }, info.leafAddr);
415
+ pk.set(rawContext.PRFaddr(info.leafAddr));
416
+ setAddr({ type: AddressType.WOTS }, info.leafAddr);
417
+ for (let k = 0; ; k++) {
418
+ if (k === wotsK) info.wotsSig.subarray(i * N).set(pk); //wotsSig.push()
419
+ if (k === W - 1) break;
420
+ setAddr({ hash: k }, info.leafAddr);
421
+ pk.set(rawContext.thash1(pk, info.leafAddr));
422
+ }
395
423
  }
424
+ return rawContext.thashN(WOTS_LEN, wotsPk, info.pkAddr);
396
425
  }
397
- return context.thashN(WOTS_LEN, wotsPk, info.pkAddr);
398
- });
426
+ );
399
427
 
400
- const forsTreehash = treehash(A, (_, addrOffset, context, forsLeafAddr: ForsLeafInfo) => {
401
- setAddr({ type: AddressType.FORSPRF, index: addrOffset }, forsLeafAddr);
402
- const prf = context.PRFaddr(forsLeafAddr);
403
- setAddr({ type: AddressType.FORSTREE }, forsLeafAddr);
404
- return context.thash1(prf, forsLeafAddr);
405
- });
428
+ const forsTreehash = treehash(
429
+ A,
430
+ (_: number, addrOffset: number, context: TArg<Context>, forsLeafAddr: TArg<ForsLeafInfo>) => {
431
+ const rawContext = context as Context;
432
+ setAddr({ type: AddressType.FORSPRF, index: addrOffset }, forsLeafAddr);
433
+ const prf = rawContext.PRFaddr(forsLeafAddr);
434
+ setAddr({ type: AddressType.FORSTREE }, forsLeafAddr);
435
+ return rawContext.thash1(prf, forsLeafAddr);
436
+ }
437
+ );
406
438
 
407
439
  // Fuse `xmss_sign` with the subtree-root computation needed by `ht_sign`, so one tree walk
408
440
  // yields both the WOTS/auth-path signature and the root that the next hypertree layer signs.
409
441
  const merkleSign = (
410
- context: Context,
411
- wotsAddr: ADRS,
412
- treeAddr: ADRS,
442
+ context: TArg<Context>,
443
+ wotsAddr: TArg<ADRS>,
444
+ treeAddr: TArg<ADRS>,
413
445
  leafIdx: number,
414
- prevRoot: Uint8Array = new Uint8Array(N)
415
- ) => {
446
+ prevRoot: TArg<Uint8Array> = new Uint8Array(N)
447
+ ): TRet<{ root: Uint8Array; sigWots: Uint8Array; sigAuth: Uint8Array }> => {
416
448
  setAddr({ type: AddressType.HASHTREE }, treeAddr);
417
449
  // State variables
418
450
  const info = {
@@ -426,20 +458,21 @@ function gen(opts: SphincsOpts, hashOpts: SphincsHashOpts): SphincsSigner {
426
458
  root,
427
459
  sigWots: info.wotsSig.subarray(0, WOTS_LEN * N),
428
460
  sigAuth: authPath,
429
- };
461
+ } as TRet<{ root: Uint8Array; sigWots: Uint8Array; sigAuth: Uint8Array }>;
430
462
  };
431
463
 
432
464
  type ForsLeafInfo = ADRS;
433
465
 
434
466
  const computeRoot = (
435
- leaf: Uint8Array,
467
+ leaf: TArg<Uint8Array>,
436
468
  leafIdx: number,
437
469
  idxOffset: number,
438
- authPath: Uint8Array,
470
+ authPath: TArg<Uint8Array>,
439
471
  treeHeight: number,
440
- context: Context,
441
- addr: ADRS
472
+ context: TArg<Context>,
473
+ addr: TArg<ADRS>
442
474
  ) => {
475
+ const rawContext = context as Context;
443
476
  const buffer = new Uint8Array(2 * N);
444
477
  const b0 = buffer.subarray(0, N);
445
478
  const b1 = buffer.subarray(N, 2 * N);
@@ -462,16 +495,16 @@ function gen(opts: SphincsOpts, hashOpts: SphincsHashOpts): SphincsSigner {
462
495
  setAddr({ height: i + 1, index: leafIdx + idxOffset }, addr);
463
496
  const a = authPath.subarray((i + 1) * N, (i + 2) * N);
464
497
  if ((leafIdx & 1) !== 0) {
465
- b1.set(context.thashN(2, buffer, addr));
498
+ b1.set(rawContext.thashN(2, buffer, addr));
466
499
  b0.set(a);
467
500
  } else {
468
- buffer.set(context.thashN(2, buffer, addr));
501
+ buffer.set(rawContext.thashN(2, buffer, addr));
469
502
  b1.set(a);
470
503
  }
471
504
  }
472
505
  // Root
473
506
  setAddr({ height: treeHeight, index: leafIdx + idxOffset }, addr);
474
- return context.thashN(2, buffer, addr);
507
+ return rawContext.thashN(2, buffer, addr);
475
508
  };
476
509
 
477
510
  const seedCoder = splitCoder('seed', N, N, N);
@@ -480,16 +513,16 @@ function gen(opts: SphincsOpts, hashOpts: SphincsHashOpts): SphincsSigner {
480
513
  const forsCoder = vecCoder(splitCoder('fors', N, N * A), K);
481
514
  const wotsCoder = vecCoder(splitCoder('wots', WOTS_LEN * N, TREE_HEIGHT * N), D);
482
515
  const sigCoder = splitCoder('signature', N, forsCoder, wotsCoder); // random || fors || wots
483
- const internal: Signer = {
484
- info: { type: 'internal-slh-dsa' },
485
- lengths: {
516
+ const internal: TRet<Signer> = Object.freeze({
517
+ info: Object.freeze({ type: 'internal-slh-dsa' }),
518
+ lengths: Object.freeze({
486
519
  publicKey: publicCoder.bytesLen,
487
520
  secretKey: secretCoder.bytesLen,
488
521
  signature: sigCoder.bytesLen,
489
522
  seed: seedCoder.bytesLen,
490
523
  signRand: N,
491
- },
492
- keygen(seed?: Uint8Array) {
524
+ }),
525
+ keygen(seed?: TArg<Uint8Array>) {
493
526
  if (seed !== undefined) abytes(seed, seedCoder.bytesLen, 'seed');
494
527
  seed = seed === undefined ? randomBytes(seedCoder.bytesLen) : copyBytes(seed);
495
528
  // Set SK.seed, SK.prf, and PK.seed to random n-byte
@@ -504,13 +537,16 @@ function gen(opts: SphincsOpts, hashOpts: SphincsHashOpts): SphincsSigner {
504
537
  const secretKey = secretCoder.encode([secretSeed, secretPRF, publicKey]);
505
538
  context.clean();
506
539
  cleanBytes(secretSeed, secretPRF, root, wotsAddr, topTreeAddr);
507
- return { publicKey, secretKey };
540
+ return {
541
+ publicKey: publicKey as TRet<Uint8Array>,
542
+ secretKey: secretKey as TRet<Uint8Array>,
543
+ };
508
544
  },
509
- getPublicKey: (secretKey: Uint8Array) => {
545
+ getPublicKey: (secretKey: TArg<Uint8Array>): TRet<Uint8Array> => {
510
546
  const [_skSeed, _skPRF, pk] = secretCoder.decode(secretKey);
511
- return Uint8Array.from(pk);
547
+ return Uint8Array.from(pk) as TRet<Uint8Array>;
512
548
  },
513
- sign: (msg: Uint8Array, sk: Uint8Array, opts: SigOpts = {}) => {
549
+ sign: (msg: TArg<Uint8Array>, sk: TArg<Uint8Array>, opts: TArg<SigOpts> = {}) => {
514
550
  validateSigOpts(opts);
515
551
  let { extraEntropy: random } = opts;
516
552
  const [skSeed, skPRF, pk] = secretCoder.decode(sk); // todo: fix
@@ -545,7 +581,9 @@ function gen(opts: SphincsOpts, hashOpts: SphincsHashOpts): SphincsSigner {
545
581
  },
546
582
  forsTreeAddr
547
583
  );
548
- const prf = context.PRFaddr(forsTreeAddr);
584
+ // Copy: PRFaddr returns a per-context scratch view, and this value is retained in
585
+ // `fors` across the many PRFaddr calls inside forsTreehash below.
586
+ const prf = copyBytes(context.PRFaddr(forsTreeAddr));
549
587
  setAddr({ type: AddressType.FORSTREE }, forsTreeAddr);
550
588
  const { root, authPath } = forsTreehash(
551
589
  context,
@@ -561,7 +599,9 @@ function gen(opts: SphincsOpts, hashOpts: SphincsHashOpts): SphincsSigner {
561
599
  type: AddressType.FORSPK,
562
600
  keypairAddr: wotsAddr,
563
601
  });
564
- const root = context.thashN(K, concatBytes(...roots), forsPkAddr);
602
+ // Copy: thashN returns a per-context scratch view, and `root` lives across every hash
603
+ // call in the hypertree loop below (it is also mutated via root.set).
604
+ const root = copyBytes(context.thashN(K, concatBytes(...roots), forsPkAddr));
565
605
  // WOTS signatures
566
606
  const treeAddr = setAddr({ type: AddressType.HASHTREE });
567
607
  const wots: [Uint8Array, Uint8Array][] = [];
@@ -581,13 +621,17 @@ function gen(opts: SphincsOpts, hashOpts: SphincsHashOpts): SphincsSigner {
581
621
  context.clean();
582
622
  const SIG = sigCoder.encode([R, fors, wots]);
583
623
  cleanBytes(R, random, treeAddr, wotsAddr, forsLeaf, forsTreeAddr, indices, roots);
584
- return SIG;
624
+ return SIG as TRet<Uint8Array>;
585
625
  },
586
- verify: (sig: Uint8Array, msg: Uint8Array, publicKey: Uint8Array) => {
626
+ verify: (sig: TArg<Uint8Array>, msg: TArg<Uint8Array>, publicKey: TArg<Uint8Array>) => {
587
627
  const [pkSeed, pubRoot] = publicCoder.decode(publicKey);
588
- const [random, forsVec, wotsVec] = sigCoder.decode(sig);
589
628
  const pk = publicKey;
629
+ // FIPS 205 Algorithm 20 step 1: wrong-length signatures return false instead of throwing
630
+ // (same as ml-dsa). Must run before sigCoder.decode, which throws on length mismatch.
631
+ // Preserve TypeError for non-byte API arguments before treating byte lengths as invalid.
632
+ abytes(sig, undefined, 'signature');
590
633
  if (sig.length !== sigCoder.bytesLen) return false;
634
+ const [random, forsVec, wotsVec] = sigCoder.decode(sig);
591
635
  const context = getContext(pkSeed);
592
636
  let { tree, leafIdx, md } = hashMessage(random, pk, msg, context);
593
637
  const wotsAddr = setAddr({
@@ -607,14 +651,18 @@ function gen(opts: SphincsOpts, hashOpts: SphincsHashOpts): SphincsSigner {
607
651
  const idxOffset = i << A;
608
652
  setAddr({ height: 0, index: indices[i] + idxOffset }, forsTreeAddr);
609
653
  const leaf = context.thash1(prf, forsTreeAddr);
610
- // Compute inplace, because we need all roots in same byte array
611
- roots.push(computeRoot(leaf, indices[i], idxOffset, authPath, A, context, forsTreeAddr));
654
+ // Copy: computeRoot returns a thashN scratch view, and roots are retained across the
655
+ // remaining FORS iterations (computeRoot itself copies `leaf` before hashing).
656
+ roots.push(
657
+ copyBytes(computeRoot(leaf, indices[i], idxOffset, authPath, A, context, forsTreeAddr))
658
+ );
612
659
  }
613
660
  const forsPkAddr = setAddr({
614
661
  type: AddressType.FORSPK,
615
662
  keypairAddr: wotsAddr,
616
663
  });
617
- let root = context.thashN(K, concatBytes(...roots), forsPkAddr); // root = thash()
664
+ // Copy: `root` must survive the thash1/thashN calls of the WOTS chain loop below.
665
+ let root = copyBytes(context.thashN(K, concatBytes(...roots), forsPkAddr)); // root = thash()
618
666
  // WOTS signature
619
667
  const treeAddr = setAddr({ type: AddressType.HASHTREE });
620
668
  const wotsPkAddr = setAddr({ type: AddressType.WOTSPK });
@@ -637,83 +685,119 @@ function gen(opts: SphincsOpts, hashOpts: SphincsHashOpts): SphincsSigner {
637
685
  }
638
686
  }
639
687
  const leaf = context.thashN(WOTS_LEN, wotsPk, wotsPkAddr);
640
- root = computeRoot(leaf, leafIdx, 0, sigAuth, TREE_HEIGHT, context, treeAddr);
688
+ // Copy: `root` is read by chainLengths / equalBytes after later hash calls.
689
+ root = copyBytes(computeRoot(leaf, leafIdx, 0, sigAuth, TREE_HEIGHT, context, treeAddr));
641
690
  leafIdx = Number(tree & getMaskBig(TREE_HEIGHT));
642
691
  }
643
692
  return equalBytes(root, pubRoot);
644
693
  },
645
- };
646
- return {
647
- info: { type: 'slh-dsa' },
694
+ });
695
+ return Object.freeze({
696
+ info: Object.freeze({ type: 'slh-dsa' }),
648
697
  internal,
649
698
  securityLevel: securityLevel,
650
699
  lengths: internal.lengths,
651
700
  keygen: internal.keygen,
652
701
  getPublicKey: internal.getPublicKey,
653
- sign: (msg: Uint8Array, secretKey: Uint8Array, opts: SigOpts = {}) => {
702
+ sign: (msg: TArg<Uint8Array>, secretKey: TArg<Uint8Array>, opts: TArg<SigOpts> = {}) => {
654
703
  validateSigOpts(opts);
655
704
  const M = getMessage(msg, opts.context);
656
705
  const res = internal.sign(M, secretKey, opts);
657
706
  cleanBytes(M);
658
- return res;
707
+ return res as TRet<Uint8Array>;
659
708
  },
660
- verify: (sig: Uint8Array, msg: Uint8Array, publicKey: Uint8Array, opts: VerOpts = {}) => {
709
+ verify: (
710
+ sig: TArg<Uint8Array>,
711
+ msg: TArg<Uint8Array>,
712
+ publicKey: TArg<Uint8Array>,
713
+ opts: TArg<VerOpts> = {}
714
+ ) => {
661
715
  validateVerOpts(opts);
662
716
  return internal.verify(sig, getMessage(msg, opts.context), publicKey);
663
717
  },
664
- prehash: (hash: CHash) => {
665
- checkHash(hash, securityLevel);
666
- return {
667
- info: { type: 'hashslh-dsa' },
718
+ prehash: (hash: TArg<CHash>): TRet<Signer> => {
719
+ checkHash(hash as CHash, securityLevel);
720
+ const rawHash = hash as CHash;
721
+ return Object.freeze({
722
+ info: Object.freeze({ type: 'hashslh-dsa' }),
668
723
  lengths: internal.lengths,
669
724
  keygen: internal.keygen,
670
725
  getPublicKey: internal.getPublicKey,
671
- sign: (msg: Uint8Array, secretKey: Uint8Array, opts: SigOpts = {}) => {
726
+ sign: (msg: TArg<Uint8Array>, secretKey: TArg<Uint8Array>, opts: TArg<SigOpts> = {}) => {
672
727
  validateSigOpts(opts);
673
- const M = getMessagePrehash(hash, msg, opts.context);
728
+ const M = getMessagePrehash(rawHash, msg, opts.context);
674
729
  const res = internal.sign(M, secretKey, opts);
675
730
  cleanBytes(M);
676
- return res;
731
+ return res as TRet<Uint8Array>;
677
732
  },
678
- verify: (sig: Uint8Array, msg: Uint8Array, publicKey: Uint8Array, opts: VerOpts = {}) => {
733
+ verify: (
734
+ sig: TArg<Uint8Array>,
735
+ msg: TArg<Uint8Array>,
736
+ publicKey: TArg<Uint8Array>,
737
+ opts: TArg<VerOpts> = {}
738
+ ) => {
679
739
  validateVerOpts(opts);
680
- return internal.verify(sig, getMessagePrehash(hash, msg, opts.context), publicKey);
740
+ return internal.verify(sig, getMessagePrehash(rawHash, msg, opts.context), publicKey);
681
741
  },
682
- };
742
+ });
683
743
  },
684
- };
744
+ });
685
745
  }
686
746
 
687
747
  // FIPS 205 §11.1 SHAKE instantiation: this path hashes the full uncompressed address bytes,
688
748
  // unlike the compressed 22-byte SHA2 path in §11.2.
689
749
  const genShake =
690
- (): GetContext => (opts: SphincsOpts) => (pubSeed: Uint8Array, skSeed?: Uint8Array) => {
750
+ (): TRet<GetContext> =>
751
+ (opts: SphincsOpts) =>
752
+ (pubSeed: TArg<Uint8Array>, skSeed?: TArg<Uint8Array>): TRet<Context> => {
691
753
  const { N } = opts;
692
754
  const stats = { prf: 0, thash: 0, hmsg: 0, gen_message_random: 0 };
693
755
  // §11.1 prefixes PRF/F/H/T_l with `PK.seed`, so cache that absorbed prefix once and clone it
694
756
  // for each address-bound call instead of reabsorbing the same seed every time.
695
757
  const h0 = shake256.create({}).update(pubSeed);
696
758
  const h0tmp = h0.clone();
697
- const thash = (blocks: number, input: Uint8Array, addr: ADRS) => {
759
+ // Per-context output scratch: thash1/thashN/PRFaddr return these buffers directly, so
760
+ // callers must consume or copy a result before the next call on the same lane.
761
+ const thashOut = new Uint8Array(N);
762
+ const prfOut = new Uint8Array(N);
763
+ const thash = (blocks: number, input: TArg<Uint8Array>, addr: TArg<ADRS>): TRet<Uint8Array> => {
698
764
  stats.thash++;
699
- return h0
700
- ._cloneInto(h0tmp)
765
+ const len = blocks * N;
766
+ h0._cloneInto(h0tmp)
701
767
  .update(addr)
702
- .update(input.subarray(0, blocks * N))
703
- .xof(N);
768
+ .update(
769
+ input.length === len ? (input as Uint8Array) : (input as Uint8Array).subarray(0, len)
770
+ )
771
+ .xofInto(thashOut);
772
+ return thashOut as TRet<Uint8Array>;
704
773
  };
705
774
  return {
706
- PRFaddr: (addr: ADRS) => {
775
+ PRFaddr: (addr: TArg<ADRS>): TRet<Uint8Array> => {
707
776
  if (!skSeed) throw new Error('no sk seed');
708
777
  stats.prf++;
709
- const res = h0._cloneInto(h0tmp).update(addr).update(skSeed).xof(N);
710
- return res;
778
+ h0._cloneInto(h0tmp).update(addr).update(skSeed).xofInto(prfOut);
779
+ return prfOut as TRet<Uint8Array>;
711
780
  },
712
- PRFmsg: (skPRF: Uint8Array, random: Uint8Array, msg: Uint8Array) => {
781
+ PRFmsg: (
782
+ skPRF: TArg<Uint8Array>,
783
+ random: TArg<Uint8Array>,
784
+ msg: TArg<Uint8Array>
785
+ ): TRet<Uint8Array> => {
713
786
  stats.gen_message_random++;
714
- return shake256.create({}).update(skPRF).update(random).update(msg).digest().subarray(0, N);
787
+ return shake256
788
+ .create({})
789
+ .update(skPRF)
790
+ .update(random)
791
+ .update(msg)
792
+ .digest()
793
+ .subarray(0, N) as TRet<Uint8Array>;
715
794
  },
716
- Hmsg: (R: Uint8Array, pk: Uint8Array, m: Uint8Array, outLen) => {
795
+ Hmsg: (
796
+ R: TArg<Uint8Array>,
797
+ pk: TArg<Uint8Array>,
798
+ m: TArg<Uint8Array>,
799
+ outLen
800
+ ): TRet<Uint8Array> => {
717
801
  stats.hmsg++;
718
802
  return shake256.create({}).update(R.subarray(0, N)).update(pk).update(m).xof(outLen);
719
803
  },
@@ -722,9 +806,10 @@ const genShake =
722
806
  clean: () => {
723
807
  h0.destroy();
724
808
  h0tmp.destroy();
809
+ cleanBytes(thashOut, prfOut);
725
810
  //console.log(stats);
726
811
  },
727
- };
812
+ } as TRet<Context>;
728
813
  };
729
814
 
730
815
  const SHAKE_SIMPLE = /* @__PURE__ */ (() => ({ getContext: genShake() }))();
@@ -734,42 +819,42 @@ const SHAKE_SIMPLE = /* @__PURE__ */ (() => ({ getContext: genShake() }))();
734
819
  * lengths `publicKey=32`, `secretKey=64`, `signature=17088`, `seed=48`, `signRand=16`.
735
820
  * Also exposes `.prehash(...)`.
736
821
  */
737
- export const slh_dsa_shake_128f: SphincsSigner = /* @__PURE__ */ (() =>
822
+ export const slh_dsa_shake_128f: TRet<SphincsSigner> = /* @__PURE__ */ (() =>
738
823
  gen(PARAMS['128f'], SHAKE_SIMPLE))();
739
824
  /**
740
825
  * SLH-DSA-SHAKE-128s: Table 2 row `n=16, h=63, d=7, h'=9, a=12, k=14, lg w=4, m=30`;
741
826
  * lengths `publicKey=32`, `secretKey=64`, `signature=7856`, `seed=48`, `signRand=16`.
742
827
  * Also exposes `.prehash(...)`.
743
828
  */
744
- export const slh_dsa_shake_128s: SphincsSigner = /* @__PURE__ */ (() =>
829
+ export const slh_dsa_shake_128s: TRet<SphincsSigner> = /* @__PURE__ */ (() =>
745
830
  gen(PARAMS['128s'], SHAKE_SIMPLE))();
746
831
  /**
747
832
  * SLH-DSA-SHAKE-192f: Table 2 row `n=24, h=66, d=22, h'=3, a=8, k=33, lg w=4, m=42`;
748
833
  * lengths `publicKey=48`, `secretKey=96`, `signature=35664`, `seed=72`, `signRand=24`.
749
834
  * Also exposes `.prehash(...)`.
750
835
  */
751
- export const slh_dsa_shake_192f: SphincsSigner = /* @__PURE__ */ (() =>
836
+ export const slh_dsa_shake_192f: TRet<SphincsSigner> = /* @__PURE__ */ (() =>
752
837
  gen(PARAMS['192f'], SHAKE_SIMPLE))();
753
838
  /**
754
839
  * SLH-DSA-SHAKE-192s: Table 2 row `n=24, h=63, d=7, h'=9, a=14, k=17, lg w=4, m=39`;
755
840
  * lengths `publicKey=48`, `secretKey=96`, `signature=16224`, `seed=72`, `signRand=24`.
756
841
  * Also exposes `.prehash(...)`.
757
842
  */
758
- export const slh_dsa_shake_192s: SphincsSigner = /* @__PURE__ */ (() =>
843
+ export const slh_dsa_shake_192s: TRet<SphincsSigner> = /* @__PURE__ */ (() =>
759
844
  gen(PARAMS['192s'], SHAKE_SIMPLE))();
760
845
  /**
761
846
  * SLH-DSA-SHAKE-256f: Table 2 row `n=32, h=68, d=17, h'=4, a=9, k=35, lg w=4, m=49`;
762
847
  * lengths `publicKey=64`, `secretKey=128`, `signature=49856`, `seed=96`, `signRand=32`.
763
848
  * Also exposes `.prehash(...)`.
764
849
  */
765
- export const slh_dsa_shake_256f: SphincsSigner = /* @__PURE__ */ (() =>
850
+ export const slh_dsa_shake_256f: TRet<SphincsSigner> = /* @__PURE__ */ (() =>
766
851
  gen(PARAMS['256f'], SHAKE_SIMPLE))();
767
852
  /**
768
853
  * SLH-DSA-SHAKE-256s: Table 2 row `n=32, h=64, d=8, h'=8, a=14, k=22, lg w=4, m=47`;
769
854
  * lengths `publicKey=64`, `secretKey=128`, `signature=29792`, `seed=96`, `signRand=32`.
770
855
  * Also exposes `.prehash(...)`.
771
856
  */
772
- export const slh_dsa_shake_256s: SphincsSigner = /* @__PURE__ */ (() =>
857
+ export const slh_dsa_shake_256s: TRet<SphincsSigner> = /* @__PURE__ */ (() =>
773
858
  gen(PARAMS['256s'], SHAKE_SIMPLE))();
774
859
 
775
860
  type ShaType = typeof sha256 | typeof sha512;
@@ -777,9 +862,9 @@ type ShaType = typeof sha256 | typeof sha512;
777
862
  // category-1 keeps everything on SHA-256, while category-3/5 keep `PRFaddr` / `thash1`
778
863
  // on SHA-256 but switch `PRFmsg`, `Hmsg`, and multi-block `thashN` to SHA-512.
779
864
  const genSha =
780
- (h0: ShaType, h1: ShaType): GetContext =>
865
+ (h0: ShaType, h1: ShaType): TRet<GetContext> =>
781
866
  (opts) =>
782
- (pub_seed, sk_seed?) => {
867
+ (pub_seed: TArg<Uint8Array>, sk_seed?: TArg<Uint8Array>): TRet<Context> => {
783
868
  const { N } = opts;
784
869
  /*
785
870
  Perf debug stats, how much hashes we call?
@@ -804,12 +889,22 @@ const genSha =
804
889
 
805
890
  const h0tmp = h0ps.clone();
806
891
  const h1tmp = h1ps.clone();
892
+ // Per-context output scratch: thash1/thashN/PRFaddr return views into these buffers, so
893
+ // callers must consume or copy a result before the next call on the same lane (see Context
894
+ // docs). digestInto also skips digest()'s per-call destroy(): the tmp states are fully
895
+ // overwritten by the next _cloneInto and wiped in clean().
896
+ const h0out = new Uint8Array(h0.outputLen);
897
+ const h1out = new Uint8Array(h1.outputLen);
898
+ const prfOut = new Uint8Array(h0.outputLen);
899
+ const h0outN = h0out.subarray(0, N);
900
+ const h1outN = h1out.subarray(0, N);
901
+ const prfOutN = prfOut.subarray(0, N);
807
902
 
808
903
  // https://www.rfc-editor.org/rfc/rfc8017.html#appendix-B.2.1
809
904
  // This local helper is intentionally stricter than generic MGF1 reuse: current SLH-DSA callers
810
905
  // only request tiny `m`-byte outputs, but the guard below rejects `length > 2^32` instead of
811
906
  // RFC 8017's broader `maskLen > 2^32 * hLen` bound.
812
- function mgf1(seed: Uint8Array, length: number, hash: ShaType) {
907
+ function mgf1(seed: TArg<Uint8Array>, length: number, hash: ShaType): TRet<Uint8Array> {
813
908
  stats.mgf1++;
814
909
  const out = new Uint8Array(Math.ceil(length / hash.outputLen) * hash.outputLen);
815
910
  // NOT 2^32-1
@@ -820,37 +915,52 @@ const genSha =
820
915
  o = o.subarray(hash.outputLen);
821
916
  }
822
917
  cleanBytes(out.subarray(length));
823
- return out.subarray(0, length);
918
+ return out.subarray(0, length) as TRet<Uint8Array>;
824
919
  }
825
920
 
826
921
  const thash =
827
- (_: ShaType, h: typeof h0ps, hTmp: typeof h0ps) =>
828
- (blocks: number, input: Uint8Array, addr: ADRS) => {
922
+ (h: typeof h0ps, hTmp: typeof h0ps, out: TArg<Uint8Array>, outN: TArg<Uint8Array>) =>
923
+ (blocks: number, input: TArg<Uint8Array>, addr: TArg<ADRS>): TRet<Uint8Array> => {
829
924
  stats.thash++;
830
- const d = h
831
- ._cloneInto(hTmp as any)
925
+ const len = blocks * N;
926
+ h._cloneInto(hTmp as any)
832
927
  .update(addr)
833
- .update(input.subarray(0, blocks * N))
834
- .digest();
835
- return d.subarray(0, N);
928
+ .update(
929
+ input.length === len ? (input as Uint8Array) : (input as Uint8Array).subarray(0, len)
930
+ )
931
+ .digestInto(out);
932
+ return outN as TRet<Uint8Array>;
836
933
  };
837
934
  return {
838
- PRFaddr: (addr: ADRS) => {
935
+ PRFaddr: (addr: TArg<ADRS>): TRet<Uint8Array> => {
839
936
  if (!sk_seed) throw new Error('No sk seed');
840
937
  stats.prf++;
841
- const res = h0ps
938
+ h0ps
842
939
  ._cloneInto(h0tmp as any)
843
940
  .update(addr)
844
941
  .update(sk_seed)
845
- .digest()
846
- .subarray(0, N);
847
- return res;
942
+ .digestInto(prfOut);
943
+ return prfOutN as TRet<Uint8Array>;
848
944
  },
849
- PRFmsg: (skPRF: Uint8Array, random: Uint8Array, msg: Uint8Array) => {
945
+ PRFmsg: (
946
+ skPRF: TArg<Uint8Array>,
947
+ random: TArg<Uint8Array>,
948
+ msg: TArg<Uint8Array>
949
+ ): TRet<Uint8Array> => {
850
950
  stats.gen_message_random++;
851
- return hmac.create(h1, skPRF).update(random).update(msg).digest().subarray(0, N);
951
+ return hmac
952
+ .create(h1, skPRF)
953
+ .update(random)
954
+ .update(msg)
955
+ .digest()
956
+ .subarray(0, N) as TRet<Uint8Array>;
852
957
  },
853
- Hmsg: (R: Uint8Array, pk: Uint8Array, m: Uint8Array, outLen) => {
958
+ Hmsg: (
959
+ R: TArg<Uint8Array>,
960
+ pk: TArg<Uint8Array>,
961
+ m: TArg<Uint8Array>,
962
+ outLen
963
+ ): TRet<Uint8Array> => {
854
964
  stats.hmsg++;
855
965
  const seed = concatBytes(
856
966
  R.subarray(0, N),
@@ -859,16 +969,17 @@ const genSha =
859
969
  );
860
970
  return mgf1(seed, outLen, h1);
861
971
  },
862
- thash1: thash(h0, h0ps, h0tmp).bind(null, 1),
863
- thashN: thash(h1, h1ps, h1tmp),
972
+ thash1: thash(h0ps, h0tmp, h0out, h0outN).bind(null, 1),
973
+ thashN: thash(h1ps, h1tmp, h1out, h1outN),
864
974
  clean: () => {
865
975
  h0ps.destroy();
866
976
  h1ps.destroy();
867
977
  h0tmp.destroy();
868
978
  h1tmp.destroy();
979
+ cleanBytes(h0out, h1out, prfOut);
869
980
  //console.log(stats);
870
981
  },
871
- };
982
+ } as TRet<Context>;
872
983
  };
873
984
 
874
985
  const SHA256_SIMPLE = /* @__PURE__ */ (() => ({
@@ -884,41 +995,58 @@ const SHA512_SIMPLE = /* @__PURE__ */ (() => ({
884
995
  * SLH-DSA-SHA2-128f: Table 2 row `n=16, h=66, d=22, h'=3, a=6, k=33, lg w=4, m=34`;
885
996
  * lengths `publicKey=32`, `secretKey=64`, `signature=17088`, `seed=48`, `signRand=16`.
886
997
  * Also exposes `.prehash(...)`.
998
+ * @example
999
+ * Generate deterministic SLH-DSA keys, sign one message, and verify the signature.
1000
+ * ```ts
1001
+ * import { sha256 } from '@noble/hashes/sha2.js';
1002
+ * import { slh_dsa_sha2_128f } from '@noble/post-quantum/slh-dsa.js';
1003
+ * const seed = new Uint8Array(slh_dsa_sha2_128f.lengths.seed!);
1004
+ * const { secretKey, publicKey } = slh_dsa_sha2_128f.keygen(seed);
1005
+ * const msg = new TextEncoder().encode('hello noble');
1006
+ * const sig = slh_dsa_sha2_128f.sign(msg, secretKey);
1007
+ * const isValid = slh_dsa_sha2_128f.verify(sig, msg, publicKey);
1008
+ * const recovered = slh_dsa_sha2_128f.getPublicKey(secretKey);
1009
+ * const context = new Uint8Array([1, 2, 3]);
1010
+ * const prehash = slh_dsa_sha2_128f.prehash(sha256);
1011
+ * const preSig = prehash.sign(msg, secretKey, { context });
1012
+ * const preValid = prehash.verify(preSig, msg, publicKey, { context });
1013
+ * const internalSig = slh_dsa_sha2_128f.internal.sign(msg, secretKey);
1014
+ * ```
887
1015
  */
888
- export const slh_dsa_sha2_128f: SphincsSigner = /* @__PURE__ */ (() =>
1016
+ export const slh_dsa_sha2_128f: TRet<SphincsSigner> = /* @__PURE__ */ (() =>
889
1017
  gen(PARAMS['128f'], SHA256_SIMPLE))();
890
1018
  /**
891
1019
  * SLH-DSA-SHA2-128s: Table 2 row `n=16, h=63, d=7, h'=9, a=12, k=14, lg w=4, m=30`;
892
1020
  * lengths `publicKey=32`, `secretKey=64`, `signature=7856`, `seed=48`, `signRand=16`.
893
1021
  * Also exposes `.prehash(...)`.
894
1022
  */
895
- export const slh_dsa_sha2_128s: SphincsSigner = /* @__PURE__ */ (() =>
1023
+ export const slh_dsa_sha2_128s: TRet<SphincsSigner> = /* @__PURE__ */ (() =>
896
1024
  gen(PARAMS['128s'], SHA256_SIMPLE))();
897
1025
  /**
898
1026
  * SLH-DSA-SHA2-192f: Table 2 row `n=24, h=66, d=22, h'=3, a=8, k=33, lg w=4, m=42`;
899
1027
  * lengths `publicKey=48`, `secretKey=96`, `signature=35664`, `seed=72`, `signRand=24`.
900
1028
  * Also exposes `.prehash(...)`.
901
1029
  */
902
- export const slh_dsa_sha2_192f: SphincsSigner = /* @__PURE__ */ (() =>
1030
+ export const slh_dsa_sha2_192f: TRet<SphincsSigner> = /* @__PURE__ */ (() =>
903
1031
  gen(PARAMS['192f'], SHA512_SIMPLE))();
904
1032
  /**
905
1033
  * SLH-DSA-SHA2-192s: Table 2 row `n=24, h=63, d=7, h'=9, a=14, k=17, lg w=4, m=39`;
906
1034
  * lengths `publicKey=48`, `secretKey=96`, `signature=16224`, `seed=72`, `signRand=24`.
907
1035
  * Also exposes `.prehash(...)`.
908
1036
  */
909
- export const slh_dsa_sha2_192s: SphincsSigner = /* @__PURE__ */ (() =>
1037
+ export const slh_dsa_sha2_192s: TRet<SphincsSigner> = /* @__PURE__ */ (() =>
910
1038
  gen(PARAMS['192s'], SHA512_SIMPLE))();
911
1039
  /**
912
1040
  * SLH-DSA-SHA2-256f: Table 2 row `n=32, h=68, d=17, h'=4, a=9, k=35, lg w=4, m=49`;
913
1041
  * lengths `publicKey=64`, `secretKey=128`, `signature=49856`, `seed=96`, `signRand=32`.
914
1042
  * Also exposes `.prehash(...)`.
915
1043
  */
916
- export const slh_dsa_sha2_256f: SphincsSigner = /* @__PURE__ */ (() =>
1044
+ export const slh_dsa_sha2_256f: TRet<SphincsSigner> = /* @__PURE__ */ (() =>
917
1045
  gen(PARAMS['256f'], SHA512_SIMPLE))();
918
1046
  /**
919
1047
  * SLH-DSA-SHA2-256s: Table 2 row `n=32, h=64, d=8, h'=8, a=14, k=22, lg w=4, m=47`;
920
1048
  * lengths `publicKey=64`, `secretKey=128`, `signature=29792`, `seed=96`, `signRand=32`.
921
1049
  * Also exposes `.prehash(...)`.
922
1050
  */
923
- export const slh_dsa_sha2_256s: SphincsSigner = /* @__PURE__ */ (() =>
1051
+ export const slh_dsa_sha2_256s: TRet<SphincsSigner> = /* @__PURE__ */ (() =>
924
1052
  gen(PARAMS['256s'], SHA512_SIMPLE))();