@noble/post-quantum 0.1.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 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);