@noble/post-quantum 0.6.0 → 0.6.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/src/slh-dsa.ts CHANGED
@@ -53,6 +53,8 @@ import {
53
53
  vecCoder,
54
54
  type Signer,
55
55
  type SigOpts,
56
+ type TArg,
57
+ type TRet,
56
58
  type VerOpts,
57
59
  } from './utils.ts';
58
60
 
@@ -96,14 +98,14 @@ export type SphincsHashOpts = {
96
98
  * stay derived at the export layer.
97
99
  */
98
100
  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)();
101
+ Object.freeze({
102
+ '128f': Object.freeze({ W: 16, N: 16, H: 66, D: 22, K: 33, A: 6, securityLevel: 128 }),
103
+ '128s': Object.freeze({ W: 16, N: 16, H: 63, D: 7, K: 14, A: 12, securityLevel: 128 }),
104
+ '192f': Object.freeze({ W: 16, N: 24, H: 66, D: 22, K: 33, A: 8, securityLevel: 192 }),
105
+ '192s': Object.freeze({ W: 16, N: 24, H: 63, D: 7, K: 17, A: 14, securityLevel: 192 }),
106
+ '256f': Object.freeze({ W: 16, N: 32, H: 68, D: 17, K: 35, A: 9, securityLevel: 256 }),
107
+ '256s': Object.freeze({ W: 16, N: 32, H: 64, D: 8, K: 22, A: 14, securityLevel: 256 }),
108
+ } as const))();
107
109
 
108
110
  // FIPS 205 `ADRS.setTypeAndClear(...)` selectors. Local names shorten the spec labels
109
111
  // (`WOTS_HASH` -> `WOTS`, `TREE` -> `HASHTREE`, `FORS_ROOTS` -> `FORSPK`), and `setAddr({ type })`
@@ -128,7 +130,7 @@ export type Context = {
128
130
  * @param addr - Address bytes.
129
131
  * @returns PRF output bytes.
130
132
  */
131
- PRFaddr: (addr: ADRS) => Uint8Array;
133
+ PRFaddr: (addr: TArg<ADRS>) => TRet<Uint8Array>;
132
134
  /**
133
135
  * Derive the randomized message hash prefix.
134
136
  * @param skPRF - Secret PRF seed.
@@ -136,7 +138,11 @@ export type Context = {
136
138
  * @param msg - Message bytes.
137
139
  * @returns PRF output bytes.
138
140
  */
139
- PRFmsg: (skPRF: Uint8Array, random: Uint8Array, msg: Uint8Array) => Uint8Array;
141
+ PRFmsg: (
142
+ skPRF: TArg<Uint8Array>,
143
+ random: TArg<Uint8Array>,
144
+ msg: TArg<Uint8Array>
145
+ ) => TRet<Uint8Array>;
140
146
  /**
141
147
  * Hash one randomized message transcript.
142
148
  * @param R - Randomized message prefix.
@@ -145,14 +151,19 @@ export type Context = {
145
151
  * @param outLen - Output length in bytes.
146
152
  * @returns Transcript hash bytes.
147
153
  */
148
- Hmsg: (R: Uint8Array, pk: Uint8Array, m: Uint8Array, outLen: number) => Uint8Array;
154
+ Hmsg: (
155
+ R: TArg<Uint8Array>,
156
+ pk: TArg<Uint8Array>,
157
+ m: TArg<Uint8Array>,
158
+ outLen: number
159
+ ) => TRet<Uint8Array>;
149
160
  /**
150
161
  * Tweakable hash over one input block.
151
162
  * @param input - Input block.
152
163
  * @param addr - Address bytes.
153
164
  * @returns Hash output bytes.
154
165
  */
155
- thash1: (input: Uint8Array, addr: ADRS) => Uint8Array;
166
+ thash1: (input: TArg<Uint8Array>, addr: TArg<ADRS>) => TRet<Uint8Array>;
156
167
  /**
157
168
  * Tweakable hash over multiple input blocks.
158
169
  * @param blocks - Number of input blocks.
@@ -160,14 +171,14 @@ export type Context = {
160
171
  * @param addr - Address bytes.
161
172
  * @returns Hash output bytes.
162
173
  */
163
- thashN: (blocks: number, input: Uint8Array, addr: ADRS) => Uint8Array;
174
+ thashN: (blocks: number, input: TArg<Uint8Array>, addr: TArg<ADRS>) => TRet<Uint8Array>;
164
175
  /** Wipe any buffered hash state for the current context. */
165
176
  clean: () => void;
166
177
  };
167
178
  /** Factory that creates a context generator for one SLH-DSA parameter set. */
168
179
  export type GetContext = (
169
180
  opts: SphincsOpts
170
- ) => (pub_seed: Uint8Array, sk_seed?: Uint8Array) => Context;
181
+ ) => (pub_seed: TArg<Uint8Array>, sk_seed?: TArg<Uint8Array>) => TRet<Context>;
171
182
 
