@theqrl/mldsa87 0.2.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.
@@ -0,0 +1,1471 @@
1
+ import { shake128, shake256 } from '@noble/hashes/sha3';
2
+ import pkg from 'randombytes';
3
+
4
+ const Shake128Rate = 168;
5
+ const Shake256Rate = 136;
6
+ const Stream128BlockBytes = Shake128Rate;
7
+ const Stream256BlockBytes = Shake256Rate;
8
+
9
+ const SeedBytes = 32;
10
+ const CRHBytes = 64;
11
+ const TRBytes = 64;
12
+ const RNDBytes = 32;
13
+ const N = 256;
14
+ const Q = 8380417;
15
+ const QInv = 58728449;
16
+ const D = 13;
17
+
18
+ const K = 8;
19
+ const L = 7;
20
+ const ETA = 2;
21
+ const TAU = 60;
22
+ const BETA = 120;
23
+ const GAMMA1 = 1 << 19;
24
+ const GAMMA2 = Math.floor((Q - 1) / 32);
25
+ const OMEGA = 75;
26
+ const CTILDEBytes = 64;
27
+
28
+ const PolyT1PackedBytes = 320;
29
+ const PolyT0PackedBytes = 416;
30
+ const PolyETAPackedBytes = 96;
31
+ const PolyZPackedBytes = 640;
32
+ const PolyVecHPackedBytes = OMEGA + K;
33
+ const PolyW1PackedBytes = 128;
34
+
35
+ const CryptoPublicKeyBytes = SeedBytes + K * PolyT1PackedBytes;
36
+ const CryptoSecretKeyBytes =
37
+ 2 * SeedBytes + TRBytes + L * PolyETAPackedBytes + K * PolyETAPackedBytes + K * PolyT0PackedBytes;
38
+ const CryptoBytes = CTILDEBytes + L * PolyZPackedBytes + PolyVecHPackedBytes;
39
+
40
+ const PolyUniformNBlocks = Math.floor((768 + Stream128BlockBytes - 1) / Stream128BlockBytes);
41
+ const PolyUniformETANBlocks = Math.floor((136 + Stream256BlockBytes - 1) / Stream256BlockBytes);
42
+ const PolyUniformGamma1NBlocks = Math.floor((PolyZPackedBytes + Stream256BlockBytes - 1) / Stream256BlockBytes);
43
+
44
+ const zetas = [
45
+ 0, 25847, -2608894, -518909, 237124, -777960, -876248, 466468, 1826347, 2353451, -359251, -2091905, 3119733, -2884855,
46
+ 3111497, 2680103, 2725464, 1024112, -1079900, 3585928, -549488, -1119584, 2619752, -2108549, -2118186, -3859737,
47
+ -1399561, -3277672, 1757237, -19422, 4010497, 280005, 2706023, 95776, 3077325, 3530437, -1661693, -3592148, -2537516,
48
+ 3915439, -3861115, -3043716, 3574422, -2867647, 3539968, -300467, 2348700, -539299, -1699267, -1643818, 3505694,
49
+ -3821735, 3507263, -2140649, -1600420, 3699596, 811944, 531354, 954230, 3881043, 3900724, -2556880, 2071892, -2797779,
50
+ -3930395, -1528703, -3677745, -3041255, -1452451, 3475950, 2176455, -1585221, -1257611, 1939314, -4083598, -1000202,
51
+ -3190144, -3157330, -3632928, 126922, 3412210, -983419, 2147896, 2715295, -2967645, -3693493, -411027, -2477047,
52
+ -671102, -1228525, -22981, -1308169, -381987, 1349076, 1852771, -1430430, -3343383, 264944, 508951, 3097992, 44288,
53
+ -1100098, 904516, 3958618, -3724342, -8578, 1653064, -3249728, 2389356, -210977, 759969, -1316856, 189548, -3553272,
54
+ 3159746, -1851402, -2409325, -177440, 1315589, 1341330, 1285669, -1584928, -812732, -1439742, -3019102, -3881060,
55
+ -3628969, 3839961, 2091667, 3407706, 2316500, 3817976, -3342478, 2244091, -2446433, -3562462, 266997, 2434439,
56
+ -1235728, 3513181, -3520352, -3759364, -1197226, -3193378, 900702, 1859098, 909542, 819034, 495491, -1613174, -43260,
57
+ -522500, -655327, -3122442, 2031748, 3207046, -3556995, -525098, -768622, -3595838, 342297, 286988, -2437823, 4108315,
58
+ 3437287, -3342277, 1735879, 203044, 2842341, 2691481, -2590150, 1265009, 4055324, 1247620, 2486353, 1595974, -3767016,
59
+ 1250494, 2635921, -3548272, -2994039, 1869119, 1903435, -1050970, -1333058, 1237275, -3318210, -1430225, -451100,
60
+ 1312455, 3306115, -1962642, -1279661, 1917081, -2546312, -1374803, 1500165, 777191, 2235880, 3406031, -542412,
61
+ -2831860, -1671176, -1846953, -2584293, -3724270, 594136, -3776993, -2013608, 2432395, 2454455, -164721, 1957272,
62
+ 3369112, 185531, -1207385, -3183426, 162844, 1616392, 3014001, 810149, 1652634, -3694233, -1799107, -3038916, 3523897,
63
+ 3866901, 269760, 2213111, -975884, 1717735, 472078, -426683, 1723600, -1803090, 1910376, -1667432, -1104333, -260646,
64
+ -3833893, -2939036, -2235985, -420899, -2286327, 183443, -976891, 1612842, -3545687, -554416, 3919660, -48306,
65
+ -1362209, 3937738, 1400424, -846154, 1976782,
66
+ ];
67
+
68
+ /**
69
+ * FIPS 202 SHAKE functions using @noble/hashes
70
+ * Provides streaming XOF (extendable output function) interface
71
+ */
72
+
73
+
74
+ /**
75
+ * Keccak state wrapper for @noble/hashes
76
+ * Maintains hasher instance for streaming operations
77
+ */
78
+ class KeccakState {
79
+ constructor() {
80
+ this.hasher = null;
81
+ this.finalized = false;
82
+ }
83
+ }
84
+
85
+ // SHAKE-128 functions
86
+
87
+ function shake128Init(state) {
88
+ state.hasher = shake128.create({});
89
+ state.finalized = false;
90
+ }
91
+
92
+ function shake128Absorb(state, input) {
93
+ state.hasher.update(input);
94
+ }
95
+
96
+ function shake128Finalize(state) {
97
+ // Mark as finalized - actual finalization happens on first xofInto call
98
+ state.finalized = true;
99
+ }
100
+
101
+ function shake128SqueezeBlocks(out, outputOffset, nBlocks, state) {
102
+ const len = nBlocks * Shake128Rate;
103
+ const output = out.subarray(outputOffset, outputOffset + len);
104
+ state.hasher.xofInto(output);
105
+ }
106
+
107
+ // SHAKE-256 functions
108
+
109
+ function shake256Init(state) {
110
+ state.hasher = shake256.create({});
111
+ state.finalized = false;
112
+ }
113
+
114
+ function shake256Absorb(state, input) {
115
+ state.hasher.update(input);
116
+ }
117
+
118
+ function shake256Finalize(state) {
119
+ // Mark as finalized - actual finalization happens on first xofInto call
120
+ state.finalized = true;
121
+ }
122
+
123
+ function shake256SqueezeBlocks(out, outputOffset, nBlocks, state) {
124
+ const len = nBlocks * Shake256Rate;
125
+ const output = out.subarray(outputOffset, outputOffset + len);
126
+ state.hasher.xofInto(output);
127
+ }
128
+
129
+ function mldsaShake128StreamInit(state, seed, nonce) {
130
+ if (seed.length !== SeedBytes) {
131
+ throw new Error(`invalid seed length ${seed.length} | expected ${SeedBytes}`);
132
+ }
133
+ const t = new Uint8Array(2);
134
+ t[0] = nonce & 0xff;
135
+ t[1] = nonce >> 8;
136
+
137
+ shake128Init(state);
138
+ shake128Absorb(state, seed);
139
+ shake128Absorb(state, t);
140
+ shake128Finalize(state);
141
+ }
142
+
143
+ function mldsaShake256StreamInit(state, seed, nonce) {
144
+ if (seed.length !== CRHBytes) {
145
+ throw new Error(`invalid seed length ${seed.length} | expected ${CRHBytes}`);
146
+ }
147
+ const t = new Uint8Array(2);
148
+ t[0] = nonce & 0xff;
149
+ t[1] = nonce >> 8;
150
+
151
+ shake256Init(state);
152
+ shake256Absorb(state, seed);
153
+ shake256Absorb(state, t);
154
+ shake256Finalize(state);
155
+ }
156
+
157
+ function montgomeryReduce(a) {
158
+ let t = BigInt.asIntN(32, BigInt.asIntN(64, BigInt.asIntN(32, a)) * BigInt(QInv));
159
+ t = BigInt.asIntN(32, (a - t * BigInt(Q)) >> 32n);
160
+ return t;
161
+ }
162
+
163
+ function reduce32(a) {
164
+ let t = (a + (1 << 22)) >> 23;
165
+ t = a - t * Q;
166
+ return t;
167
+ }
168
+
169
+ function cAddQ(a) {
170
+ let ar = a;
171
+ ar += (ar >> 31) & Q;
172
+ return ar;
173
+ }
174
+
175
+ function ntt(a) {
176
+ let k = 0;
177
+ let j = 0;
178
+
179
+ for (let len = 128; len > 0; len >>= 1) {
180
+ for (let start = 0; start < N; start = j + len) {
181
+ const zeta = zetas[++k];
182
+ for (j = start; j < start + len; ++j) {
183
+ const t = Number(montgomeryReduce(BigInt.asIntN(64, BigInt(zeta) * BigInt(a[j + len]))));
184
+ a[j + len] = a[j] - t;
185
+ a[j] += t;
186
+ }
187
+ }
188
+ }
189
+ }
190
+
191
+ function invNTTToMont(a) {
192
+ const f = 41978n; // mont^2/256
193
+ let j = 0;
194
+ let k = 256;
195
+
196
+ for (let len = 1; len < N; len <<= 1) {
197
+ for (let start = 0; start < N; start = j + len) {
198
+ const zeta = BigInt.asIntN(32, BigInt(-zetas[--k]));
199
+ for (j = start; j < start + len; ++j) {
200
+ const t = a[j];
201
+ a[j] = t + a[j + len];
202
+ a[j + len] = t - a[j + len];
203
+ a[j + len] = Number(montgomeryReduce(BigInt.asIntN(64, zeta * BigInt(a[j + len]))));
204
+ }
205
+ }
206
+ }
207
+ for (let j = 0; j < N; ++j) {
208
+ a[j] = Number(montgomeryReduce(BigInt.asIntN(64, f * BigInt(a[j]))));
209
+ }
210
+ }
211
+
212
+ function power2round(a0p, i, a) {
213
+ const a0 = a0p;
214
+ const a1 = (a + (1 << (D - 1)) - 1) >> D;
215
+ a0[i] = a - (a1 << D);
216
+ return a1;
217
+ }
218
+
219
+ function decompose(a0p, i, a) {
220
+ const a0 = a0p;
221
+ let a1 = (a + 127) >> 7;
222
+ a1 = (a1 * 1025 + (1 << 21)) >> 22;
223
+ a1 &= 15;
224
+
225
+ a0[i] = a - a1 * 2 * GAMMA2;
226
+ a0[i] -= (((Q - 1) / 2 - a0[i]) >> 31) & Q;
227
+ return a1;
228
+ }
229
+
230
+ function makeHint(a0, a1) {
231
+ if (a0 > GAMMA2 || a0 < -GAMMA2 || (a0 === -GAMMA2 && a1 !== 0)) return 1;
232
+
233
+ return 0;
234
+ }
235
+
236
+ function useHint(a, hint) {
237
+ const a0 = new Int32Array(1);
238
+ const a1 = decompose(a0, 0, a);
239
+
240
+ if (hint === 0) return a1;
241
+
242
+ if (a0[0] > 0) return (a1 + 1) & 15;
243
+ return (a1 - 1) & 15;
244
+ }
245
+
246
+ class Poly {
247
+ constructor() {
248
+ this.coeffs = new Int32Array(N);
249
+ }
250
+
251
+ copy(poly) {
252
+ for (let i = N - 1; i >= 0; i--) {
253
+ this.coeffs[i] = poly.coeffs[i];
254
+ }
255
+ }
256
+ }
257
+
258
+ function polyReduce(aP) {
259
+ const a = aP;
260
+ for (let i = 0; i < N; ++i) a.coeffs[i] = reduce32(a.coeffs[i]);
261
+ }
262
+
263
+ function polyCAddQ(aP) {
264
+ const a = aP;
265
+ for (let i = 0; i < N; ++i) a.coeffs[i] = cAddQ(a.coeffs[i]);
266
+ }
267
+
268
+ function polyAdd(cP, a, b) {
269
+ const c = cP;
270
+ for (let i = 0; i < N; ++i) c.coeffs[i] = a.coeffs[i] + b.coeffs[i];
271
+ }
272
+
273
+ function polySub(cP, a, b) {
274
+ const c = cP;
275
+ for (let i = 0; i < N; ++i) c.coeffs[i] = a.coeffs[i] - b.coeffs[i];
276
+ }
277
+
278
+ function polyShiftL(aP) {
279
+ const a = aP;
280
+ for (let i = 0; i < N; ++i) a.coeffs[i] <<= D;
281
+ }
282
+
283
+ function polyNTT(a) {
284
+ ntt(a.coeffs);
285
+ }
286
+
287
+ function polyInvNTTToMont(a) {
288
+ invNTTToMont(a.coeffs);
289
+ }
290
+
291
+ function polyPointWiseMontgomery(cP, a, b) {
292
+ const c = cP;
293
+ for (let i = 0; i < N; ++i) c.coeffs[i] = Number(montgomeryReduce(BigInt(a.coeffs[i]) * BigInt(b.coeffs[i])));
294
+ }
295
+
296
+ function polyPower2round(a1p, a0, a) {
297
+ const a1 = a1p;
298
+ for (let i = 0; i < N; ++i) a1.coeffs[i] = power2round(a0.coeffs, i, a.coeffs[i]);
299
+ }
300
+
301
+ function polyDecompose(a1p, a0, a) {
302
+ const a1 = a1p;
303
+ for (let i = 0; i < N; ++i) a1.coeffs[i] = decompose(a0.coeffs, i, a.coeffs[i]);
304
+ }
305
+
306
+ function polyMakeHint(hp, a0, a1) {
307
+ let s = 0;
308
+ const h = hp;
309
+ for (let i = 0; i < N; ++i) {
310
+ h.coeffs[i] = makeHint(a0.coeffs[i], a1.coeffs[i]);
311
+ s += h.coeffs[i];
312
+ }
313
+
314
+ return s;
315
+ }
316
+
317
+ function polyUseHint(bp, a, h) {
318
+ const b = bp;
319
+ for (let i = 0; i < N; ++i) {
320
+ b.coeffs[i] = useHint(a.coeffs[i], h.coeffs[i]);
321
+ }
322
+ }
323
+
324
+ function polyChkNorm(a, b) {
325
+ if (b > Math.floor((Q - 1) / 8)) {
326
+ return 1;
327
+ }
328
+
329
+ for (let i = 0; i < N; i++) {
330
+ let t = a.coeffs[i] >> 31;
331
+ t = a.coeffs[i] - (t & (2 * a.coeffs[i]));
332
+
333
+ if (t >= b) {
334
+ return 1;
335
+ }
336
+ }
337
+
338
+ return 0;
339
+ }
340
+
341
+ function rejUniform(ap, aOffset, len, buf, bufLen) {
342
+ let ctr = 0;
343
+ let pos = 0;
344
+ const a = ap;
345
+ while (ctr < len && pos + 3 <= bufLen) {
346
+ let t = buf[pos++];
347
+ t |= buf[pos++] << 8;
348
+ t |= buf[pos++] << 16;
349
+ t &= 0x7fffff;
350
+
351
+ if (t < Q) {
352
+ a[aOffset + ctr++] = t;
353
+ }
354
+ }
355
+
356
+ return ctr;
357
+ }
358
+
359
+ function polyUniform(a, seed, nonce) {
360
+ let off = 0;
361
+ let bufLen = PolyUniformNBlocks * Stream128BlockBytes;
362
+ const buf = new Uint8Array(PolyUniformNBlocks * Stream128BlockBytes + 2);
363
+
364
+ const state = new KeccakState();
365
+ mldsaShake128StreamInit(state, seed, nonce);
366
+ shake128SqueezeBlocks(buf, off, PolyUniformNBlocks, state);
367
+
368
+ let ctr = rejUniform(a.coeffs, 0, N, buf, bufLen);
369
+
370
+ while (ctr < N) {
371
+ off = bufLen % 3;
372
+ for (let i = 0; i < off; ++i) buf[i] = buf[bufLen - off + i];
373
+
374
+ shake128SqueezeBlocks(buf, off, 1, state);
375
+ bufLen = Stream128BlockBytes + off;
376
+ ctr += rejUniform(a.coeffs, ctr, N - ctr, buf, bufLen);
377
+ }
378
+ }
379
+
380
+ function rejEta(aP, aOffset, len, buf, bufLen) {
381
+ let ctr;
382
+ let pos;
383
+ let t0;
384
+ let t1;
385
+ const a = aP;
386
+ ctr = 0;
387
+ pos = 0;
388
+ while (ctr < len && pos < bufLen) {
389
+ t0 = buf[pos] & 0x0f;
390
+ t1 = buf[pos++] >> 4;
391
+
392
+ if (t0 < 15) {
393
+ t0 -= ((205 * t0) >> 10) * 5;
394
+ a[aOffset + ctr++] = 2 - t0;
395
+ }
396
+ if (t1 < 15 && ctr < len) {
397
+ t1 -= ((205 * t1) >> 10) * 5;
398
+ a[aOffset + ctr++] = 2 - t1;
399
+ }
400
+ }
401
+
402
+ return ctr;
403
+ }
404
+
405
+ function polyUniformEta(a, seed, nonce) {
406
+ let ctr;
407
+ const bufLen = PolyUniformETANBlocks * Stream256BlockBytes;
408
+ const buf = new Uint8Array(bufLen);
409
+
410
+ const state = new KeccakState();
411
+ mldsaShake256StreamInit(state, seed, nonce);
412
+ shake256SqueezeBlocks(buf, 0, PolyUniformETANBlocks, state);
413
+
414
+ ctr = rejEta(a.coeffs, 0, N, buf, bufLen);
415
+ while (ctr < N) {
416
+ shake256SqueezeBlocks(buf, 0, 1, state);
417
+ ctr += rejEta(a.coeffs, ctr, N - ctr, buf, Stream256BlockBytes);
418
+ }
419
+ }
420
+
421
+ function polyZUnpack(rP, a, aOffset) {
422
+ const r = rP;
423
+ for (let i = 0; i < N / 2; ++i) {
424
+ r.coeffs[2 * i] = a[aOffset + 5 * i];
425
+ r.coeffs[2 * i] |= a[aOffset + 5 * i + 1] << 8;
426
+ r.coeffs[2 * i] |= a[aOffset + 5 * i + 2] << 16;
427
+ r.coeffs[2 * i] &= 0xfffff;
428
+
429
+ r.coeffs[2 * i + 1] = a[aOffset + 5 * i + 2] >> 4;
430
+ r.coeffs[2 * i + 1] |= a[aOffset + 5 * i + 3] << 4;
431
+ r.coeffs[2 * i + 1] |= a[aOffset + 5 * i + 4] << 12;
432
+ r.coeffs[2 * i] &= 0xfffff;
433
+
434
+ r.coeffs[2 * i] = GAMMA1 - r.coeffs[2 * i];
435
+ r.coeffs[2 * i + 1] = GAMMA1 - r.coeffs[2 * i + 1];
436
+ }
437
+ }
438
+
439
+ function polyUniformGamma1(a, seed, nonce) {
440
+ const buf = new Uint8Array(PolyUniformGamma1NBlocks * Stream256BlockBytes);
441
+
442
+ const state = new KeccakState();
443
+ mldsaShake256StreamInit(state, seed, nonce);
444
+ shake256SqueezeBlocks(buf, 0, PolyUniformGamma1NBlocks, state);
445
+ polyZUnpack(a, buf, 0);
446
+ }
447
+
448
+ function polyChallenge(cP, seed) {
449
+ if (seed.length !== CTILDEBytes) throw new Error('invalid ctilde length');
450
+
451
+ let b;
452
+ let pos;
453
+ const c = cP;
454
+ const buf = new Uint8Array(Shake256Rate);
455
+
456
+ const state = new KeccakState();
457
+ shake256Init(state);
458
+ shake256Absorb(state, seed);
459
+ shake256Finalize(state);
460
+ shake256SqueezeBlocks(buf, 0, 1, state);
461
+
462
+ let signs = 0n;
463
+ for (let i = 0; i < 8; ++i) {
464
+ signs = BigInt.asUintN(64, signs | (BigInt(buf[i]) << BigInt(8 * i)));
465
+ }
466
+ pos = 8;
467
+
468
+ for (let i = 0; i < N; ++i) {
469
+ c.coeffs[i] = 0;
470
+ }
471
+ for (let i = N - TAU; i < N; ++i) {
472
+ do {
473
+ if (pos >= Shake256Rate) {
474
+ shake256SqueezeBlocks(buf, 0, 1, state);
475
+ pos = 0;
476
+ }
477
+
478
+ b = buf[pos++];
479
+ } while (b > i);
480
+
481
+ c.coeffs[i] = c.coeffs[b];
482
+ c.coeffs[b] = Number(1n - 2n * (signs & 1n));
483
+ signs >>= 1n;
484
+ }
485
+ }
486
+
487
+ function polyEtaPack(rP, rOffset, a) {
488
+ const t = new Uint8Array(8);
489
+ const r = rP;
490
+ for (let i = 0; i < N / 8; ++i) {
491
+ t[0] = ETA - a.coeffs[8 * i];
492
+ t[1] = ETA - a.coeffs[8 * i + 1];
493
+ t[2] = ETA - a.coeffs[8 * i + 2];
494
+ t[3] = ETA - a.coeffs[8 * i + 3];
495
+ t[4] = ETA - a.coeffs[8 * i + 4];
496
+ t[5] = ETA - a.coeffs[8 * i + 5];
497
+ t[6] = ETA - a.coeffs[8 * i + 6];
498
+ t[7] = ETA - a.coeffs[8 * i + 7];
499
+
500
+ r[rOffset + 3 * i] = (t[0] >> 0) | (t[1] << 3) | (t[2] << 6);
501
+ r[rOffset + 3 * i + 1] = (t[2] >> 2) | (t[3] << 1) | (t[4] << 4) | (t[5] << 7);
502
+ r[rOffset + 3 * i + 2] = (t[5] >> 1) | (t[6] << 2) | (t[7] << 5);
503
+ }
504
+ }
505
+
506
+ function polyEtaUnpack(rP, a, aOffset) {
507
+ const r = rP;
508
+ for (let i = 0; i < N / 8; ++i) {
509
+ r.coeffs[8 * i] = (a[aOffset + 3 * i] >> 0) & 7;
510
+ r.coeffs[8 * i + 1] = (a[aOffset + 3 * i] >> 3) & 7;
511
+ r.coeffs[8 * i + 2] = ((a[aOffset + 3 * i] >> 6) | (a[aOffset + 3 * i + 1] << 2)) & 7;
512
+ r.coeffs[8 * i + 3] = (a[aOffset + 3 * i + 1] >> 1) & 7;
513
+ r.coeffs[8 * i + 4] = (a[aOffset + 3 * i + 1] >> 4) & 7;
514
+ r.coeffs[8 * i + 5] = ((a[aOffset + 3 * i + 1] >> 7) | (a[aOffset + 3 * i + 2] << 1)) & 7;
515
+ r.coeffs[8 * i + 6] = (a[aOffset + 3 * i + 2] >> 2) & 7;
516
+ r.coeffs[8 * i + 7] = (a[aOffset + 3 * i + 2] >> 5) & 7;
517
+
518
+ r.coeffs[8 * i] = ETA - r.coeffs[8 * i];
519
+ r.coeffs[8 * i + 1] = ETA - r.coeffs[8 * i + 1];
520
+ r.coeffs[8 * i + 2] = ETA - r.coeffs[8 * i + 2];
521
+ r.coeffs[8 * i + 3] = ETA - r.coeffs[8 * i + 3];
522
+ r.coeffs[8 * i + 4] = ETA - r.coeffs[8 * i + 4];
523
+ r.coeffs[8 * i + 5] = ETA - r.coeffs[8 * i + 5];
524
+ r.coeffs[8 * i + 6] = ETA - r.coeffs[8 * i + 6];
525
+ r.coeffs[8 * i + 7] = ETA - r.coeffs[8 * i + 7];
526
+ }
527
+ }
528
+
529
+ function polyT1Pack(rP, rOffset, a) {
530
+ const r = rP;
531
+ for (let i = 0; i < N / 4; ++i) {
532
+ r[rOffset + 5 * i] = a.coeffs[4 * i] >> 0;
533
+ r[rOffset + 5 * i + 1] = (a.coeffs[4 * i] >> 8) | (a.coeffs[4 * i + 1] << 2);
534
+ r[rOffset + 5 * i + 2] = (a.coeffs[4 * i + 1] >> 6) | (a.coeffs[4 * i + 2] << 4);
535
+ r[rOffset + 5 * i + 3] = (a.coeffs[4 * i + 2] >> 4) | (a.coeffs[4 * i + 3] << 6);
536
+ r[rOffset + 5 * i + 4] = a.coeffs[4 * i + 3] >> 2;
537
+ }
538
+ }
539
+
540
+ function polyT1Unpack(rP, a, aOffset) {
541
+ const r = rP;
542
+ for (let i = 0; i < N / 4; ++i) {
543
+ r.coeffs[4 * i] = ((a[aOffset + 5 * i] >> 0) | (a[aOffset + 5 * i + 1] << 8)) & 0x3ff;
544
+ r.coeffs[4 * i + 1] = ((a[aOffset + 5 * i + 1] >> 2) | (a[aOffset + 5 * i + 2] << 6)) & 0x3ff;
545
+ r.coeffs[4 * i + 2] = ((a[aOffset + 5 * i + 2] >> 4) | (a[aOffset + 5 * i + 3] << 4)) & 0x3ff;
546
+ r.coeffs[4 * i + 3] = ((a[aOffset + 5 * i + 3] >> 6) | (a[aOffset + 5 * i + 4] << 2)) & 0x3ff;
547
+ }
548
+ }
549
+
550
+ function polyT0Pack(rP, rOffset, a) {
551
+ const t = new Uint32Array(8);
552
+ const r = rP;
553
+ for (let i = 0; i < N / 8; ++i) {
554
+ t[0] = (1 << (D - 1)) - a.coeffs[8 * i];
555
+ t[1] = (1 << (D - 1)) - a.coeffs[8 * i + 1];
556
+ t[2] = (1 << (D - 1)) - a.coeffs[8 * i + 2];
557
+ t[3] = (1 << (D - 1)) - a.coeffs[8 * i + 3];
558
+ t[4] = (1 << (D - 1)) - a.coeffs[8 * i + 4];
559
+ t[5] = (1 << (D - 1)) - a.coeffs[8 * i + 5];
560
+ t[6] = (1 << (D - 1)) - a.coeffs[8 * i + 6];
561
+ t[7] = (1 << (D - 1)) - a.coeffs[8 * i + 7];
562
+
563
+ r[rOffset + 13 * i] = t[0];
564
+ r[rOffset + 13 * i + 1] = t[0] >> 8;
565
+ r[rOffset + 13 * i + 1] |= t[1] << 5;
566
+ r[rOffset + 13 * i + 2] = t[1] >> 3;
567
+ r[rOffset + 13 * i + 3] = t[1] >> 11;
568
+ r[rOffset + 13 * i + 3] |= t[2] << 2;
569
+ r[rOffset + 13 * i + 4] = t[2] >> 6;
570
+ r[rOffset + 13 * i + 4] |= t[3] << 7;
571
+ r[rOffset + 13 * i + 5] = t[3] >> 1;
572
+ r[rOffset + 13 * i + 6] = t[3] >> 9;
573
+ r[rOffset + 13 * i + 6] |= t[4] << 4;
574
+ r[rOffset + 13 * i + 7] = t[4] >> 4;
575
+ r[rOffset + 13 * i + 8] = t[4] >> 12;
576
+ r[rOffset + 13 * i + 8] |= t[5] << 1;
577
+ r[rOffset + 13 * i + 9] = t[5] >> 7;
578
+ r[rOffset + 13 * i + 9] |= t[6] << 6;
579
+ r[rOffset + 13 * i + 10] = t[6] >> 2;
580
+ r[rOffset + 13 * i + 11] = t[6] >> 10;
581
+ r[rOffset + 13 * i + 11] |= t[7] << 3;
582
+ r[rOffset + 13 * i + 12] = t[7] >> 5;
583
+ }
584
+ }
585
+
586
+ function polyT0Unpack(rP, a, aOffset) {
587
+ const r = rP;
588
+ for (let i = 0; i < N / 8; ++i) {
589
+ r.coeffs[8 * i] = a[aOffset + 13 * i];
590
+ r.coeffs[8 * i] |= a[aOffset + 13 * i + 1] << 8;
591
+ r.coeffs[8 * i] &= 0x1fff;
592
+
593
+ r.coeffs[8 * i + 1] = a[aOffset + 13 * i + 1] >> 5;
594
+ r.coeffs[8 * i + 1] |= a[aOffset + 13 * i + 2] << 3;
595
+ r.coeffs[8 * i + 1] |= a[aOffset + 13 * i + 3] << 11;
596
+ r.coeffs[8 * i + 1] &= 0x1fff;
597
+
598
+ r.coeffs[8 * i + 2] = a[aOffset + 13 * i + 3] >> 2;
599
+ r.coeffs[8 * i + 2] |= a[aOffset + 13 * i + 4] << 6;
600
+ r.coeffs[8 * i + 2] &= 0x1fff;
601
+
602
+ r.coeffs[8 * i + 3] = a[aOffset + 13 * i + 4] >> 7;
603
+ r.coeffs[8 * i + 3] |= a[aOffset + 13 * i + 5] << 1;
604
+ r.coeffs[8 * i + 3] |= a[aOffset + 13 * i + 6] << 9;
605
+ r.coeffs[8 * i + 3] &= 0x1fff;
606
+
607
+ r.coeffs[8 * i + 4] = a[aOffset + 13 * i + 6] >> 4;
608
+ r.coeffs[8 * i + 4] |= a[aOffset + 13 * i + 7] << 4;
609
+ r.coeffs[8 * i + 4] |= a[aOffset + 13 * i + 8] << 12;
610
+ r.coeffs[8 * i + 4] &= 0x1fff;
611
+
612
+ r.coeffs[8 * i + 5] = a[aOffset + 13 * i + 8] >> 1;
613
+ r.coeffs[8 * i + 5] |= a[aOffset + 13 * i + 9] << 7;
614
+ r.coeffs[8 * i + 5] &= 0x1fff;
615
+
616
+ r.coeffs[8 * i + 6] = a[aOffset + 13 * i + 9] >> 6;
617
+ r.coeffs[8 * i + 6] |= a[aOffset + 13 * i + 10] << 2;
618
+ r.coeffs[8 * i + 6] |= a[aOffset + 13 * i + 11] << 10;
619
+ r.coeffs[8 * i + 6] &= 0x1fff;
620
+
621
+ r.coeffs[8 * i + 7] = a[aOffset + 13 * i + 11] >> 3;
622
+ r.coeffs[8 * i + 7] |= a[aOffset + 13 * i + 12] << 5;
623
+ r.coeffs[8 * i + 7] &= 0x1fff;
624
+
625
+ r.coeffs[8 * i] = (1 << (D - 1)) - r.coeffs[8 * i];
626
+ r.coeffs[8 * i + 1] = (1 << (D - 1)) - r.coeffs[8 * i + 1];
627
+ r.coeffs[8 * i + 2] = (1 << (D - 1)) - r.coeffs[8 * i + 2];
628
+ r.coeffs[8 * i + 3] = (1 << (D - 1)) - r.coeffs[8 * i + 3];
629
+ r.coeffs[8 * i + 4] = (1 << (D - 1)) - r.coeffs[8 * i + 4];
630
+ r.coeffs[8 * i + 5] = (1 << (D - 1)) - r.coeffs[8 * i + 5];
631
+ r.coeffs[8 * i + 6] = (1 << (D - 1)) - r.coeffs[8 * i + 6];
632
+ r.coeffs[8 * i + 7] = (1 << (D - 1)) - r.coeffs[8 * i + 7];
633
+ }
634
+ }
635
+
636
+ function polyZPack(rP, rOffset, a) {
637
+ const t = new Uint32Array(4);
638
+ const r = rP;
639
+ for (let i = 0; i < N / 2; ++i) {
640
+ t[0] = GAMMA1 - a.coeffs[2 * i];
641
+ t[1] = GAMMA1 - a.coeffs[2 * i + 1];
642
+
643
+ r[rOffset + 5 * i] = t[0];
644
+ r[rOffset + 5 * i + 1] = t[0] >> 8;
645
+ r[rOffset + 5 * i + 2] = t[0] >> 16;
646
+ r[rOffset + 5 * i + 2] |= t[1] << 4;
647
+ r[rOffset + 5 * i + 3] = t[1] >> 4;
648
+ r[rOffset + 5 * i + 4] = t[1] >> 12;
649
+ }
650
+ }
651
+
652
+ function polyW1Pack(rP, rOffset, a) {
653
+ const r = rP;
654
+ for (let i = 0; i < N / 2; ++i) {
655
+ r[rOffset + i] = a.coeffs[2 * i] | (a.coeffs[2 * i + 1] << 4);
656
+ }
657
+ }
658
+
659
+ class PolyVecK {
660
+ constructor() {
661
+ this.vec = new Array(K).fill().map(() => new Poly());
662
+ }
663
+ }
664
+
665
+ class PolyVecL {
666
+ constructor() {
667
+ this.vec = new Array(L).fill().map(() => new Poly());
668
+ }
669
+
670
+ copy(polyVecL) {
671
+ for (let i = L - 1; i >= 0; i--) {
672
+ this.vec[i].copy(polyVecL.vec[i]);
673
+ }
674
+ }
675
+ }
676
+
677
+ function polyVecMatrixExpand(mat, rho) {
678
+ if (rho.length !== SeedBytes) {
679
+ throw new Error(`invalid rho length ${rho.length} | Expected length ${SeedBytes}`);
680
+ }
681
+ for (let i = 0; i < K; ++i) {
682
+ for (let j = 0; j < L; ++j) {
683
+ polyUniform(mat[i].vec[j], rho, (i << 8) + j);
684
+ }
685
+ }
686
+ }
687
+
688
+ function polyVecMatrixPointWiseMontgomery(t, mat, v) {
689
+ for (let i = 0; i < K; ++i) {
690
+ polyVecLPointWiseAccMontgomery(t.vec[i], mat[i], v);
691
+ }
692
+ }
693
+
694
+ function polyVecLUniformEta(v, seed, nonceP) {
695
+ let nonce = nonceP;
696
+ if (seed.length !== CRHBytes) {
697
+ throw new Error(`invalid seed length ${seed.length} | Expected length ${CRHBytes}`);
698
+ }
699
+ for (let i = 0; i < L; i++) {
700
+ polyUniformEta(v.vec[i], seed, nonce++);
701
+ }
702
+ }
703
+
704
+ function polyVecLUniformGamma1(v, seed, nonce) {
705
+ if (seed.length !== CRHBytes) {
706
+ throw new Error(`invalid seed length ${seed.length} | Expected length ${CRHBytes}`);
707
+ }
708
+ for (let i = 0; i < L; i++) {
709
+ polyUniformGamma1(v.vec[i], seed, L * nonce + i);
710
+ }
711
+ }
712
+
713
+ function polyVecLReduce(v) {
714
+ for (let i = 0; i < L; i++) {
715
+ polyReduce(v.vec[i]);
716
+ }
717
+ }
718
+
719
+ function polyVecLAdd(w, u, v) {
720
+ for (let i = 0; i < L; ++i) {
721
+ polyAdd(w.vec[i], u.vec[i], v.vec[i]);
722
+ }
723
+ }
724
+
725
+ function polyVecLNTT(v) {
726
+ for (let i = 0; i < L; ++i) {
727
+ polyNTT(v.vec[i]);
728
+ }
729
+ }
730
+
731
+ function polyVecLInvNTTToMont(v) {
732
+ for (let i = 0; i < L; ++i) {
733
+ polyInvNTTToMont(v.vec[i]);
734
+ }
735
+ }
736
+
737
+ function polyVecLPointWisePolyMontgomery(r, a, v) {
738
+ for (let i = 0; i < L; ++i) {
739
+ polyPointWiseMontgomery(r.vec[i], a, v.vec[i]);
740
+ }
741
+ }
742
+
743
+ function polyVecLPointWiseAccMontgomery(w, u, v) {
744
+ const t = new Poly();
745
+ polyPointWiseMontgomery(w, u.vec[0], v.vec[0]);
746
+ for (let i = 1; i < L; i++) {
747
+ polyPointWiseMontgomery(t, u.vec[i], v.vec[i]);
748
+ polyAdd(w, w, t);
749
+ }
750
+ }
751
+
752
+ function polyVecLChkNorm(v, bound) {
753
+ for (let i = 0; i < L; i++) {
754
+ if (polyChkNorm(v.vec[i], bound) !== 0) {
755
+ return 1;
756
+ }
757
+ }
758
+ return 0;
759
+ }
760
+
761
+ function polyVecKUniformEta(v, seed, nonceP) {
762
+ let nonce = nonceP;
763
+ for (let i = 0; i < K; ++i) {
764
+ polyUniformEta(v.vec[i], seed, nonce++);
765
+ }
766
+ }
767
+
768
+ function polyVecKReduce(v) {
769
+ for (let i = 0; i < K; ++i) {
770
+ polyReduce(v.vec[i]);
771
+ }
772
+ }
773
+
774
+ function polyVecKCAddQ(v) {
775
+ for (let i = 0; i < K; ++i) {
776
+ polyCAddQ(v.vec[i]);
777
+ }
778
+ }
779
+
780
+ function polyVecKAdd(w, u, v) {
781
+ for (let i = 0; i < K; ++i) {
782
+ polyAdd(w.vec[i], u.vec[i], v.vec[i]);
783
+ }
784
+ }
785
+
786
+ function polyVecKSub(w, u, v) {
787
+ for (let i = 0; i < K; ++i) {
788
+ polySub(w.vec[i], u.vec[i], v.vec[i]);
789
+ }
790
+ }
791
+
792
+ function polyVecKShiftL(v) {
793
+ for (let i = 0; i < K; ++i) {
794
+ polyShiftL(v.vec[i]);
795
+ }
796
+ }
797
+
798
+ function polyVecKNTT(v) {
799
+ for (let i = 0; i < K; i++) {
800
+ polyNTT(v.vec[i]);
801
+ }
802
+ }
803
+
804
+ function polyVecKInvNTTToMont(v) {
805
+ for (let i = 0; i < K; i++) {
806
+ polyInvNTTToMont(v.vec[i]);
807
+ }
808
+ }
809
+
810
+ function polyVecKPointWisePolyMontgomery(r, a, v) {
811
+ for (let i = 0; i < K; i++) {
812
+ polyPointWiseMontgomery(r.vec[i], a, v.vec[i]);
813
+ }
814
+ }
815
+
816
+ function polyVecKChkNorm(v, bound) {
817
+ for (let i = 0; i < K; i++) {
818
+ if (polyChkNorm(v.vec[i], bound) !== 0) {
819
+ return 1;
820
+ }
821
+ }
822
+ return 0;
823
+ }
824
+
825
+ function polyVecKPower2round(v1, v0, v) {
826
+ for (let i = 0; i < K; i++) {
827
+ polyPower2round(v1.vec[i], v0.vec[i], v.vec[i]);
828
+ }
829
+ }
830
+
831
+ function polyVecKDecompose(v1, v0, v) {
832
+ for (let i = 0; i < K; i++) {
833
+ polyDecompose(v1.vec[i], v0.vec[i], v.vec[i]);
834
+ }
835
+ }
836
+
837
+ function polyVecKMakeHint(h, v0, v1) {
838
+ let s = 0;
839
+ for (let i = 0; i < K; i++) {
840
+ s += polyMakeHint(h.vec[i], v0.vec[i], v1.vec[i]);
841
+ }
842
+ return s;
843
+ }
844
+
845
+ function polyVecKUseHint(w, u, h) {
846
+ for (let i = 0; i < K; ++i) {
847
+ polyUseHint(w.vec[i], u.vec[i], h.vec[i]);
848
+ }
849
+ }
850
+
851
+ function polyVecKPackW1(r, w1) {
852
+ for (let i = 0; i < K; ++i) {
853
+ polyW1Pack(r, i * PolyW1PackedBytes, w1.vec[i]);
854
+ }
855
+ }
856
+
857
+ function packPk(pkp, rho, t1) {
858
+ const pk = pkp;
859
+ for (let i = 0; i < SeedBytes; ++i) {
860
+ pk[i] = rho[i];
861
+ }
862
+ for (let i = 0; i < K; ++i) {
863
+ polyT1Pack(pk, SeedBytes + i * PolyT1PackedBytes, t1.vec[i]);
864
+ }
865
+ }
866
+
867
+ function unpackPk(rhop, t1, pk) {
868
+ const rho = rhop;
869
+ for (let i = 0; i < SeedBytes; ++i) {
870
+ rho[i] = pk[i];
871
+ }
872
+
873
+ for (let i = 0; i < K; ++i) {
874
+ polyT1Unpack(t1.vec[i], pk, SeedBytes + i * PolyT1PackedBytes);
875
+ }
876
+ }
877
+
878
+ function packSk(skp, rho, tr, key, t0, s1, s2) {
879
+ let skOffset = 0;
880
+ const sk = skp;
881
+ for (let i = 0; i < SeedBytes; ++i) {
882
+ sk[i] = rho[i];
883
+ }
884
+ skOffset += SeedBytes;
885
+
886
+ for (let i = 0; i < SeedBytes; ++i) {
887
+ sk[skOffset + i] = key[i];
888
+ }
889
+ skOffset += SeedBytes;
890
+
891
+ for (let i = 0; i < TRBytes; ++i) {
892
+ sk[skOffset + i] = tr[i];
893
+ }
894
+ skOffset += TRBytes;
895
+
896
+ for (let i = 0; i < L; ++i) {
897
+ polyEtaPack(sk, skOffset + i * PolyETAPackedBytes, s1.vec[i]);
898
+ }
899
+ skOffset += L * PolyETAPackedBytes;
900
+
901
+ for (let i = 0; i < K; ++i) {
902
+ polyEtaPack(sk, skOffset + i * PolyETAPackedBytes, s2.vec[i]);
903
+ }
904
+ skOffset += K * PolyETAPackedBytes;
905
+
906
+ for (let i = 0; i < K; ++i) {
907
+ polyT0Pack(sk, skOffset + i * PolyT0PackedBytes, t0.vec[i]);
908
+ }
909
+ }
910
+
911
+ function unpackSk(rhoP, trP, keyP, t0, s1, s2, sk) {
912
+ let skOffset = 0;
913
+ const rho = rhoP;
914
+ const tr = trP;
915
+ const key = keyP;
916
+ for (let i = 0; i < SeedBytes; ++i) {
917
+ rho[i] = sk[i];
918
+ }
919
+ skOffset += SeedBytes;
920
+
921
+ for (let i = 0; i < SeedBytes; ++i) {
922
+ key[i] = sk[skOffset + i];
923
+ }
924
+ skOffset += SeedBytes;
925
+
926
+ for (let i = 0; i < TRBytes; ++i) {
927
+ tr[i] = sk[skOffset + i];
928
+ }
929
+ skOffset += TRBytes;
930
+
931
+ for (let i = 0; i < L; ++i) {
932
+ polyEtaUnpack(s1.vec[i], sk, skOffset + i * PolyETAPackedBytes);
933
+ }
934
+ skOffset += L * PolyETAPackedBytes;
935
+
936
+ for (let i = 0; i < K; ++i) {
937
+ polyEtaUnpack(s2.vec[i], sk, skOffset + i * PolyETAPackedBytes);
938
+ }
939
+ skOffset += K * PolyETAPackedBytes;
940
+
941
+ for (let i = 0; i < K; ++i) {
942
+ polyT0Unpack(t0.vec[i], sk, skOffset + i * PolyT0PackedBytes);
943
+ }
944
+ }
945
+
946
+ function packSig(sigP, ctilde, z, h) {
947
+ let sigOffset = 0;
948
+ const sig = sigP;
949
+ for (let i = 0; i < CTILDEBytes; ++i) {
950
+ sig[i] = ctilde[i];
951
+ }
952
+ sigOffset += CTILDEBytes;
953
+
954
+ for (let i = 0; i < L; ++i) {
955
+ polyZPack(sig, sigOffset + i * PolyZPackedBytes, z.vec[i]);
956
+ }
957
+ sigOffset += L * PolyZPackedBytes;
958
+
959
+ for (let i = 0; i < OMEGA + K; ++i) {
960
+ sig[sigOffset + i] = 0;
961
+ }
962
+
963
+ let k = 0;
964
+ for (let i = 0; i < K; ++i) {
965
+ for (let j = 0; j < N; ++j) {
966
+ if (h.vec[i].coeffs[j] !== 0) {
967
+ sig[sigOffset + k++] = j;
968
+ }
969
+ }
970
+
971
+ sig[sigOffset + OMEGA + i] = k;
972
+ }
973
+ }
974
+
975
+ function unpackSig(cP, z, hP, sig) {
976
+ let sigOffset = 0;
977
+ const c = cP; // ctilde
978
+ const h = hP;
979
+ for (let i = 0; i < CTILDEBytes; ++i) {
980
+ c[i] = sig[i];
981
+ }
982
+ sigOffset += CTILDEBytes;
983
+
984
+ for (let i = 0; i < L; ++i) {
985
+ polyZUnpack(z.vec[i], sig, sigOffset + i * PolyZPackedBytes);
986
+ }
987
+ sigOffset += L * PolyZPackedBytes;
988
+
989
+ /* Decode h */
990
+ let k = 0;
991
+ for (let i = 0; i < K; ++i) {
992
+ for (let j = 0; j < N; ++j) {
993
+ h.vec[i].coeffs[j] = 0;
994
+ }
995
+
996
+ if (sig[sigOffset + OMEGA + i] < k || sig[sigOffset + OMEGA + i] > OMEGA) {
997
+ return 1;
998
+ }
999
+
1000
+ for (let j = k; j < sig[sigOffset + OMEGA + i]; ++j) {
1001
+ /* Coefficients are ordered for strong unforgeability */
1002
+ if (j > k && sig[sigOffset + j] <= sig[sigOffset + j - 1]) {
1003
+ return 1;
1004
+ }
1005
+ h.vec[i].coeffs[sig[sigOffset + j]] = 1;
1006
+ }
1007
+
1008
+ k = sig[sigOffset + OMEGA + i];
1009
+ }
1010
+
1011
+ /* Extra indices are zero for strong unforgeability */
1012
+ for (let j = k; j < OMEGA; ++j) {
1013
+ if (sig[sigOffset + j]) {
1014
+ return 1;
1015
+ }
1016
+ }
1017
+
1018
+ return 0;
1019
+ }
1020
+
1021
+ const randomBytes = pkg;
1022
+
1023
+ /**
1024
+ * Default signing context ("ZOND" in ASCII).
1025
+ * Used for domain separation per FIPS 204.
1026
+ * @constant {Uint8Array}
1027
+ */
1028
+ const DEFAULT_CTX = new Uint8Array([0x5a, 0x4f, 0x4e, 0x44]); // "ZOND"
1029
+
1030
+ /**
1031
+ * Convert hex string to Uint8Array
1032
+ * @param {string} hex - Hex-encoded string
1033
+ * @returns {Uint8Array} Decoded bytes
1034
+ * @private
1035
+ */
1036
+ function hexToBytes(hex) {
1037
+ const len = hex.length / 2;
1038
+ const result = new Uint8Array(len);
1039
+ for (let i = 0; i < len; i++) {
1040
+ result[i] = parseInt(hex.substr(i * 2, 2), 16);
1041
+ }
1042
+ return result;
1043
+ }
1044
+
1045
+ /**
1046
+ * Generate an ML-DSA-87 key pair.
1047
+ *
1048
+ * Key generation follows FIPS 204, using domain separator [K, L] during
1049
+ * seed expansion to ensure algorithm binding.
1050
+ *
1051
+ * @param {Uint8Array|null} passedSeed - Optional 32-byte seed for deterministic key generation.
1052
+ * Pass null for random key generation.
1053
+ * @param {Uint8Array} pk - Output buffer for public key (must be CryptoPublicKeyBytes = 2592 bytes)
1054
+ * @param {Uint8Array} sk - Output buffer for secret key (must be CryptoSecretKeyBytes = 4896 bytes)
1055
+ * @returns {Uint8Array} The seed used for key generation (useful when passedSeed is null)
1056
+ * @throws {Error} If pk/sk buffers are null or wrong size, or if seed is wrong size
1057
+ *
1058
+ * @example
1059
+ * const pk = new Uint8Array(CryptoPublicKeyBytes);
1060
+ * const sk = new Uint8Array(CryptoSecretKeyBytes);
1061
+ * const seed = cryptoSignKeypair(null, pk, sk);
1062
+ */
1063
+ function cryptoSignKeypair(passedSeed, pk, sk) {
1064
+ try {
1065
+ if (pk.length !== CryptoPublicKeyBytes) {
1066
+ throw new Error(`invalid pk length ${pk.length} | Expected length ${CryptoPublicKeyBytes}`);
1067
+ }
1068
+ if (sk.length !== CryptoSecretKeyBytes) {
1069
+ throw new Error(`invalid sk length ${sk.length} | Expected length ${CryptoSecretKeyBytes}`);
1070
+ }
1071
+ } catch (e) {
1072
+ if (e instanceof TypeError) {
1073
+ throw new Error(`pk/sk cannot be null`);
1074
+ } else {
1075
+ throw new Error(`${e.message}`);
1076
+ }
1077
+ }
1078
+
1079
+ // Validate seed length if provided
1080
+ if (passedSeed !== null && passedSeed !== undefined) {
1081
+ if (passedSeed.length !== SeedBytes) {
1082
+ throw new Error(`invalid seed length ${passedSeed.length} | Expected length ${SeedBytes}`);
1083
+ }
1084
+ }
1085
+
1086
+ const mat = new Array(K).fill().map(() => new PolyVecL());
1087
+ const s1 = new PolyVecL();
1088
+ const s2 = new PolyVecK();
1089
+ const t1 = new PolyVecK();
1090
+ const t0 = new PolyVecK();
1091
+
1092
+ // Expand seed -> rho(32), rhoPrime(64), key(32) with domain sep [K, L]
1093
+ const seed = passedSeed || randomBytes(SeedBytes);
1094
+
1095
+ const outputLength = 2 * SeedBytes + CRHBytes;
1096
+ const domainSep = new Uint8Array([K, L]);
1097
+ const seedBuf = shake256.create({}).update(seed).update(domainSep).xof(outputLength);
1098
+ const rho = seedBuf.slice(0, SeedBytes);
1099
+ const rhoPrime = seedBuf.slice(SeedBytes, SeedBytes + CRHBytes);
1100
+ const key = seedBuf.slice(SeedBytes + CRHBytes);
1101
+
1102
+ // Expand matrix
1103
+ polyVecMatrixExpand(mat, rho);
1104
+
1105
+ // Sample short vectors s1 and s2
1106
+ polyVecLUniformEta(s1, rhoPrime, 0);
1107
+ polyVecKUniformEta(s2, rhoPrime, L);
1108
+
1109
+ // Matrix-vector multiplication
1110
+ const s1hat = new PolyVecL();
1111
+ s1hat.copy(s1);
1112
+ polyVecLNTT(s1hat);
1113
+ polyVecMatrixPointWiseMontgomery(t1, mat, s1hat);
1114
+ polyVecKReduce(t1);
1115
+ polyVecKInvNTTToMont(t1);
1116
+
1117
+ // Add error vector s2
1118
+ polyVecKAdd(t1, t1, s2);
1119
+
1120
+ // Extract t1 and write public key
1121
+ polyVecKCAddQ(t1);
1122
+ polyVecKPower2round(t1, t0, t1);
1123
+ packPk(pk, rho, t1);
1124
+
1125
+ // Compute tr = SHAKE256(pk) (64 bytes) and write secret key
1126
+ const tr = shake256.create({}).update(pk).xof(TRBytes);
1127
+ packSk(sk, rho, tr, key, t0, s1, s2);
1128
+
1129
+ return seed;
1130
+ }
1131
+
1132
+ /**
1133
+ * Create a detached signature for a message with optional context.
1134
+ *
1135
+ * Uses the ML-DSA-87 (FIPS 204) signing algorithm with rejection sampling.
1136
+ * The context parameter provides domain separation as required by FIPS 204.
1137
+ *
1138
+ * @param {Uint8Array} sig - Output buffer for signature (must be at least CryptoBytes = 4627 bytes)
1139
+ * @param {string|Uint8Array} m - Message to sign (hex string or Uint8Array)
1140
+ * @param {Uint8Array} sk - Secret key (must be CryptoSecretKeyBytes = 4896 bytes)
1141
+ * @param {boolean} randomizedSigning - If true, use random nonce for hedged signing.
1142
+ * If false, use deterministic nonce derived from message and key.
1143
+ * @param {Uint8Array} [ctx=DEFAULT_CTX] - Context string for domain separation (max 255 bytes).
1144
+ * Defaults to "ZOND" for QRL compatibility.
1145
+ * @returns {number} 0 on success
1146
+ * @throws {Error} If sk is wrong size or context exceeds 255 bytes
1147
+ *
1148
+ * @example
1149
+ * const sig = new Uint8Array(CryptoBytes);
1150
+ * cryptoSignSignature(sig, message, sk, false);
1151
+ * // Or with custom context:
1152
+ * cryptoSignSignature(sig, message, sk, false, new Uint8Array([0x01, 0x02]));
1153
+ */
1154
+ function cryptoSignSignature(sig, m, sk, randomizedSigning, ctx = DEFAULT_CTX) {
1155
+ if (ctx.length > 255) throw new Error(`invalid context length: ${ctx.length} (max 255)`);
1156
+ if (sk.length !== CryptoSecretKeyBytes) {
1157
+ throw new Error(`invalid sk length ${sk.length} | Expected length ${CryptoSecretKeyBytes}`);
1158
+ }
1159
+
1160
+ const rho = new Uint8Array(SeedBytes);
1161
+ const tr = new Uint8Array(TRBytes);
1162
+ const key = new Uint8Array(SeedBytes);
1163
+ let rhoPrime = new Uint8Array(CRHBytes);
1164
+ let nonce = 0;
1165
+ const mat = Array(K)
1166
+ .fill()
1167
+ .map(() => new PolyVecL());
1168
+ const s1 = new PolyVecL();
1169
+ const y = new PolyVecL();
1170
+ const z = new PolyVecL();
1171
+ const t0 = new PolyVecK();
1172
+ const s2 = new PolyVecK();
1173
+ const w1 = new PolyVecK();
1174
+ const w0 = new PolyVecK();
1175
+ const h = new PolyVecK();
1176
+ const cp = new Poly();
1177
+
1178
+ unpackSk(rho, tr, key, t0, s1, s2, sk);
1179
+
1180
+ // pre = 0x00 || len(ctx) || ctx
1181
+ const pre = new Uint8Array(2 + ctx.length);
1182
+ pre[0] = 0;
1183
+ pre[1] = ctx.length;
1184
+ pre.set(ctx, 2);
1185
+
1186
+ // Convert hex message to bytes
1187
+ const mBytes = typeof m === 'string' ? hexToBytes(m) : m;
1188
+
1189
+ // mu = SHAKE256(tr || pre || m)
1190
+ const mu = shake256.create({}).update(tr).update(pre).update(mBytes).xof(CRHBytes);
1191
+
1192
+ // rhoPrime = SHAKE256(key || rnd || mu)
1193
+ const rnd = randomizedSigning ? randomBytes(RNDBytes) : new Uint8Array(RNDBytes);
1194
+ rhoPrime = shake256.create({}).update(key).update(rnd).update(mu).xof(CRHBytes);
1195
+
1196
+ polyVecMatrixExpand(mat, rho);
1197
+ polyVecLNTT(s1);
1198
+ polyVecKNTT(s2);
1199
+ polyVecKNTT(t0);
1200
+
1201
+ while (true) {
1202
+ polyVecLUniformGamma1(y, rhoPrime, nonce++);
1203
+ // Matrix-vector multiplication
1204
+ z.copy(y);
1205
+ polyVecLNTT(z);
1206
+ polyVecMatrixPointWiseMontgomery(w1, mat, z);
1207
+ polyVecKReduce(w1);
1208
+ polyVecKInvNTTToMont(w1);
1209
+
1210
+ // Decompose w and call the random oracle
1211
+ polyVecKCAddQ(w1);
1212
+ polyVecKDecompose(w1, w0, w1);
1213
+ polyVecKPackW1(sig, w1);
1214
+
1215
+ // ctilde = SHAKE256(mu || w1_packed) (64 bytes)
1216
+ const ctilde = shake256
1217
+ .create({})
1218
+ .update(mu)
1219
+ .update(sig.slice(0, K * PolyW1PackedBytes))
1220
+ .xof(CTILDEBytes);
1221
+
1222
+ polyChallenge(cp, ctilde);
1223
+ polyNTT(cp);
1224
+
1225
+ // Compute z, reject if it reveals secret
1226
+ polyVecLPointWisePolyMontgomery(z, cp, s1);
1227
+ polyVecLInvNTTToMont(z);
1228
+ polyVecLAdd(z, z, y);
1229
+ polyVecLReduce(z);
1230
+ if (polyVecLChkNorm(z, GAMMA1 - BETA) !== 0) {
1231
+ continue;
1232
+ }
1233
+
1234
+ polyVecKPointWisePolyMontgomery(h, cp, s2);
1235
+ polyVecKInvNTTToMont(h);
1236
+ polyVecKSub(w0, w0, h);
1237
+ polyVecKReduce(w0);
1238
+ if (polyVecKChkNorm(w0, GAMMA2 - BETA) !== 0) {
1239
+ continue;
1240
+ }
1241
+
1242
+ polyVecKPointWisePolyMontgomery(h, cp, t0);
1243
+ polyVecKInvNTTToMont(h);
1244
+ polyVecKReduce(h);
1245
+ if (polyVecKChkNorm(h, GAMMA2) !== 0) {
1246
+ continue;
1247
+ }
1248
+
1249
+ polyVecKAdd(w0, w0, h);
1250
+ const n = polyVecKMakeHint(h, w0, w1);
1251
+ if (n > OMEGA) {
1252
+ continue;
1253
+ }
1254
+
1255
+ packSig(sig, ctilde, z, h);
1256
+ return 0;
1257
+ }
1258
+ }
1259
+
1260
+ /**
1261
+ * Sign a message, returning signature concatenated with message.
1262
+ *
1263
+ * This is the combined sign operation that produces a "signed message" containing
1264
+ * both the signature and the original message (signature || message).
1265
+ *
1266
+ * @param {Uint8Array} msg - Message to sign
1267
+ * @param {Uint8Array} sk - Secret key (must be CryptoSecretKeyBytes = 4896 bytes)
1268
+ * @param {boolean} randomizedSigning - If true, use random nonce; if false, deterministic
1269
+ * @param {Uint8Array} [ctx=DEFAULT_CTX] - Context string for domain separation (max 255 bytes).
1270
+ * Defaults to "ZOND" for QRL compatibility.
1271
+ * @returns {Uint8Array} Signed message (CryptoBytes + msg.length bytes)
1272
+ * @throws {Error} If signing fails
1273
+ *
1274
+ * @example
1275
+ * const signedMsg = cryptoSign(message, sk, false);
1276
+ * // signedMsg contains: signature (4627 bytes) || message
1277
+ */
1278
+ function cryptoSign(msg, sk, randomizedSigning, ctx = DEFAULT_CTX) {
1279
+ const sm = new Uint8Array(CryptoBytes + msg.length);
1280
+ const mLen = msg.length;
1281
+ for (let i = 0; i < mLen; ++i) {
1282
+ sm[CryptoBytes + mLen - 1 - i] = msg[mLen - 1 - i];
1283
+ }
1284
+ const result = cryptoSignSignature(sm, msg, sk, randomizedSigning, ctx);
1285
+
1286
+ if (result !== 0) {
1287
+ throw new Error('failed to sign');
1288
+ }
1289
+ return sm;
1290
+ }
1291
+
1292
+ /**
1293
+ * Verify a detached signature with optional context.
1294
+ *
1295
+ * Performs constant-time verification to prevent timing side-channel attacks.
1296
+ * The context must match the one used during signing.
1297
+ *
1298
+ * @param {Uint8Array} sig - Signature to verify (must be CryptoBytes = 4627 bytes)
1299
+ * @param {string|Uint8Array} m - Message that was signed (hex string or Uint8Array)
1300
+ * @param {Uint8Array} pk - Public key (must be CryptoPublicKeyBytes = 2592 bytes)
1301
+ * @param {Uint8Array} [ctx=DEFAULT_CTX] - Context string used during signing (max 255 bytes).
1302
+ * Defaults to "ZOND" for QRL compatibility.
1303
+ * @returns {boolean} true if signature is valid, false otherwise
1304
+ *
1305
+ * @example
1306
+ * const isValid = cryptoSignVerify(signature, message, pk);
1307
+ * if (!isValid) {
1308
+ * throw new Error('Invalid signature');
1309
+ * }
1310
+ */
1311
+ function cryptoSignVerify(sig, m, pk, ctx = DEFAULT_CTX) {
1312
+ if (ctx.length > 255) return false;
1313
+ let i;
1314
+ const buf = new Uint8Array(K * PolyW1PackedBytes);
1315
+ const rho = new Uint8Array(SeedBytes);
1316
+ const mu = new Uint8Array(CRHBytes);
1317
+ const c = new Uint8Array(CTILDEBytes);
1318
+ const c2 = new Uint8Array(CTILDEBytes);
1319
+ const cp = new Poly();
1320
+ const mat = new Array(K).fill().map(() => new PolyVecL());
1321
+ const z = new PolyVecL();
1322
+ const t1 = new PolyVecK();
1323
+ const w1 = new PolyVecK();
1324
+ const h = new PolyVecK();
1325
+
1326
+ if (sig.length !== CryptoBytes) {
1327
+ return false;
1328
+ }
1329
+ if (pk.length !== CryptoPublicKeyBytes) {
1330
+ return false;
1331
+ }
1332
+
1333
+ unpackPk(rho, t1, pk);
1334
+ if (unpackSig(c, z, h, sig)) {
1335
+ return false;
1336
+ }
1337
+ if (polyVecLChkNorm(z, GAMMA1 - BETA)) {
1338
+ return false;
1339
+ }
1340
+
1341
+ /* Compute mu = SHAKE256(tr || pre || m) with tr = SHAKE256(pk) */
1342
+ const tr = shake256.create({}).update(pk).xof(TRBytes);
1343
+
1344
+ const pre = new Uint8Array(2 + ctx.length);
1345
+ pre[0] = 0;
1346
+ pre[1] = ctx.length;
1347
+ pre.set(ctx, 2);
1348
+
1349
+ // Convert hex message to bytes
1350
+ const mBytes = typeof m === 'string' ? hexToBytes(m) : m;
1351
+ const muFull = shake256.create({}).update(tr).update(pre).update(mBytes).xof(CRHBytes);
1352
+ mu.set(muFull);
1353
+
1354
+ /* Matrix-vector multiplication; compute Az - c2^dt1 */
1355
+ polyChallenge(cp, c);
1356
+ polyVecMatrixExpand(mat, rho);
1357
+
1358
+ polyVecLNTT(z);
1359
+ polyVecMatrixPointWiseMontgomery(w1, mat, z);
1360
+
1361
+ polyNTT(cp);
1362
+ polyVecKShiftL(t1);
1363
+ polyVecKNTT(t1);
1364
+ polyVecKPointWisePolyMontgomery(t1, cp, t1);
1365
+
1366
+ polyVecKSub(w1, w1, t1);
1367
+ polyVecKReduce(w1);
1368
+ polyVecKInvNTTToMont(w1);
1369
+
1370
+ /* Reconstruct w1 */
1371
+ polyVecKCAddQ(w1);
1372
+ polyVecKUseHint(w1, w1, h);
1373
+ polyVecKPackW1(buf, w1);
1374
+
1375
+ /* Call random oracle and verify challenge */
1376
+ const c2Hash = shake256.create({}).update(mu).update(buf).xof(CTILDEBytes);
1377
+ c2.set(c2Hash);
1378
+
1379
+ // Constant-time comparison to prevent timing attacks
1380
+ let diff = 0;
1381
+ for (i = 0; i < CTILDEBytes; ++i) {
1382
+ diff |= c[i] ^ c2[i];
1383
+ }
1384
+ return diff === 0;
1385
+ }
1386
+
1387
+ /**
1388
+ * Open a signed message (verify and extract message).
1389
+ *
1390
+ * This is the counterpart to cryptoSign(). It verifies the signature and
1391
+ * extracts the original message from a signed message.
1392
+ *
1393
+ * @param {Uint8Array} sm - Signed message (signature || message)
1394
+ * @param {Uint8Array} pk - Public key (must be CryptoPublicKeyBytes = 2592 bytes)
1395
+ * @param {Uint8Array} [ctx=DEFAULT_CTX] - Context string used during signing (max 255 bytes).
1396
+ * Defaults to "ZOND" for QRL compatibility.
1397
+ * @returns {Uint8Array|undefined} The original message if valid, undefined if verification fails
1398
+ *
1399
+ * @example
1400
+ * const message = cryptoSignOpen(signedMsg, pk);
1401
+ * if (message === undefined) {
1402
+ * throw new Error('Invalid signature');
1403
+ * }
1404
+ */
1405
+ function cryptoSignOpen(sm, pk, ctx = DEFAULT_CTX) {
1406
+ if (sm.length < CryptoBytes) {
1407
+ return undefined;
1408
+ }
1409
+
1410
+ const sig = sm.slice(0, CryptoBytes);
1411
+ const msg = sm.slice(CryptoBytes);
1412
+ if (!cryptoSignVerify(sig, msg, pk, ctx)) {
1413
+ return undefined;
1414
+ }
1415
+
1416
+ return msg;
1417
+ }
1418
+
1419
+ /**
1420
+ * Security utilities for ML-DSA-87
1421
+ *
1422
+ * IMPORTANT: JavaScript cannot guarantee secure memory zeroization.
1423
+ * See SECURITY.md for details on limitations.
1424
+ */
1425
+
1426
+ /**
1427
+ * Attempts to zero out a Uint8Array buffer.
1428
+ *
1429
+ * WARNING: This is a BEST-EFFORT operation. Due to JavaScript/JIT limitations:
1430
+ * - The write may be optimized away if the buffer is unused afterward
1431
+ * - Copies may exist in garbage collector memory
1432
+ * - Data may have been swapped to disk
1433
+ *
1434
+ * For high-security applications, consider native implementations (go-qrllib)
1435
+ * or hardware security modules.
1436
+ *
1437
+ * @param {Uint8Array} buffer - The buffer to zero
1438
+ * @returns {void}
1439
+ */
1440
+ function zeroize(buffer) {
1441
+ if (!(buffer instanceof Uint8Array)) {
1442
+ throw new TypeError('zeroize requires a Uint8Array');
1443
+ }
1444
+ // Use fill(0) for zeroing - best effort
1445
+ buffer.fill(0);
1446
+ // Additional volatile-like access to discourage optimization
1447
+ // (This is a hint to the JIT, not a guarantee)
1448
+ if (buffer.length > 0 && buffer[0] !== 0) {
1449
+ throw new Error('zeroize failed'); // Should never happen
1450
+ }
1451
+ }
1452
+
1453
+ /**
1454
+ * Checks if a buffer is all zeros.
1455
+ * Uses constant-time comparison to avoid timing leaks.
1456
+ *
1457
+ * @param {Uint8Array} buffer - The buffer to check
1458
+ * @returns {boolean} True if all bytes are zero
1459
+ */
1460
+ function isZero(buffer) {
1461
+ if (!(buffer instanceof Uint8Array)) {
1462
+ throw new TypeError('isZero requires a Uint8Array');
1463
+ }
1464
+ let acc = 0;
1465
+ for (let i = 0; i < buffer.length; i++) {
1466
+ acc |= buffer[i];
1467
+ }
1468
+ return acc === 0;
1469
+ }
1470
+
1471
+ export { BETA, CRHBytes, CTILDEBytes, CryptoBytes, CryptoPublicKeyBytes, CryptoSecretKeyBytes, D, ETA, GAMMA1, GAMMA2, K, KeccakState, L, N, OMEGA, Poly, PolyETAPackedBytes, PolyT0PackedBytes, PolyT1PackedBytes, PolyUniformETANBlocks, PolyUniformGamma1NBlocks, PolyUniformNBlocks, PolyVecHPackedBytes, PolyVecK, PolyVecL, PolyW1PackedBytes, PolyZPackedBytes, Q, QInv, RNDBytes, SeedBytes, Shake128Rate, Shake256Rate, Stream128BlockBytes, Stream256BlockBytes, TAU, TRBytes, cAddQ, cryptoSign, cryptoSignKeypair, cryptoSignOpen, cryptoSignSignature, cryptoSignVerify, decompose, invNTTToMont, isZero, makeHint, mldsaShake128StreamInit, mldsaShake256StreamInit, montgomeryReduce, ntt, packPk, packSig, packSk, polyAdd, polyCAddQ, polyChallenge, polyChkNorm, polyDecompose, polyEtaPack, polyEtaUnpack, polyInvNTTToMont, polyMakeHint, polyNTT, polyPointWiseMontgomery, polyPower2round, polyReduce, polyShiftL, polySub, polyT0Pack, polyT0Unpack, polyT1Pack, polyT1Unpack, polyUniform, polyUniformEta, polyUniformGamma1, polyUseHint, polyVecKAdd, polyVecKCAddQ, polyVecKChkNorm, polyVecKDecompose, polyVecKInvNTTToMont, polyVecKMakeHint, polyVecKNTT, polyVecKPackW1, polyVecKPointWisePolyMontgomery, polyVecKPower2round, polyVecKReduce, polyVecKShiftL, polyVecKSub, polyVecKUniformEta, polyVecKUseHint, polyVecLAdd, polyVecLChkNorm, polyVecLInvNTTToMont, polyVecLNTT, polyVecLPointWiseAccMontgomery, polyVecLPointWisePolyMontgomery, polyVecLReduce, polyVecLUniformEta, polyVecLUniformGamma1, polyVecMatrixExpand, polyVecMatrixPointWiseMontgomery, polyW1Pack, polyZPack, polyZUnpack, power2round, reduce32, rejEta, rejUniform, shake128Absorb, shake128Finalize, shake128Init, shake128SqueezeBlocks, shake256Absorb, shake256Finalize, shake256Init, shake256SqueezeBlocks, unpackPk, unpackSig, unpackSk, useHint, zeroize, zetas };