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