172
183
  function hexToNumber(hex: string): bigint {
173
184
  if (typeof hex !== 'string') throw new Error('hex string expected, got ' + typeof hex);
@@ -175,12 +186,12 @@ function hexToNumber(hex: string): bigint {
175
186
  }
176
187
 
177
188
  // BE: Big Endian, LE: Little Endian. This is the local FIPS 205 `toInt(...)` equivalent.
178
- function bytesToNumberBE(bytes: Uint8Array): bigint {
189
+ function bytesToNumberBE(bytes: TArg<Uint8Array>): bigint {
179
190
  return hexToNumber(bytesToHex(bytes));
180
191
  }
181
192
 
182
193
  // Local in-range FIPS 205 `toByte(x, n)` equivalent; callers must keep `n < 256^len`.
183
- function numberToBytesBE(n: number | bigint, len: number): Uint8Array {
194
+ function numberToBytesBE(n: number | bigint, len: number): TRet<Uint8Array> {
184
195
  return hexToBytes(n.toString(16).padStart(len * 2, '0'));
185
196
  }
186
197
 
@@ -189,7 +200,7 @@ function numberToBytesBE(n: number | bigint, len: number): Uint8Array {
189
200
  // short inputs are not rejected and would zero-extend implicitly.
190
201
  const base2b = (outLen: number, b: number) => {
191
202
  const mask = getMask(b);
192
- return (bytes: Uint8Array) => {
203
+ return (bytes: TArg<Uint8Array>): TRet<Uint32Array> => {
193
204
  const baseB = new Uint32Array(outLen);
194
205
  for (let out = 0, pos = 0, bits = 0, total = 0; out < outLen; out++) {
195
206
  while (bits < b) {
@@ -199,7 +210,7 @@ const base2b = (outLen: number, b: number) => {
199
210
  bits -= b;
200
211
  baseB[out] = (total >>> bits) & mask;
201
212
  }
202
- return baseB;
213
+ return baseB as TRet<Uint32Array>;
203
214
  };
204
215
  };
205
216
 
@@ -209,9 +220,9 @@ function getMaskBig(bits: number) {
209
220
 
210
221
  /** Public SLH-DSA signer with prehash customization. */
211
222
  export type SphincsSigner = Signer & {
212
- internal: Signer;
223
+ internal: TRet<Signer>;
213
224
  securityLevel: number;
214
- prehash: (hash: CHash) => Signer;
225
+ prehash: (hash: TArg<CHash>) => TRet<Signer>;
215
226
  };
216
227
 
217
228
  /** One parameter/hash instantiation of the public SLH-DSA API.
@@ -219,7 +230,8 @@ export type SphincsSigner = Signer & {
219
230
  * and `getPublicKey(secretKey)` only extracts the embedded public key
220
231
  * instead of recomputing `PK.root`.
221
232
  */
222
- function gen(opts: SphincsOpts, hashOpts: SphincsHashOpts): SphincsSigner {
233
+ function gen(opts: SphincsOpts, hashOpts_: TArg<SphincsHashOpts>): TRet<SphincsSigner> {
234
+ const hashOpts = hashOpts_ as SphincsHashOpts;
223
235
  const { N, W, H, D, K, A, securityLevel: securityLevel } = opts;
224
236
  const getContext = hashOpts.getContext(opts);
225
237
  if (W !== 16) throw new Error('Unsupported Winternitz parameter');
@@ -256,7 +268,7 @@ function gen(opts: SphincsOpts, hashOpts: SphincsHashOpts): SphincsSigner {
256
268
  // `height` / `chain` and `index` / `hash` share the same spec words, so callers must use the
257
269
  // address-type-specific combinations instead of mixing both meanings in one call.
258
270
  const setAddr = (
259
- opts: {
271
+ opts: TArg<{
260
272
  type?: (typeof AddressType)[keyof typeof AddressType];
261
273
  height?: number;
262
274
  tree?: bigint;
@@ -267,8 +279,8 @@ function gen(opts: SphincsOpts, hashOpts: SphincsHashOpts): SphincsSigner {
267
279
  keypair?: number;
268
280
  subtreeAddr?: ADRS;
269
281
  keypairAddr?: ADRS;
270
- },
271
- addr: ADRS = new Uint8Array(ADDR_BYTES)
282
+ }>,
283
+ addr: TArg<ADRS> = new Uint8Array(ADDR_BYTES)
272
284
  ) => {
273
285
  const { type, height, tree, layer, index, chain, hash, keypair } = opts;
274
286
  const { subtreeAddr, keypairAddr } = opts;
@@ -295,7 +307,7 @@ function gen(opts: SphincsOpts, hashOpts: SphincsHashOpts): SphincsSigner {
295
307
  };
296
308
 
297
309
  const chainCoder = base2b(WOTS_LEN2, WOTS_LOGW);
298
- const chainLengths = (msg: Uint8Array) => {
310
+ const chainLengths = (msg: TArg<Uint8Array>) => {
299
311
  const W1 = base2b(WOTS_LEN1, WOTS_LOGW)(msg);
300
312
  let csum = 0;
301
313
  for (let i = 0; i < W1.length; i++) csum += W - 1 - W1[i]; // ▷ Compute checksum
@@ -321,9 +333,15 @@ function gen(opts: SphincsOpts, hashOpts: SphincsHashOpts): SphincsSigner {
321
333
  );
322
334
  // `pkSeed` is the full public key byte string `PK.seed || PK.root`; after splitting `Hmsg`,
323
335
  // 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) => {
336
+ const hashMessage = (
337
+ R: TArg<Uint8Array>,
338
+ pkSeed: TArg<Uint8Array>,
339
+ msg: TArg<Uint8Array>,
340
+ context: TArg<Context>
341
+ ) => {
342
+ const rawContext = context as Context;
325
343
  // digest ← Hmsg(R, PK.seed, PK.root, M)
326
- const digest = context.Hmsg(R, pkSeed, msg, hashMsgCoder.bytesLen);
344
+ const digest = rawContext.Hmsg(R, pkSeed, msg, hashMsgCoder.bytesLen);
327
345
  const [md, tmpIdxTree, tmpIdxLeaf] = hashMsgCoder.decode(digest);
328
346
  const tree = bytesToNumberBE(tmpIdxTree) & getMaskBig(TREE_BITS);
329
347
  const leafIdx = Number(bytesToNumberBE(tmpIdxLeaf)) & getMask(LEAF_BITS);
@@ -335,15 +353,22 @@ function gen(opts: SphincsOpts, hashOpts: SphincsHashOpts): SphincsSigner {
335
353
  // neighbor of the target leaf at that height.
336
354
  const treehash = <T>(
337
355
  height: number,
338
- fn: (leafIdx: number, addrOffset: number, context: Context, info: T) => Uint8Array
356
+ fn: TArg<(leafIdx: number, addrOffset: number, context: Context, info: T) => Uint8Array>
339
357
  ) =>
340
358
  function treehash_i(
341
- context: Context,
359
+ context: TArg<Context>,
342
360
  leafIdx: number,
343
361
  idxOffset: number,
344
- treeAddr: ADRS,
362
+ treeAddr: TArg<ADRS>,
345
363
  info: T
346
364
  ) {
365
+ const rawContext = context as Context;
366
+ const leafFn = fn as (
367
+ leafIdx: number,
368
+ addrOffset: number,
369
+ context: Context,
370
+ info: T
371
+ ) => Uint8Array;
347
372
  const maxIdx = (1 << height) - 1;
348
373
  const stack = new Uint8Array(height * N);
349
374
  const authPath = new Uint8Array(height * N);
@@ -352,7 +377,7 @@ function gen(opts: SphincsOpts, hashOpts: SphincsHashOpts): SphincsSigner {
352
377
  const cur0 = current.subarray(0, N);
353
378
  const cur1 = current.subarray(N);
354
379
  const addrOffset = idx + idxOffset;
355
- cur1.set(fn(leafIdx, addrOffset, context, info));
380
+ cur1.set(leafFn(leafIdx, addrOffset, rawContext, info));
356
381
  let h = 0;
357
382
  for (let i = idx, o = idxOffset, l = leafIdx; ; h++, i >>>= 1, l >>>= 1, o >>>= 1) {
358
383
  if (h === height) return { root: cur1, authPath }; // Returns from here
@@ -360,7 +385,7 @@ function gen(opts: SphincsOpts, hashOpts: SphincsHashOpts): SphincsSigner {
360
385
  if ((i & 1) === 0 && idx < maxIdx) break;
361
386
  setAddr({ height: h + 1, index: (i >> 1) + (o >> 1) }, treeAddr);
362
387
  cur0.set(stack.subarray(h * N).subarray(0, N));
363
- cur1.set(context.thashN(2, current, treeAddr));
388
+ cur1.set(rawContext.thashN(2, current, treeAddr));
364
389
  }
365
390
  stack.subarray(h * N).set(cur1); // stack.push(cur1)
366
391
  }
@@ -374,45 +399,53 @@ function gen(opts: SphincsOpts, hashOpts: SphincsHashOpts): SphincsSigner {
374
399
  leafAddr: ADRS;
375
400
  pkAddr: ADRS;
376
401
  };
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));
402
+ const wotsTreehash = treehash(
403
+ TREE_HEIGHT,
404
+ (leafIdx: number, addrOffset: number, context: TArg<Context>, info: TArg<LeafInfo>) => {
405
+ const rawContext = context as Context;
406
+ const wotsPk = new Uint8Array(WOTS_LEN * N);
407
+ // `keygen()` passes `leafIdx = ~0 >>> 0`, so no real XMSS leaf matches and this suppresses
408
+ // WOTS signature capture while still hashing every chain to its public-key endpoint.
409
+ const wotsKmask = addrOffset === leafIdx ? 0 : ~0 >>> 0;
410
+ setAddr({ keypair: addrOffset }, info.leafAddr);
411
+ setAddr({ keypair: addrOffset }, info.pkAddr);
412
+ for (let i = 0; i < WOTS_LEN; i++) {
413
+ const wotsK = info.wotsSteps[i] | wotsKmask;
414
+ const pk = wotsPk.subarray(i * N, (i + 1) * N);
415
+ setAddr({ chain: i, hash: 0, type: AddressType.WOTSPRF }, info.leafAddr);
416
+ pk.set(rawContext.PRFaddr(info.leafAddr));
417
+ setAddr({ type: AddressType.WOTS }, info.leafAddr);
418
+ for (let k = 0; ; k++) {
419
+ if (k === wotsK) info.wotsSig.subarray(i * N).set(pk); //wotsSig.push()
420
+ if (k === W - 1) break;
421
+ setAddr({ hash: k }, info.leafAddr);
422
+ pk.set(rawContext.thash1(pk, info.leafAddr));
423
+ }
395
424
  }
425
+ return rawContext.thashN(WOTS_LEN, wotsPk, info.pkAddr);
396
426
  }
397
- return context.thashN(WOTS_LEN, wotsPk, info.pkAddr);
398
- });
427
+ );
399
428
 
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
- });
429
+ const forsTreehash = treehash(
430
+ A,
431
+ (_: number, addrOffset: number, context: TArg<Context>, forsLeafAddr: TArg<ForsLeafInfo>) => {
432
+ const rawContext = context as Context;
433
+ setAddr({ type: AddressType.FORSPRF, index: addrOffset }, forsLeafAddr);
434
+ const prf = rawContext.PRFaddr(forsLeafAddr);
435
+ setAddr({ type: AddressType.FORSTREE }, forsLeafAddr);
436
+ return rawContext.thash1(prf, forsLeafAddr);
437
+ }
438
+ );
406
439
 
407
440
  // Fuse `xmss_sign` with the subtree-root computation needed by `ht_sign`, so one tree walk
408
441
  // yields both the WOTS/auth-path signature and the root that the next hypertree layer signs.
409
442
  const merkleSign = (
410
- context: Context,
411
- wotsAddr: ADRS,
412
- treeAddr: ADRS,
443
+ context: TArg<Context>,
444
+ wotsAddr: TArg<ADRS>,
445
+ treeAddr: TArg<ADRS>,
413
446
  leafIdx: number,
414
- prevRoot: Uint8Array = new Uint8Array(N)
415
- ) => {
447
+ prevRoot: TArg<Uint8Array> = new Uint8Array(N)
448
+ ): TRet<{ root: Uint8Array; sigWots: Uint8Array; sigAuth: Uint8Array }> => {
416
449
  setAddr({ type: AddressType.HASHTREE }, treeAddr);
417
450
  // State variables
418
451
  const info = {
@@ -426,20 +459,21 @@ function gen(opts: SphincsOpts, hashOpts: SphincsHashOpts): SphincsSigner {
426
459
  root,
427
460
  sigWots: info.wotsSig.subarray(0, WOTS_LEN * N),
428
461
  sigAuth: authPath,
429
- };
462
+ } as TRet<{ root: Uint8Array; sigWots: Uint8Array; sigAuth: Uint8Array }>;
430
463
  };
431
464
 
432
465
  type ForsLeafInfo = ADRS;
433
466
 
434
467
  const computeRoot = (
435
- leaf: Uint8Array,
468
+ leaf: TArg<Uint8Array>,
436
469
  leafIdx: number,
437
470
  idxOffset: number,
438
- authPath: Uint8Array,
471
+ authPath: TArg<Uint8Array>,
439
472
  treeHeight: number,
440
- context: Context,
441
- addr: ADRS
473
+ context: TArg<Context>,
474
+ addr: TArg<ADRS>
442
475
  ) => {
476
+ const rawContext = context as Context;
443
477
  const buffer = new Uint8Array(2 * N);
444
478
  const b0 = buffer.subarray(0, N);
445
479
  const b1 = buffer.subarray(N, 2 * N);
@@ -462,16 +496,16 @@ function gen(opts: SphincsOpts, hashOpts: SphincsHashOpts): SphincsSigner {
462
496
  setAddr({ height: i + 1, index: leafIdx + idxOffset }, addr);
463
497
  const a = authPath.subarray((i + 1) * N, (i + 2) * N);
464
498
  if ((leafIdx & 1) !== 0) {
465
- b1.set(context.thashN(2, buffer, addr));
499
+ b1.set(rawContext.thashN(2, buffer, addr));
466
500
  b0.set(a);
467
501
  } else {
468
- buffer.set(context.thashN(2, buffer, addr));
502
+ buffer.set(rawContext.thashN(2, buffer, addr));
469
503
  b1.set(a);
470
504
  }
471
505
  }
472
506
  // Root
473
507
  setAddr({ height: treeHeight, index: leafIdx + idxOffset }, addr);
474
- return context.thashN(2, buffer, addr);
508
+ return rawContext.thashN(2, buffer, addr);
475
509
  };
476
510
 
477
511
  const seedCoder = splitCoder('seed', N, N, N);
@@ -480,16 +514,16 @@ function gen(opts: SphincsOpts, hashOpts: SphincsHashOpts): SphincsSigner {
480
514
  const forsCoder = vecCoder(splitCoder('fors', N, N * A), K);
481
515
  const wotsCoder = vecCoder(splitCoder('wots', WOTS_LEN * N, TREE_HEIGHT * N), D);
482
516
  const sigCoder = splitCoder('signature', N, forsCoder, wotsCoder); // random || fors || wots
483
- const internal: Signer = {
484
- info: { type: 'internal-slh-dsa' },
485
- lengths: {
517
+ const internal: TRet<Signer> = Object.freeze({
518
+ info: Object.freeze({ type: 'internal-slh-dsa' }),
519
+ lengths: Object.freeze({
486
520
  publicKey: publicCoder.bytesLen,
487
521
  secretKey: secretCoder.bytesLen,
488
522
  signature: sigCoder.bytesLen,
489
523
  seed: seedCoder.bytesLen,
490
524
  signRand: N,
491
- },
492
- keygen(seed?: Uint8Array) {
525
+ }),
526
+ keygen(seed?: TArg<Uint8Array>) {
493
527
  if (seed !== undefined) abytes(seed, seedCoder.bytesLen, 'seed');
494
528
  seed = seed === undefined ? randomBytes(seedCoder.bytesLen) : copyBytes(seed);
495
529
  // Set SK.seed, SK.prf, and PK.seed to random n-byte
@@ -504,13 +538,16 @@ function gen(opts: SphincsOpts, hashOpts: SphincsHashOpts): SphincsSigner {
504
538
  const secretKey = secretCoder.encode([secretSeed, secretPRF, publicKey]);
505
539
  context.clean();
506
540
  cleanBytes(secretSeed, secretPRF, root, wotsAddr, topTreeAddr);
507
- return { publicKey, secretKey };
541
+ return {
542
+ publicKey: publicKey as TRet<Uint8Array>,
543
+ secretKey: secretKey as TRet<Uint8Array>,
544
+ };
508
545
  },
509
- getPublicKey: (secretKey: Uint8Array) => {
546
+ getPublicKey: (secretKey: TArg<Uint8Array>): TRet<Uint8Array> => {
510
547
  const [_skSeed, _skPRF, pk] = secretCoder.decode(secretKey);
511
- return Uint8Array.from(pk);
548
+ return Uint8Array.from(pk) as TRet<Uint8Array>;
512
549
  },
513
- sign: (msg: Uint8Array, sk: Uint8Array, opts: SigOpts = {}) => {
550
+ sign: (msg: TArg<Uint8Array>, sk: TArg<Uint8Array>, opts: TArg<SigOpts> = {}) => {
514
551
  validateSigOpts(opts);
515
552
  let { extraEntropy: random } = opts;
516
553
  const [skSeed, skPRF, pk] = secretCoder.decode(sk); // todo: fix
@@ -581,9 +618,9 @@ function gen(opts: SphincsOpts, hashOpts: SphincsHashOpts): SphincsSigner {
581
618
  context.clean();
582
619
  const SIG = sigCoder.encode([R, fors, wots]);
583
620
  cleanBytes(R, random, treeAddr, wotsAddr, forsLeaf, forsTreeAddr, indices, roots);
584
- return SIG;
621
+ return SIG as TRet<Uint8Array>;
585
622
  },
586
- verify: (sig: Uint8Array, msg: Uint8Array, publicKey: Uint8Array) => {
623
+ verify: (sig: TArg<Uint8Array>, msg: TArg<Uint8Array>, publicKey: TArg<Uint8Array>) => {
587
624
  const [pkSeed, pubRoot] = publicCoder.decode(publicKey);
588
625
  const [random, forsVec, wotsVec] = sigCoder.decode(sig);
589
626
  const pk = publicKey;
@@ -642,78 +679,106 @@ function gen(opts: SphincsOpts, hashOpts: SphincsHashOpts): SphincsSigner {
642
679
  }
643
680
  return equalBytes(root, pubRoot);
644
681
  },
645
- };
646
- return {
647
- info: { type: 'slh-dsa' },
682
+ });
683
+ return Object.freeze({
684
+ info: Object.freeze({ type: 'slh-dsa' }),
648
685
  internal,
649
686
  securityLevel: securityLevel,
650
687
  lengths: internal.lengths,
651
688
  keygen: internal.keygen,
652
689
  getPublicKey: internal.getPublicKey,
653
- sign: (msg: Uint8Array, secretKey: Uint8Array, opts: SigOpts = {}) => {
690
+ sign: (msg: TArg<Uint8Array>, secretKey: TArg<Uint8Array>, opts: TArg<SigOpts> = {}) => {
654
691
  validateSigOpts(opts);
655
692
  const M = getMessage(msg, opts.context);
656
693
  const res = internal.sign(M, secretKey, opts);
657
694
  cleanBytes(M);
658
- return res;
695
+ return res as TRet<Uint8Array>;
659
696
  },
660
- verify: (sig: Uint8Array, msg: Uint8Array, publicKey: Uint8Array, opts: VerOpts = {}) => {
697
+ verify: (
698
+ sig: TArg<Uint8Array>,
699
+ msg: TArg<Uint8Array>,
700
+ publicKey: TArg<Uint8Array>,
701
+ opts: TArg<VerOpts> = {}
702
+ ) => {
661
703
  validateVerOpts(opts);
662
704
  return internal.verify(sig, getMessage(msg, opts.context), publicKey);
663
705
  },
664
- prehash: (hash: CHash) => {
665
- checkHash(hash, securityLevel);
666
- return {
667
- info: { type: 'hashslh-dsa' },
706
+ prehash: (hash: TArg<CHash>): TRet<Signer> => {
707
+ checkHash(hash as CHash, securityLevel);
708
+ const rawHash = hash as CHash;
709
+ return Object.freeze({
710
+ info: Object.freeze({ type: 'hashslh-dsa' }),
668
711
  lengths: internal.lengths,
669
712
  keygen: internal.keygen,
670
713
  getPublicKey: internal.getPublicKey,
671
- sign: (msg: Uint8Array, secretKey: Uint8Array, opts: SigOpts = {}) => {
714
+ sign: (msg: TArg<Uint8Array>, secretKey: TArg<Uint8Array>, opts: TArg<SigOpts> = {}) => {
672
715
  validateSigOpts(opts);
673
- const M = getMessagePrehash(hash, msg, opts.context);
716
+ const M = getMessagePrehash(rawHash, msg, opts.context);
674
717
  const res = internal.sign(M, secretKey, opts);
675
718
  cleanBytes(M);
676
- return res;
719
+ return res as TRet<Uint8Array>;
677
720
  },
678
- verify: (sig: Uint8Array, msg: Uint8Array, publicKey: Uint8Array, opts: VerOpts = {}) => {
721
+ verify: (
722
+ sig: TArg<Uint8Array>,
723
+ msg: TArg<Uint8Array>,
724
+ publicKey: TArg<Uint8Array>,
725
+ opts: TArg<VerOpts> = {}
726
+ ) => {
679
727
  validateVerOpts(opts);
680
- return internal.verify(sig, getMessagePrehash(hash, msg, opts.context), publicKey);
728
+ return internal.verify(sig, getMessagePrehash(rawHash, msg, opts.context), publicKey);
681
729
  },
682
- };
730
+ });
683
731
  },
684
- };
732
+ });
685
733
  }
686
734
 
687
735
  // FIPS 205 §11.1 SHAKE instantiation: this path hashes the full uncompressed address bytes,
688
736
  // unlike the compressed 22-byte SHA2 path in §11.2.
689
737
  const genShake =
690
- (): GetContext => (opts: SphincsOpts) => (pubSeed: Uint8Array, skSeed?: Uint8Array) => {
738
+ (): TRet<GetContext> =>
739
+ (opts: SphincsOpts) =>
740
+ (pubSeed: TArg<Uint8Array>, skSeed?: TArg<Uint8Array>): TRet<Context> => {
691
741
  const { N } = opts;
692
742
  const stats = { prf: 0, thash: 0, hmsg: 0, gen_message_random: 0 };
693
743
  // §11.1 prefixes PRF/F/H/T_l with `PK.seed`, so cache that absorbed prefix once and clone it
694
744
  // for each address-bound call instead of reabsorbing the same seed every time.
695
745
  const h0 = shake256.create({}).update(pubSeed);
696
746
  const h0tmp = h0.clone();
697
- const thash = (blocks: number, input: Uint8Array, addr: ADRS) => {
747
+ const thash = (blocks: number, input: TArg<Uint8Array>, addr: TArg<ADRS>): TRet<Uint8Array> => {
698
748
  stats.thash++;
699
749
  return h0
700
750
  ._cloneInto(h0tmp)
701
751
  .update(addr)
702
752
  .update(input.subarray(0, blocks * N))
703
- .xof(N);
753
+ .xof(N) as TRet<Uint8Array>;
704
754
  };
705
755
  return {
706
- PRFaddr: (addr: ADRS) => {
756
+ PRFaddr: (addr: TArg<ADRS>): TRet<Uint8Array> => {
707
757
  if (!skSeed) throw new Error('no sk seed');
708
758
  stats.prf++;
709
759
  const res = h0._cloneInto(h0tmp).update(addr).update(skSeed).xof(N);
710
- return res;
760
+ return res as TRet<Uint8Array>;
711
761
  },
712
- PRFmsg: (skPRF: Uint8Array, random: Uint8Array, msg: Uint8Array) => {
762
+ PRFmsg: (
763
+ skPRF: TArg<Uint8Array>,
764
+ random: TArg<Uint8Array>,
765
+ msg: TArg<Uint8Array>
766
+ ): TRet<Uint8Array> => {
713
767
  stats.gen_message_random++;
714
- return shake256.create({}).update(skPRF).update(random).update(msg).digest().subarray(0, N);
768
+ return shake256
769
+ .create({})
770
+ .update(skPRF)
771
+ .update(random)
772
+ .update(msg)
773
+ .digest()
774
+ .subarray(0, N) as TRet<Uint8Array>;
715
775
  },
716
- Hmsg: (R: Uint8Array, pk: Uint8Array, m: Uint8Array, outLen) => {
776
+ Hmsg: (
777
+ R: TArg<Uint8Array>,
778
+ pk: TArg<Uint8Array>,
779
+ m: TArg<Uint8Array>,
780
+ outLen
781
+ ): TRet<Uint8Array> => {
717
782
  stats.hmsg++;
718
783
  return shake256.create({}).update(R.subarray(0, N)).update(pk).update(m).xof(outLen);
719
784
  },
@@ -724,7 +789,7 @@ const genShake =
724
789
  h0tmp.destroy();
725
790
  //console.log(stats);
726
791
  },
727
- };
792
+ } as TRet<Context>;
728
793
  };
729
794
 
730
795
  const SHAKE_SIMPLE = /* @__PURE__ */ (() => ({ getContext: genShake() }))();
@@ -734,42 +799,42 @@ const SHAKE_SIMPLE = /* @__PURE__ */ (() => ({ getContext: genShake() }))();
734
799
  * lengths `publicKey=32`, `secretKey=64`, `signature=17088`, `seed=48`, `signRand=16`.
735
800
  * Also exposes `.prehash(...)`.
736
801
  */
737
- export const slh_dsa_shake_128f: SphincsSigner = /* @__PURE__ */ (() =>
802
+ export const slh_dsa_shake_128f: TRet<SphincsSigner> = /* @__PURE__ */ (() =>
738
803
  gen(PARAMS['128f'], SHAKE_SIMPLE))();
739
804
  /**
740
805
  * 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
806
  * lengths `publicKey=32`, `secretKey=64`, `signature=7856`, `seed=48`, `signRand=16`.
742
807
  * Also exposes `.prehash(...)`.
743
808
  */
744
- export const slh_dsa_shake_128s: SphincsSigner = /* @__PURE__ */ (() =>
809
+ export const slh_dsa_shake_128s: TRet<SphincsSigner> = /* @__PURE__ */ (() =>
745
810
  gen(PARAMS['128s'], SHAKE_SIMPLE))();
746
811
  /**
747
812
  * 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
813
  * lengths `publicKey=48`, `secretKey=96`, `signature=35664`, `seed=72`, `signRand=24`.
749
814
  * Also exposes `.prehash(...)`.
750
815
  */
751
- export const slh_dsa_shake_192f: SphincsSigner = /* @__PURE__ */ (() =>
816
+ export const slh_dsa_shake_192f: TRet<SphincsSigner> = /* @__PURE__ */ (() =>
752
817
  gen(PARAMS['192f'], SHAKE_SIMPLE))();
753
818
  /**
754
819
  * 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
820
  * lengths `publicKey=48`, `secretKey=96`, `signature=16224`, `seed=72`, `signRand=24`.
756
821
  * Also exposes `.prehash(...)`.
757
822
  */
758
- export const slh_dsa_shake_192s: SphincsSigner = /* @__PURE__ */ (() =>
823
+ export const slh_dsa_shake_192s: TRet<SphincsSigner> = /* @__PURE__ */ (() =>
759
824
  gen(PARAMS['192s'], SHAKE_SIMPLE))();
760
825
  /**
761
826
  * 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
827
  * lengths `publicKey=64`, `secretKey=128`, `signature=49856`, `seed=96`, `signRand=32`.
763
828
  * Also exposes `.prehash(...)`.
764
829
  */
765
- export const slh_dsa_shake_256f: SphincsSigner = /* @__PURE__ */ (() =>
830
+ export const slh_dsa_shake_256f: TRet<SphincsSigner> = /* @__PURE__ */ (() =>
766
831
  gen(PARAMS['256f'], SHAKE_SIMPLE))();
767
832
  /**
768
833
  * 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
834
  * lengths `publicKey=64`, `secretKey=128`, `signature=29792`, `seed=96`, `signRand=32`.
770
835
  * Also exposes `.prehash(...)`.
771
836
  */
772
- export const slh_dsa_shake_256s: SphincsSigner = /* @__PURE__ */ (() =>
837
+ export const slh_dsa_shake_256s: TRet<SphincsSigner> = /* @__PURE__ */ (() =>
773
838
  gen(PARAMS['256s'], SHAKE_SIMPLE))();
774
839
 
775
840
  type ShaType = typeof sha256 | typeof sha512;
@@ -777,9 +842,9 @@ type ShaType = typeof sha256 | typeof sha512;
777
842
  // category-1 keeps everything on SHA-256, while category-3/5 keep `PRFaddr` / `thash1`
778
843
  // on SHA-256 but switch `PRFmsg`, `Hmsg`, and multi-block `thashN` to SHA-512.
779
844
  const genSha =
780
- (h0: ShaType, h1: ShaType): GetContext =>
845
+ (h0: ShaType, h1: ShaType): TRet<GetContext> =>
781
846
  (opts) =>
782
- (pub_seed, sk_seed?) => {
847
+ (pub_seed: TArg<Uint8Array>, sk_seed?: TArg<Uint8Array>): TRet<Context> => {
783
848
  const { N } = opts;
784
849
  /*
785
850
  Perf debug stats, how much hashes we call?
@@ -809,7 +874,7 @@ const genSha =
809
874
  // This local helper is intentionally stricter than generic MGF1 reuse: current SLH-DSA callers
810
875
  // only request tiny `m`-byte outputs, but the guard below rejects `length > 2^32` instead of
811
876
  // RFC 8017's broader `maskLen > 2^32 * hLen` bound.
812
- function mgf1(seed: Uint8Array, length: number, hash: ShaType) {
877
+ function mgf1(seed: TArg<Uint8Array>, length: number, hash: ShaType): TRet<Uint8Array> {
813
878
  stats.mgf1++;
814
879
  const out = new Uint8Array(Math.ceil(length / hash.outputLen) * hash.outputLen);
815
880
  // NOT 2^32-1
@@ -820,22 +885,22 @@ const genSha =
820
885
  o = o.subarray(hash.outputLen);
821
886
  }
822
887
  cleanBytes(out.subarray(length));
823
- return out.subarray(0, length);
888
+ return out.subarray(0, length) as TRet<Uint8Array>;
824
889
  }
825
890
 
826
891
  const thash =
827
892
  (_: ShaType, h: typeof h0ps, hTmp: typeof h0ps) =>
828
- (blocks: number, input: Uint8Array, addr: ADRS) => {
893
+ (blocks: number, input: TArg<Uint8Array>, addr: TArg<ADRS>): TRet<Uint8Array> => {
829
894
  stats.thash++;
830
895
  const d = h
831
896
  ._cloneInto(hTmp as any)
832
897
  .update(addr)
833
898
  .update(input.subarray(0, blocks * N))
834
899
  .digest();
835
- return d.subarray(0, N);
900
+ return d.subarray(0, N) as TRet<Uint8Array>;
836
901
  };
837
902
  return {
838
- PRFaddr: (addr: ADRS) => {
903
+ PRFaddr: (addr: TArg<ADRS>): TRet<Uint8Array> => {
839
904
  if (!sk_seed) throw new Error('No sk seed');
840
905
  stats.prf++;
841
906
  const res = h0ps
@@ -844,13 +909,27 @@ const genSha =
844
909
  .update(sk_seed)
845
910
  .digest()
846
911
  .subarray(0, N);
847
- return res;
912
+ return res as TRet<Uint8Array>;
848
913
  },
849
- PRFmsg: (skPRF: Uint8Array, random: Uint8Array, msg: Uint8Array) => {
914
+ PRFmsg: (
915
+ skPRF: TArg<Uint8Array>,
916
+ random: TArg<Uint8Array>,
917
+ msg: TArg<Uint8Array>
918
+ ): TRet<Uint8Array> => {
850
919
  stats.gen_message_random++;
851
- return hmac.create(h1, skPRF).update(random).update(msg).digest().subarray(0, N);
920
+ return hmac
921
+ .create(h1, skPRF)
922
+ .update(random)
923
+ .update(msg)
924
+ .digest()
925
+ .subarray(0, N) as TRet<Uint8Array>;
852
926
  },
853
- Hmsg: (R: Uint8Array, pk: Uint8Array, m: Uint8Array, outLen) => {
927
+ Hmsg: (
928
+ R: TArg<Uint8Array>,
929
+ pk: TArg<Uint8Array>,
930
+ m: TArg<Uint8Array>,
931
+ outLen
932
+ ): TRet<Uint8Array> => {
854
933
  stats.hmsg++;
855
934
  const seed = concatBytes(
856
935
  R.subarray(0, N),
@@ -868,7 +947,7 @@ const genSha =
868
947
  h1tmp.destroy();
869
948
  //console.log(stats);
870
949
  },
871
- };
950
+ } as TRet<Context>;
872
951
  };
873
952
 
874
953
  const SHA256_SIMPLE = /* @__PURE__ */ (() => ({
@@ -885,40 +964,40 @@ const SHA512_SIMPLE = /* @__PURE__ */ (() => ({
885
964
  * lengths `publicKey=32`, `secretKey=64`, `signature=17088`, `seed=48`, `signRand=16`.
886
965
  * Also exposes `.prehash(...)`.
887
966
  */
888
- export const slh_dsa_sha2_128f: SphincsSigner = /* @__PURE__ */ (() =>
967
+ export const slh_dsa_sha2_128f: TRet<SphincsSigner> = /* @__PURE__ */ (() =>
889
968
  gen(PARAMS['128f'], SHA256_SIMPLE))();
890
969
  /**
891
970
  * 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
971
  * lengths `publicKey=32`, `secretKey=64`, `signature=7856`, `seed=48`, `signRand=16`.
893
972
  * Also exposes `.prehash(...)`.
894
973
  */
895
- export const slh_dsa_sha2_128s: SphincsSigner = /* @__PURE__ */ (() =>
974
+ export const slh_dsa_sha2_128s: TRet<SphincsSigner> = /* @__PURE__ */ (() =>
896
975
  gen(PARAMS['128s'], SHA256_SIMPLE))();
897
976
  /**
898
977
  * 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
978
  * lengths `publicKey=48`, `secretKey=96`, `signature=35664`, `seed=72`, `signRand=24`.
900
979
  * Also exposes `.prehash(...)`.
901
980
  */
902
- export const slh_dsa_sha2_192f: SphincsSigner = /* @__PURE__ */ (() =>
981
+ export const slh_dsa_sha2_192f: TRet<SphincsSigner> = /* @__PURE__ */ (() =>
903
982
  gen(PARAMS['192f'], SHA512_SIMPLE))();
904
983
  /**
905
984
  * 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
985
  * lengths `publicKey=48`, `secretKey=96`, `signature=16224`, `seed=72`, `signRand=24`.
907
986
  * Also exposes `.prehash(...)`.
908
987
  */
909
- export const slh_dsa_sha2_192s: SphincsSigner = /* @__PURE__ */ (() =>
988
+ export const slh_dsa_sha2_192s: TRet<SphincsSigner> = /* @__PURE__ */ (() =>
910
989
  gen(PARAMS['192s'], SHA512_SIMPLE))();
911
990
  /**
912
991
  * 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
992
  * lengths `publicKey=64`, `secretKey=128`, `signature=49856`, `seed=96`, `signRand=32`.
914
993
  * Also exposes `.prehash(...)`.
915
994
  */
916
- export const slh_dsa_sha2_256f: SphincsSigner = /* @__PURE__ */ (() =>
995
+ export const slh_dsa_sha2_256f: TRet<SphincsSigner> = /* @__PURE__ */ (() =>
917
996
  gen(PARAMS['256f'], SHA512_SIMPLE))();
918
997
  /**
919
998
  * 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
999
  * lengths `publicKey=64`, `secretKey=128`, `signature=29792`, `seed=96`, `signRand=32`.
921
1000
  * Also exposes `.prehash(...)`.
922
1001
  */
923
- export const slh_dsa_sha2_256s: SphincsSigner = /* @__PURE__ */ (() =>
1002
+ export const slh_dsa_sha2_256s: TRet<SphincsSigner> = /* @__PURE__ */ (() =>
924
1003
  gen(PARAMS['256s'], SHA512_SIMPLE))();