@noble/post-quantum 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
package/src/slh-dsa.ts ADDED
@@ -0,0 +1,771 @@
1
+ /*! noble-post-quantum - MIT License (c) 2024 Paul Miller (paulmillr.com) */
2
+ import { HMAC } from '@noble/hashes/hmac';
3
+ import { sha256, sha512 } from '@noble/hashes/sha2';
4
+ import { shake256 } from '@noble/hashes/sha3';
5
+ import { bytesToHex, hexToBytes, createView, concatBytes, u32 } from '@noble/hashes/utils';
6
+ import {
7
+ Signer,
8
+ cleanBytes,
9
+ ensureBytes,
10
+ equalBytes,
11
+ getMask,
12
+ randomBytes,
13
+ splitCoder,
14
+ vecCoder,
15
+ } from './utils.js';
16
+
17
+ /*
18
+ Hash-based digital signature algorithm. See [official site](https://sphincs.org).
19
+ We implement spec v3.1 with latest FIPS-205 changes.
20
+ It's compatible with the latest version in the [official repo](https://github.com/sphincs/sphincsplus).
21
+
22
+ Three versions are provided:
23
+
24
+ 1. SHAKE256-based
25
+ 2. SHA2-based
26
+ 3. SLH-DSA aka [FIPS-205](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.205.ipd.pdf)
27
+ */
28
+
29
+ /*
30
+ WOTS: One-time signatures (can be forged if same key used twice)
31
+ FORS: Forest of Random Subsets
32
+
33
+ Hashes are like signatures. You take private key, hash it, and share the result pubKey.
34
+ After that you can verify it was yours by also sharing the private key.
35
+ However, it will only work once: after pre-image was disclosed, it can't be used again.
36
+ It also doesn't sign the message: can be interceptd and message can be replaced.
37
+
38
+ How to solve "one-time" hashing? Instead of hash(k), we can provide merkle tree root hash:
39
+
40
+ h(h(h(0) || h(1)) || h(h(2) || h(3))))
41
+
42
+ Now, we have the same pubKey output of hash, but disclosing one path in tree doesn't
43
+ invalidate the others, since they are still unknown. By choosing path which is related
44
+ to the message, we can "sign" it.
45
+
46
+ There is a limitation: only a fixed amount of signatures can be made,
47
+ a merkle tree with depth: 8 would mean 2**8 (256) paths aka 256 distinct messages.
48
+ Attaching a different tree to each node will solve forgeries, but the key would still degrade.
49
+ */
50
+
51
+ /**
52
+ * * N: Security parameter (in bytes). W: Winternitz parameter
53
+ * * H: Hypertree height. D: Hypertree layers
54
+ * * K: FORS trees numbers. A: FORS trees height
55
+ */
56
+ export type SphincsOpts = {
57
+ N: number;
58
+ W: number;
59
+ H: number;
60
+ D: number;
61
+ K: number;
62
+ A: number;
63
+ };
64
+
65
+ export type SphincsHashOpts = {
66
+ isCompressed?: boolean;
67
+ getContext: GetContext;
68
+ };
69
+
70
+ export const PARAMS: Record<string, SphincsOpts> = {
71
+ '128f': { W: 16, N: 16, H: 66, D: 22, K: 33, A: 6 },
72
+ '128s': { W: 16, N: 16, H: 63, D: 7, K: 14, A: 12 },
73
+ '192f': { W: 16, N: 24, H: 66, D: 22, K: 33, A: 8 },
74
+ '192s': { W: 16, N: 24, H: 63, D: 7, K: 17, A: 14 },
75
+ '256f': { W: 16, N: 32, H: 68, D: 17, K: 35, A: 9 },
76
+ '256s': { W: 16, N: 32, H: 64, D: 8, K: 22, A: 14 },
77
+ } as const;
78
+
79
+ const enum AddressType {
80
+ WOTS,
81
+ WOTSPK,
82
+ HASHTREE,
83
+ FORSTREE,
84
+ FORSPK,
85
+ WOTSPRF,
86
+ FORSPRF,
87
+ }
88
+
89
+ export type ADRS = Uint8Array;
90
+
91
+ type Context = {
92
+ PRFaddr: (addr: ADRS) => Uint8Array;
93
+ PRFmsg: (skPRF: Uint8Array, random: Uint8Array, msg: Uint8Array) => Uint8Array;
94
+ Hmsg: (R: Uint8Array, pk: Uint8Array, m: Uint8Array, outLen: number) => Uint8Array;
95
+ thash1: (input: Uint8Array, addr: ADRS) => Uint8Array;
96
+ thashN: (blocks: number, input: Uint8Array, addr: ADRS) => Uint8Array;
97
+ clean: () => void;
98
+ };
99
+ export type GetContext = (
100
+ opts: SphincsOpts
101
+ ) => (pub_seed: Uint8Array, sk_seed?: Uint8Array) => Context;
102
+
103
+ function hexToNumber(hex: string): bigint {
104
+ if (typeof hex !== 'string') throw new Error('hex string expected, got ' + typeof hex);
105
+ // Big Endian
106
+ return BigInt(hex === '' ? '0' : `0x${hex}`);
107
+ }
108
+
109
+ // BE: Big Endian, LE: Little Endian
110
+ function bytesToNumberBE(bytes: Uint8Array): bigint {
111
+ return hexToNumber(bytesToHex(bytes));
112
+ }
113
+
114
+ function numberToBytesBE(n: number | bigint, len: number): Uint8Array {
115
+ return hexToBytes(n.toString(16).padStart(len * 2, '0'));
116
+ }
117
+
118
+ // Same as bitsCoder.decode, but bits are BE instead of LE (so we cannot re-use it).
119
+ // NOTE: difference happens only if d < 8.
120
+ const base_2bBE = (N: number, d: number) => {
121
+ const mask = getMask(d);
122
+ return (bytes: Uint8Array) => {
123
+ const r = new Uint32Array(N);
124
+ for (let i = 0, buf = 0, bufLen = 0, pos = 0; i < bytes.length; i++) {
125
+ buf |= bytes[i] << bufLen;
126
+ bufLen += 8;
127
+ for (; bufLen >= d; bufLen -= d) r[pos++] = (buf >>> (bufLen - d)) & mask;
128
+ buf &= getMask(bufLen);
129
+ }
130
+ return r;
131
+ };
132
+ };
133
+ // Same as bitsCoder.decode, but maybe spec will change and unify with base2bBE.
134
+ const base_2bLE = (N: number, d: number) => {
135
+ const mask = getMask(d);
136
+ return (bytes: Uint8Array) => {
137
+ const r = new Uint32Array(N);
138
+ for (let i = 0, buf = 0, bufLen = 0, pos = 0; i < bytes.length; i++) {
139
+ buf |= bytes[i] << bufLen;
140
+ bufLen += 8;
141
+ for (; bufLen >= d; bufLen -= d, buf >>= d) r[pos++] = buf & mask;
142
+ }
143
+ return r;
144
+ };
145
+ };
146
+
147
+ function getMaskBig(bits: number) {
148
+ return (1n << BigInt(bits)) - 1n; // 4 -> 0b1111
149
+ }
150
+
151
+ type SphincsSigner = Signer & { seedLen: number };
152
+
153
+ function gen(opts: SphincsOpts, hashOpts: SphincsHashOpts): SphincsSigner {
154
+ const { N, W, H, D, K, A } = opts;
155
+ const getContext = hashOpts.getContext(opts);
156
+ if (W !== 16) throw new Error('Unsupported Winternitz parameter');
157
+ const WOTS_LOGW = 4;
158
+ const WOTS_LEN1 = Math.floor((8 * N) / WOTS_LOGW);
159
+ const WOTS_LEN2 = N <= 8 ? 2 : N <= 136 ? 3 : 4;
160
+ const TREE_HEIGHT = Math.floor(H / D);
161
+ const WOTS_LEN = WOTS_LEN1 + WOTS_LEN2;
162
+
163
+ let ADDR_BYTES = 22;
164
+ let OFFSET_LAYER = 0;
165
+ let OFFSET_TREE = 1;
166
+ let OFFSET_TYPE = 9;
167
+ let OFFSET_KP_ADDR2 = 12;
168
+ let OFFSET_KP_ADDR1 = 13;
169
+ let OFFSET_CHAIN_ADDR = 17;
170
+ let OFFSET_TREE_INDEX = 18;
171
+ let OFFSET_HASH_ADDR = 21;
172
+ if (!hashOpts.isCompressed) {
173
+ ADDR_BYTES = 32;
174
+ OFFSET_LAYER += 3;
175
+ OFFSET_TREE += 7;
176
+ OFFSET_TYPE += 10;
177
+ OFFSET_KP_ADDR2 += 10;
178
+ OFFSET_KP_ADDR1 += 10;
179
+ OFFSET_CHAIN_ADDR += 10;
180
+ OFFSET_TREE_INDEX += 10;
181
+ OFFSET_HASH_ADDR += 10;
182
+ }
183
+
184
+ const setAddr = (
185
+ opts: {
186
+ type?: AddressType;
187
+ height?: number;
188
+ tree?: bigint;
189
+ index?: number;
190
+ layer?: number;
191
+ chain?: number;
192
+ hash?: number;
193
+ keypair?: number;
194
+ subtreeAddr?: ADRS;
195
+ keypairAddr?: ADRS;
196
+ },
197
+ addr: ADRS = new Uint8Array(ADDR_BYTES)
198
+ ) => {
199
+ const { type, height, tree, layer, index, chain, hash, keypair } = opts;
200
+ const { subtreeAddr, keypairAddr } = opts;
201
+ const v = createView(addr);
202
+
203
+ if (height !== undefined) addr[OFFSET_CHAIN_ADDR] = height;
204
+ if (layer !== undefined) addr[OFFSET_LAYER] = layer;
205
+ if (type !== undefined) addr[OFFSET_TYPE] = type;
206
+ if (chain !== undefined) addr[OFFSET_CHAIN_ADDR] = chain;
207
+ if (hash !== undefined) addr[OFFSET_HASH_ADDR] = hash;
208
+ if (index !== undefined) v.setUint32(OFFSET_TREE_INDEX, index, false);
209
+ if (subtreeAddr) addr.set(subtreeAddr.subarray(0, OFFSET_TREE + 8));
210
+ if (tree !== undefined) v.setBigUint64(OFFSET_TREE, tree, false);
211
+ if (keypair !== undefined) {
212
+ addr[OFFSET_KP_ADDR1] = keypair;
213
+ if (TREE_HEIGHT > 8) addr[OFFSET_KP_ADDR2] = keypair >>> 8;
214
+ }
215
+ if (keypairAddr) {
216
+ addr.set(keypairAddr.subarray(0, OFFSET_TREE + 8));
217
+ addr[OFFSET_KP_ADDR1] = keypairAddr[OFFSET_KP_ADDR1];
218
+ if (TREE_HEIGHT > 8) addr[OFFSET_KP_ADDR2] = keypairAddr[OFFSET_KP_ADDR2];
219
+ }
220
+ return addr;
221
+ };
222
+
223
+ const chainCoder = base_2bBE(WOTS_LEN2, WOTS_LOGW);
224
+ const chainLengths = (msg: Uint8Array) => {
225
+ const W1 = base_2bBE(WOTS_LEN1, WOTS_LOGW)(msg);
226
+ let csum = 0;
227
+ for (let i = 0; i < W1.length; i++) csum += W - 1 - W1[i]; // ▷ Compute checksum
228
+ csum <<= (8 - ((WOTS_LEN2 * WOTS_LOGW) % 8)) % 8; // csum ← csum ≪ ((8 − ((len2 · lg(w)) mod 8)) mod 8
229
+ // Checksum to base(LOG_W)
230
+ const W2 = chainCoder(numberToBytesBE(csum, Math.ceil((WOTS_LEN2 * WOTS_LOGW) / 8)));
231
+ // W1 || W2 (concatBytes cannot concat TypedArrays)
232
+ const lengths = new Uint32Array(WOTS_LEN);
233
+ lengths.set(W1);
234
+ lengths.set(W2, W1.length);
235
+ return lengths;
236
+ };
237
+ // Hm, why BE vs LE?
238
+ const msgCoder = base_2bLE(K, A);
239
+ const messageToIndices = (msg: Uint8Array) => msgCoder(msg);
240
+
241
+ const TREE_BITS = TREE_HEIGHT * (D - 1);
242
+ const LEAF_BITS = TREE_HEIGHT;
243
+ const hashMsgCoder = splitCoder(
244
+ Math.ceil((A * K) / 8),
245
+ Math.ceil(TREE_BITS / 8),
246
+ Math.ceil(TREE_HEIGHT / 8)
247
+ );
248
+ const hashMessage = (R: Uint8Array, pkSeed: Uint8Array, msg: Uint8Array, context: Context) => {
249
+ const digest = context.Hmsg(R, pkSeed, msg, hashMsgCoder.bytesLen); // digest ← Hmsg(R, PK.seed, PK.root, M)
250
+ const [md, tmpIdxTree, tmpIdxLeaf] = hashMsgCoder.decode(digest);
251
+ const tree = bytesToNumberBE(tmpIdxTree) & getMaskBig(TREE_BITS);
252
+ const leafIdx = Number(bytesToNumberBE(tmpIdxLeaf)) & getMask(LEAF_BITS);
253
+ return { tree, leafIdx, md };
254
+ };
255
+
256
+ const treehash = <T>(
257
+ height: number,
258
+ fn: (leafIdx: number, addrOffset: number, context: Context, info: T) => Uint8Array
259
+ ) =>
260
+ function treehash_i(
261
+ context: Context,
262
+ leafIdx: number,
263
+ idxOffset: number,
264
+ treeAddr: ADRS,
265
+ info: T
266
+ ) {
267
+ const maxIdx = (1 << height) - 1;
268
+ const stack = new Uint8Array(height * N);
269
+ const authPath = new Uint8Array(height * N);
270
+ for (let idx = 0; ; idx++) {
271
+ const current = new Uint8Array(2 * N);
272
+ const cur0 = current.subarray(0, N);
273
+ const cur1 = current.subarray(N);
274
+ const addrOffset = idx + idxOffset;
275
+ cur1.set(fn(leafIdx, addrOffset, context, info));
276
+ let h = 0;
277
+ for (let i = idx, o = idxOffset, l = leafIdx; ; h++, i >>>= 1, l >>>= 1, o >>>= 1) {
278
+ if (h === height) return { root: cur1, authPath }; // Returns from here
279
+ if ((i ^ l) === 1) authPath.subarray(h * N).set(cur1); // authPath.push(cur1)
280
+ if ((i & 1) === 0 && idx < maxIdx) break;
281
+ setAddr({ height: h + 1, index: (i >> 1) + (o >> 1) }, treeAddr);
282
+ cur0.set(stack.subarray(h * N).subarray(0, N));
283
+ cur1.set(context.thashN(2, current, treeAddr));
284
+ }
285
+ stack.subarray(h * N).set(cur1); // stack.push(cur1)
286
+ }
287
+ // @ts-ignore
288
+ throw new Error('Unreachable code path reached, report this error');
289
+ };
290
+
291
+ type LeafInfo = {
292
+ wotsSig: Uint8Array;
293
+ wotsSteps: Uint32Array;
294
+ leafAddr: ADRS;
295
+ pkAddr: ADRS;
296
+ };
297
+ const wotsTreehash = treehash(TREE_HEIGHT, (leafIdx, addrOffset, context, info: LeafInfo) => {
298
+ const wotsPk = new Uint8Array(WOTS_LEN * N);
299
+ const wotsKmask = addrOffset === leafIdx ? 0 : ~0 >>> 0;
300
+ setAddr({ keypair: addrOffset }, info.leafAddr);
301
+ setAddr({ keypair: addrOffset }, info.pkAddr);
302
+ for (let i = 0; i < WOTS_LEN; i++) {
303
+ const wotsK = info.wotsSteps[i] | wotsKmask;
304
+ const pk = wotsPk.subarray(i * N, (i + 1) * N);
305
+ setAddr({ chain: i, hash: 0, type: AddressType.WOTSPRF }, info.leafAddr);
306
+ pk.set(context.PRFaddr(info.leafAddr));
307
+ setAddr({ type: AddressType.WOTS }, info.leafAddr);
308
+ for (let k = 0; ; k++) {
309
+ if (k === wotsK) info.wotsSig.subarray(i * N).set(pk); //wotsSig.push()
310
+ if (k === W - 1) break;
311
+ setAddr({ hash: k }, info.leafAddr);
312
+ pk.set(context.thash1(pk, info.leafAddr));
313
+ }
314
+ }
315
+ return context.thashN(WOTS_LEN, wotsPk, info.pkAddr);
316
+ });
317
+
318
+ const forsTreehash = treehash(A, (_, addrOffset, context, forsLeafAddr: ForsLeafInfo) => {
319
+ setAddr({ type: AddressType.FORSPRF, index: addrOffset }, forsLeafAddr);
320
+ const prf = context.PRFaddr(forsLeafAddr);
321
+ setAddr({ type: AddressType.FORSTREE }, forsLeafAddr);
322
+ return context.thash1(prf, forsLeafAddr);
323
+ });
324
+
325
+ const merkleSign = (
326
+ context: Context,
327
+ wotsAddr: ADRS,
328
+ treeAddr: ADRS,
329
+ leafIdx: number,
330
+ prevRoot = new Uint8Array(N)
331
+ ) => {
332
+ setAddr({ type: AddressType.HASHTREE }, treeAddr);
333
+ // State variables
334
+ const info = {
335
+ wotsSig: new Uint8Array(wotsCoder.bytesLen),
336
+ wotsSteps: chainLengths(prevRoot),
337
+ leafAddr: setAddr({ subtreeAddr: wotsAddr }),
338
+ pkAddr: setAddr({ type: AddressType.WOTSPK, subtreeAddr: wotsAddr }),
339
+ };
340
+ const { root, authPath } = wotsTreehash(context, leafIdx, 0, treeAddr, info);
341
+ return {
342
+ root,
343
+ sigWots: info.wotsSig.subarray(0, WOTS_LEN * N),
344
+ sigAuth: authPath,
345
+ };
346
+ };
347
+
348
+ type ForsLeafInfo = ADRS;
349
+
350
+ const computeRoot = (
351
+ leaf: Uint8Array,
352
+ leafIdx: number,
353
+ idxOffset: number,
354
+ authPath: Uint8Array,
355
+ treeHeight: number,
356
+ context: Context,
357
+ addr: ADRS
358
+ ) => {
359
+ const buffer = new Uint8Array(2 * N);
360
+ const b0 = buffer.subarray(0, N);
361
+ const b1 = buffer.subarray(N, 2 * N);
362
+ // First iter
363
+ if ((leafIdx & 1) !== 0) {
364
+ b1.set(leaf.subarray(0, N));
365
+ b0.set(authPath.subarray(0, N));
366
+ } else {
367
+ b0.set(leaf.subarray(0, N));
368
+ b1.set(authPath.subarray(0, N));
369
+ }
370
+ leafIdx >>>= 1;
371
+ idxOffset >>>= 1;
372
+ // Rest
373
+ for (let i = 0; i < treeHeight - 1; i++, leafIdx >>= 1, idxOffset >>= 1) {
374
+ setAddr({ height: i + 1, index: leafIdx + idxOffset }, addr);
375
+ const a = authPath.subarray((i + 1) * N, (i + 2) * N);
376
+ if ((leafIdx & 1) !== 0) {
377
+ b1.set(context.thashN(2, buffer, addr));
378
+ b0.set(a);
379
+ } else {
380
+ buffer.set(context.thashN(2, buffer, addr));
381
+ b1.set(a);
382
+ }
383
+ }
384
+ // Root
385
+ setAddr({ height: treeHeight, index: leafIdx + idxOffset }, addr);
386
+ return context.thashN(2, buffer, addr);
387
+ };
388
+
389
+ const seedCoder = splitCoder(N, N, N);
390
+ const publicCoder = splitCoder(N, N);
391
+ const secretCoder = splitCoder(N, N, publicCoder.bytesLen);
392
+ const forsCoder = vecCoder(splitCoder(N, N * A), K);
393
+ const wotsCoder = vecCoder(splitCoder(WOTS_LEN * N, TREE_HEIGHT * N), D);
394
+ const sigCoder = splitCoder(N, forsCoder, wotsCoder); // random || fors || wots
395
+ return {
396
+ seedLen: seedCoder.bytesLen,
397
+ signRandBytes: N,
398
+ keygen(seed = randomBytes(seedCoder.bytesLen)) {
399
+ // Set SK.seed, SK.prf, and PK.seed to random n-byte
400
+ const [secretSeed, secretPRF, publicSeed] = seedCoder.decode(seed);
401
+ const context = getContext(publicSeed, secretSeed);
402
+ // ADRS.setLayerAddress(d − 1)
403
+ const topTreeAddr = setAddr({ layer: D - 1 });
404
+ const wotsAddr = setAddr({ layer: D - 1 });
405
+ //PK.root ←_xmss node(SK.seed, 0, h′, PK.seed, ADRS)
406
+ const { root } = merkleSign(context, wotsAddr, topTreeAddr, ~0 >>> 0);
407
+ const publicKey = publicCoder.encode([publicSeed, root]);
408
+ const secretKey = secretCoder.encode([secretSeed, secretPRF, publicKey]);
409
+ context.clean();
410
+ cleanBytes(secretSeed, secretPRF, root, wotsAddr, topTreeAddr);
411
+ return { publicKey, secretKey };
412
+ },
413
+ sign: (sk: Uint8Array, msg: Uint8Array, random?: Uint8Array) => {
414
+ const [skSeed, skPRF, pk] = secretCoder.decode(sk); // todo: fix
415
+ const [pkSeed, _] = publicCoder.decode(pk);
416
+ // Set opt_rand to either PK.seed or to a random n-byte string
417
+ if (!random) random = pkSeed.slice();
418
+ ensureBytes(random, N);
419
+ const context = getContext(pkSeed, skSeed);
420
+ // Generate randomizer
421
+ const R = context.PRFmsg(skPRF, random, msg); // R ← PRFmsg(SK.prf, opt_rand, M)
422
+ let { tree, leafIdx, md } = hashMessage(R, pk, msg, context);
423
+ // Create FORS signatures
424
+ const wotsAddr = setAddr({
425
+ type: AddressType.WOTS,
426
+ tree,
427
+ keypair: leafIdx,
428
+ });
429
+ const roots = [];
430
+ const forsLeaf = setAddr({ keypairAddr: wotsAddr });
431
+ const forsTreeAddr = setAddr({ keypairAddr: wotsAddr });
432
+ const indices = messageToIndices(md);
433
+ const fors: [Uint8Array, Uint8Array][] = [];
434
+ for (let i = 0; i < indices.length; i++) {
435
+ const idxOffset = i << A;
436
+ setAddr(
437
+ {
438
+ type: AddressType.FORSPRF,
439
+ height: 0,
440
+ index: indices[i] + idxOffset,
441
+ },
442
+ forsTreeAddr
443
+ );
444
+ const prf = context.PRFaddr(forsTreeAddr);
445
+ setAddr({ type: AddressType.FORSTREE }, forsTreeAddr);
446
+ const { root, authPath } = forsTreehash(
447
+ context,
448
+ indices[i],
449
+ idxOffset,
450
+ forsTreeAddr,
451
+ forsLeaf
452
+ );
453
+ roots.push(root);
454
+ fors.push([prf, authPath]);
455
+ }
456
+ const forsPkAddr = setAddr({
457
+ type: AddressType.FORSPK,
458
+ keypairAddr: wotsAddr,
459
+ });
460
+ const root = context.thashN(K, concatBytes(...roots), forsPkAddr);
461
+ // WOTS signatures
462
+ const treeAddr = setAddr({ type: AddressType.HASHTREE });
463
+ const wots: [Uint8Array, Uint8Array][] = [];
464
+ for (let i = 0; i < D; i++, tree >>= BigInt(TREE_HEIGHT)) {
465
+ setAddr({ tree, layer: i }, treeAddr);
466
+ setAddr({ subtreeAddr: treeAddr, keypair: leafIdx }, wotsAddr);
467
+ const {
468
+ sigWots,
469
+ sigAuth,
470
+ root: r,
471
+ } = merkleSign(context, wotsAddr, treeAddr, leafIdx, root);
472
+ root.set(r);
473
+ r.fill(0);
474
+ wots.push([sigWots, sigAuth]);
475
+ leafIdx = Number(tree & getMaskBig(TREE_HEIGHT));
476
+ }
477
+ context.clean();
478
+ const SIG = sigCoder.encode([R, fors, wots]);
479
+ cleanBytes(R, random, treeAddr, wotsAddr, forsLeaf, forsTreeAddr, indices, roots);
480
+ return SIG;
481
+ },
482
+ verify: (publicKey: Uint8Array, msg: Uint8Array, sig: Uint8Array) => {
483
+ const [pkSeed, pubRoot] = publicCoder.decode(publicKey);
484
+ const [random, forsVec, wotsVec] = sigCoder.decode(sig);
485
+ const pk = publicKey;
486
+ if (sig.length !== sigCoder.bytesLen) return false;
487
+ const context = getContext(pkSeed);
488
+ let { tree, leafIdx, md } = hashMessage(random, pk, msg, context);
489
+ const wotsAddr = setAddr({
490
+ type: AddressType.WOTS,
491
+ tree,
492
+ keypair: leafIdx,
493
+ });
494
+ // FORS signature
495
+ const roots = [];
496
+ const forsTreeAddr = setAddr({
497
+ type: AddressType.FORSTREE,
498
+ keypairAddr: wotsAddr,
499
+ });
500
+ const indices = messageToIndices(md);
501
+ for (let i = 0; i < forsVec.length; i++) {
502
+ const [prf, authPath] = forsVec[i];
503
+ const idxOffset = i << A;
504
+ setAddr({ height: 0, index: indices[i] + idxOffset }, forsTreeAddr);
505
+ const leaf = context.thash1(prf, forsTreeAddr);
506
+ // Compute inplace, because we need all roots in same byte array
507
+ roots.push(computeRoot(leaf, indices[i], idxOffset, authPath, A, context, forsTreeAddr));
508
+ }
509
+ const forsPkAddr = setAddr({
510
+ type: AddressType.FORSPK,
511
+ keypairAddr: wotsAddr,
512
+ });
513
+ let root = context.thashN(K, concatBytes(...roots), forsPkAddr); // root = thash()
514
+ // WOTS signature
515
+ const treeAddr = setAddr({ type: AddressType.HASHTREE });
516
+ const wotsPkAddr = setAddr({ type: AddressType.WOTSPK });
517
+ const wotsPk = new Uint8Array(WOTS_LEN * N);
518
+ for (let i = 0; i < wotsVec.length; i++, tree >>= BigInt(TREE_HEIGHT)) {
519
+ const [wots, sigAuth] = wotsVec[i];
520
+ setAddr({ tree, layer: i }, treeAddr);
521
+ setAddr({ subtreeAddr: treeAddr, keypair: leafIdx }, wotsAddr);
522
+ setAddr({ keypairAddr: wotsAddr }, wotsPkAddr);
523
+ const lengths = chainLengths(root);
524
+ for (let i = 0; i < WOTS_LEN; i++) {
525
+ setAddr({ chain: i }, wotsAddr);
526
+ const steps = W - 1 - lengths[i];
527
+ const start = lengths[i];
528
+ const out = wotsPk.subarray(i * N);
529
+ out.set(wots.subarray(i * N, (i + 1) * N));
530
+ for (let j = start; j < start + steps && j < W; j++) {
531
+ setAddr({ hash: j }, wotsAddr);
532
+ out.set(context.thash1(out, wotsAddr));
533
+ }
534
+ }
535
+ const leaf = context.thashN(WOTS_LEN, wotsPk, wotsPkAddr);
536
+ root = computeRoot(leaf, leafIdx, 0, sigAuth, TREE_HEIGHT, context, treeAddr);
537
+ leafIdx = Number(tree & getMaskBig(TREE_HEIGHT));
538
+ }
539
+ return equalBytes(root, pubRoot);
540
+ },
541
+ };
542
+ }
543
+
544
+ const genShake =
545
+ (robust: boolean): GetContext =>
546
+ (opts: SphincsOpts) =>
547
+ (pubSeed: Uint8Array, skSeed?: Uint8Array) => {
548
+ const ADDR_BYTES = 32;
549
+ const { N } = opts;
550
+ const stats = { prf: 0, thash: 0, hmsg: 0, gen_message_random: 0 };
551
+ const h0 = shake256.create({}).update(pubSeed);
552
+ const h0tmp = h0.clone();
553
+ const thash_simple = (blocks: number, input: Uint8Array, addr: ADRS) => {
554
+ stats.thash++;
555
+ return h0
556
+ ._cloneInto(h0tmp)
557
+ .update(addr)
558
+ .update(input.subarray(0, blocks * N))
559
+ .xof(N);
560
+ };
561
+ const thash_robust = (blocks: number, input: Uint8Array, addr: ADRS) => {
562
+ stats.thash++;
563
+ const buf = new Uint8Array(ADDR_BYTES + (blocks + 1) * N);
564
+ buf.subarray(0, N).set(pubSeed);
565
+ buf.subarray(N, N + ADDR_BYTES).set(addr);
566
+ shake256
567
+ .create({})
568
+ .update(buf.subarray(0, N + ADDR_BYTES))
569
+ .xofInto(buf.subarray(N + ADDR_BYTES));
570
+ for (let i = 0; i < blocks * N; i++) buf[N + ADDR_BYTES + i] ^= input[i];
571
+ return shake256.create({}).update(buf).xof(N);
572
+ };
573
+ const thash = robust ? thash_robust : thash_simple;
574
+ return {
575
+ PRFaddr: (addr: ADRS) => {
576
+ if (!skSeed) throw new Error('no sk seed');
577
+ stats.prf++;
578
+ return h0._cloneInto(h0tmp).update(addr).update(skSeed).xof(N);
579
+ },
580
+ PRFmsg: (skPRF: Uint8Array, random: Uint8Array, msg: Uint8Array) => {
581
+ stats.gen_message_random++;
582
+ return shake256.create({}).update(skPRF).update(random).update(msg).digest().subarray(0, N);
583
+ },
584
+ Hmsg: (R: Uint8Array, pk: Uint8Array, m: Uint8Array, outLen) => {
585
+ stats.hmsg++;
586
+ return shake256.create({}).update(R.subarray(0, N)).update(pk).update(m).xof(outLen);
587
+ },
588
+ thash1: thash.bind(null, 1),
589
+ thashN: thash,
590
+ clean: () => {
591
+ h0.destroy();
592
+ h0tmp.destroy();
593
+ //console.log(stats);
594
+ },
595
+ };
596
+ };
597
+
598
+ const SHAKE_SIMPLE = { getContext: genShake(false) };
599
+ const SHAKE_ROBUST = { getContext: genShake(true) };
600
+
601
+ export const sphincs_shake_128f_simple = /* @__PURE__ */ gen(PARAMS['128f'], SHAKE_SIMPLE);
602
+ export const sphincs_shake_128f_robust = /* @__PURE__ */ gen(PARAMS['128f'], SHAKE_ROBUST);
603
+ export const sphincs_shake_128s_simple = /* @__PURE__ */ gen(PARAMS['128s'], SHAKE_SIMPLE);
604
+ export const sphincs_shake_128s_robust = /* @__PURE__ */ gen(PARAMS['128s'], SHAKE_ROBUST);
605
+ export const sphincs_shake_192f_simple = /* @__PURE__ */ gen(PARAMS['192f'], SHAKE_SIMPLE);
606
+ export const sphincs_shake_192f_robust = /* @__PURE__ */ gen(PARAMS['192f'], SHAKE_ROBUST);
607
+ export const sphincs_shake_192s_simple = /* @__PURE__ */ gen(PARAMS['192s'], SHAKE_SIMPLE);
608
+ export const sphincs_shake_192s_robust = /* @__PURE__ */ gen(PARAMS['192s'], SHAKE_ROBUST);
609
+ export const sphincs_shake_256f_simple = /* @__PURE__ */ gen(PARAMS['256f'], SHAKE_SIMPLE);
610
+ export const sphincs_shake_256f_robust = /* @__PURE__ */ gen(PARAMS['256f'], SHAKE_ROBUST);
611
+ export const sphincs_shake_256s_simple = /* @__PURE__ */ gen(PARAMS['256s'], SHAKE_SIMPLE);
612
+ export const sphincs_shake_256s_robust = /* @__PURE__ */ gen(PARAMS['256s'], SHAKE_ROBUST);
613
+
614
+ // Only simple mode in SLH-DSA
615
+ export const slh_dsa_shake_128f = /* @__PURE__ */ gen(PARAMS['128f'], SHAKE_SIMPLE);
616
+ export const slh_dsa_shake_128s = /* @__PURE__ */ gen(PARAMS['128s'], SHAKE_SIMPLE);
617
+ export const slh_dsa_shake_192f = /* @__PURE__ */ gen(PARAMS['192f'], SHAKE_SIMPLE);
618
+ export const slh_dsa_shake_192s = /* @__PURE__ */ gen(PARAMS['192s'], SHAKE_SIMPLE);
619
+ export const slh_dsa_shake_256f = /* @__PURE__ */ gen(PARAMS['256f'], SHAKE_SIMPLE);
620
+ export const slh_dsa_shake_256s = /* @__PURE__ */ gen(PARAMS['256s'], SHAKE_SIMPLE);
621
+
622
+ type ShaType = typeof sha256 | typeof sha512;
623
+ const genSha =
624
+ (h0: ShaType, h1: ShaType, robust: boolean): GetContext =>
625
+ (opts) =>
626
+ (pub_seed, sk_seed?) => {
627
+ const { N } = opts;
628
+ /*
629
+ Perf debug stats, how much hashes we call?
630
+ 128f_simple: { prf: 8305, thash: 96_922, hmsg: 1, gen_message_random: 1, mgf1: 2 }
631
+ 256s_robust: { prf: 497_686, thash: 2_783_203, hmsg: 1, gen_message_random: 1, mgf1: 2_783_205}
632
+ 256f_simple: { prf: 36_179, thash: 309_693, hmsg: 1, gen_message_random: 1, mgf1: 2 }
633
+ */
634
+ const stats = { prf: 0, thash: 0, hmsg: 0, gen_message_random: 0, mgf1: 0 };
635
+
636
+ const counterB = new Uint8Array(4);
637
+ const counterV = createView(counterB);
638
+ const h0ps = h0
639
+ .create()
640
+ .update(pub_seed)
641
+ .update(new Uint8Array(h0.blockLen - N));
642
+ const h1ps = h1
643
+ .create()
644
+ .update(pub_seed)
645
+ .update(new Uint8Array(h1.blockLen - N));
646
+
647
+ const h0tmp = h0ps.clone();
648
+ const h1tmp = h1ps.clone();
649
+
650
+ function mgf1(seed: Uint8Array, length: number, hash: ShaType) {
651
+ stats.mgf1++;
652
+ const out = new Uint8Array(Math.ceil(length / hash.outputLen) * hash.outputLen);
653
+ if (length > 2 ** 32) throw new Error('mask too long');
654
+ for (let counter = 0, o = out; o.length; counter++) {
655
+ counterV.setUint32(0, counter, false);
656
+ hash.create().update(seed).update(counterB).digestInto(o);
657
+ o = o.subarray(hash.outputLen);
658
+ }
659
+ out.subarray(length).fill(0);
660
+ return out.subarray(0, length);
661
+ }
662
+
663
+ const thash_simple =
664
+ (_: ShaType, h: typeof h0ps, hTmp: typeof h0ps) =>
665
+ (blocks: number, input: Uint8Array, addr: ADRS) => {
666
+ stats.thash++;
667
+ const d = h
668
+ ._cloneInto(hTmp as any)
669
+ .update(addr)
670
+ .update(input.subarray(0, blocks * N))
671
+ .digest();
672
+ return d.subarray(0, N);
673
+ };
674
+
675
+ const thash_robust =
676
+ (sha: ShaType, h: typeof h0ps, _: typeof h0ps) =>
677
+ (blocks: number, input: Uint8Array, addr: ADRS) => {
678
+ stats.thash++;
679
+ stats.mgf1++;
680
+ // inlined mgf1
681
+ const addr8 = addr;
682
+ const hh = sha.create().update(pub_seed).update(addr8);
683
+ let bitmask = new Uint8Array(Math.ceil((blocks * N) / sha.outputLen) * sha.outputLen);
684
+ for (let counter = 0, o = bitmask; o.length; counter++) {
685
+ counterV.setUint32(0, counter, false);
686
+ hh.clone().update(counterB).digestInto(o);
687
+ o = o.subarray(sha.outputLen);
688
+ }
689
+ bitmask = bitmask.subarray(0, blocks * N);
690
+ const ou32 = u32(input);
691
+ const bm32 = u32(bitmask);
692
+ for (let i = 0; i < bm32.length; i++) bm32[i] ^= ou32[i];
693
+ const d = h.clone().update(addr8).update(bitmask).digest();
694
+ return d.subarray(0, N);
695
+ };
696
+
697
+ const thash = robust ? thash_robust : thash_simple;
698
+ return {
699
+ PRFaddr: (addr: ADRS) => {
700
+ if (!sk_seed) throw new Error('No sk seed');
701
+ stats.prf++;
702
+ return h0ps
703
+ ._cloneInto(h0tmp as any)
704
+ .update(addr)
705
+ .update(sk_seed)
706
+ .digest()
707
+ .subarray(0, N);
708
+ },
709
+ PRFmsg: (skPRF: Uint8Array, random: Uint8Array, msg: Uint8Array) => {
710
+ stats.gen_message_random++;
711
+ return new HMAC(h1, skPRF).update(random).update(msg).digest().subarray(0, N);
712
+ },
713
+ Hmsg: (R: Uint8Array, pk: Uint8Array, m: Uint8Array, outLen) => {
714
+ stats.hmsg++;
715
+ const seed = concatBytes(
716
+ R.subarray(0, N),
717
+ pk.subarray(0, N),
718
+ h1.create().update(R.subarray(0, N)).update(pk).update(m).digest()
719
+ );
720
+ return mgf1(seed, outLen, h1);
721
+ },
722
+ thash1: thash(h0, h0ps, h0tmp).bind(null, 1),
723
+ thashN: thash(h1, h1ps, h1tmp),
724
+ clean: () => {
725
+ h0ps.destroy();
726
+ h1ps.destroy();
727
+ h0tmp.destroy();
728
+ h1tmp.destroy();
729
+ //console.log(stats);
730
+ },
731
+ };
732
+ };
733
+
734
+ const SHA256_SIMPLE = {
735
+ isCompressed: true,
736
+ getContext: genSha(sha256, sha256, false),
737
+ };
738
+ const SHA256_ROBUST = {
739
+ isCompressed: true,
740
+ getContext: genSha(sha256, sha256, true),
741
+ };
742
+ const SHA512_SIMPLE = {
743
+ isCompressed: true,
744
+ getContext: genSha(sha256, sha512, false),
745
+ };
746
+ const SHA512_ROBUST = {
747
+ isCompressed: true,
748
+ getContext: genSha(sha256, sha512, true),
749
+ };
750
+
751
+ export const sphincs_sha2_128f_simple = /* @__PURE__ */ gen(PARAMS['128f'], SHA256_SIMPLE);
752
+ export const sphincs_sha2_128f_robust = /* @__PURE__ */ gen(PARAMS['128f'], SHA256_ROBUST);
753
+ export const sphincs_sha2_128s_simple = /* @__PURE__ */ gen(PARAMS['128s'], SHA256_SIMPLE);
754
+ export const sphincs_sha2_128s_robust = /* @__PURE__ */ gen(PARAMS['128s'], SHA256_ROBUST);
755
+
756
+ export const sphincs_sha2_192f_simple = /* @__PURE__ */ gen(PARAMS['192f'], SHA512_SIMPLE);
757
+ export const sphincs_sha2_192f_robust = /* @__PURE__ */ gen(PARAMS['192f'], SHA512_ROBUST);
758
+ export const sphincs_sha2_192s_simple = /* @__PURE__ */ gen(PARAMS['192s'], SHA512_SIMPLE);
759
+ export const sphincs_sha2_192s_robust = /* @__PURE__ */ gen(PARAMS['192s'], SHA512_ROBUST);
760
+ export const sphincs_sha2_256f_simple = /* @__PURE__ */ gen(PARAMS['256f'], SHA512_SIMPLE);
761
+ export const sphincs_sha2_256f_robust = /* @__PURE__ */ gen(PARAMS['256f'], SHA512_ROBUST);
762
+ export const sphincs_sha2_256s_simple = /* @__PURE__ */ gen(PARAMS['256s'], SHA512_SIMPLE);
763
+ export const sphincs_sha2_256s_robust = /* @__PURE__ */ gen(PARAMS['256s'], SHA512_ROBUST);
764
+
765
+ // Only simple mode in SLH-DSA
766
+ export const slh_dsa_sha2_128f = /* @__PURE__ */ gen(PARAMS['128f'], SHA256_SIMPLE);
767
+ export const slh_dsa_sha2_128s = /* @__PURE__ */ gen(PARAMS['128s'], SHA256_SIMPLE);
768
+ export const slh_dsa_sha2_192f = /* @__PURE__ */ gen(PARAMS['192f'], SHA512_SIMPLE);
769
+ export const slh_dsa_sha2_192s = /* @__PURE__ */ gen(PARAMS['192s'], SHA512_SIMPLE);
770
+ export const slh_dsa_sha2_256f = /* @__PURE__ */ gen(PARAMS['256f'], SHA512_SIMPLE);
771
+ export const slh_dsa_sha2_256s = /* @__PURE__ */ gen(PARAMS['256s'], SHA512_SIMPLE);