@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.
- package/README.md +11 -0
- package/dist/cjs/mldsa87.js +1596 -0
- package/dist/cjs/package.json +3 -0
- package/dist/mjs/mldsa87.js +1471 -0
- package/dist/mjs/package.json +3 -0
- package/package.json +64 -0
- package/src/index.d.ts +204 -0
- package/src/index.js +11 -0
|
@@ -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 };
|