@noble/post-quantum 0.5.4 → 0.6.1
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 +86 -42
- package/_crystals.d.ts +90 -4
- package/_crystals.d.ts.map +1 -1
- package/_crystals.js +77 -6
- package/_crystals.js.map +1 -1
- package/falcon.d.ts +84 -0
- package/falcon.d.ts.map +1 -0
- package/falcon.js +2385 -0
- package/falcon.js.map +1 -0
- package/hybrid.d.ts +194 -24
- package/hybrid.d.ts.map +1 -1
- package/hybrid.js +401 -77
- package/hybrid.js.map +1 -1
- package/index.js +8 -0
- package/index.js.map +1 -1
- package/ml-dsa.d.ts +29 -8
- package/ml-dsa.d.ts.map +1 -1
- package/ml-dsa.js +154 -78
- package/ml-dsa.js.map +1 -1
- package/ml-kem.d.ts +31 -7
- package/ml-kem.d.ts.map +1 -1
- package/ml-kem.js +194 -75
- package/ml-kem.js.map +1 -1
- package/package.json +15 -8
- package/slh-dsa.d.ts +137 -34
- package/slh-dsa.d.ts.map +1 -1
- package/slh-dsa.js +189 -68
- package/slh-dsa.js.map +1 -1
- package/src/_crystals.ts +135 -24
- package/src/falcon.ts +2503 -0
- package/src/hybrid.ts +515 -144
- package/src/index.ts +8 -0
- package/src/ml-dsa.ts +263 -138
- package/src/ml-kem.ts +240 -97
- package/src/slh-dsa.ts +391 -153
- package/src/utils.ts +491 -46
- package/utils.d.ts +362 -24
- package/utils.d.ts.map +1 -1
- package/utils.js +273 -20
- package/utils.js.map +1 -1
package/ml-dsa.js
CHANGED
|
@@ -18,25 +18,43 @@ function validateInternalOpts(opts) {
|
|
|
18
18
|
abool(opts.externalMu, 'opts.externalMu');
|
|
19
19
|
}
|
|
20
20
|
// Constants
|
|
21
|
+
// FIPS 204 fixes ML-DSA over R = Z[X]/(X^256 + 1), so every polynomial has 256 coefficients.
|
|
21
22
|
const N = 256;
|
|
22
23
|
// 2**23 − 2**13 + 1, 23 bits: multiply will be 46. We have enough precision in JS to avoid bigints
|
|
23
24
|
const Q = 8380417;
|
|
25
|
+
// FIPS 204 §2.5 / Table 1 fixes zeta = 1753 as the 512th root of unity used by ML-DSA's NTT.
|
|
24
26
|
const ROOT_OF_UNITY = 1753;
|
|
25
27
|
// f = 256**−1 mod q, pow(256, -1, q) = 8347681 (python3)
|
|
26
28
|
const F = 8347681;
|
|
29
|
+
// FIPS 204 Table 1 / §7.4 fixes d = 13 dropped low bits for Power2Round on t.
|
|
27
30
|
const D = 13;
|
|
31
|
+
// FIPS 204 Table 1 fixes gamma2 to (q-1)/88 for ML-DSA-44 and (q-1)/32 for ML-DSA-65/87;
|
|
32
|
+
// §7.4 then uses alpha = 2*gamma2 for Decompose / MakeHint / UseHint.
|
|
28
33
|
// Dilithium is kinda parametrized over GAMMA2, but everything will break with any other value.
|
|
29
34
|
const GAMMA2_1 = Math.floor((Q - 1) / 88) | 0;
|
|
30
35
|
const GAMMA2_2 = Math.floor((Q - 1) / 32) | 0;
|
|
31
36
|
/** Internal params for different versions of ML-DSA */
|
|
32
37
|
// prettier-ignore
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
+
/** Built-in ML-DSA parameter presets keyed by security categories `2/3/5`
|
|
39
|
+
* for `ml_dsa44` / `ml_dsa65` / `ml_dsa87`.
|
|
40
|
+
* This is only the Table 1 subset used directly here: `BETA = TAU * ETA` is derived later,
|
|
41
|
+
* while `C_TILDE_BYTES`, `TR_BYTES`, `CRH_BYTES`, and `securityLevel` live in the preset wrappers.
|
|
42
|
+
*/
|
|
43
|
+
export const PARAMS = /* @__PURE__ */ (() => Object.freeze({
|
|
44
|
+
2: Object.freeze({
|
|
45
|
+
K: 4, L: 4, D, GAMMA1: 2 ** 17, GAMMA2: GAMMA2_1, TAU: 39, ETA: 2, OMEGA: 80
|
|
46
|
+
}),
|
|
47
|
+
3: Object.freeze({
|
|
48
|
+
K: 6, L: 5, D, GAMMA1: 2 ** 19, GAMMA2: GAMMA2_2, TAU: 49, ETA: 4, OMEGA: 55
|
|
49
|
+
}),
|
|
50
|
+
5: Object.freeze({
|
|
51
|
+
K: 8, L: 7, D, GAMMA1: 2 ** 19, GAMMA2: GAMMA2_2, TAU: 60, ETA: 2, OMEGA: 75
|
|
52
|
+
}),
|
|
53
|
+
}))();
|
|
38
54
|
const newPoly = (n) => new Int32Array(n);
|
|
39
|
-
|
|
55
|
+
// Shared CRYSTALS helper in the ML-DSA branch: non-Kyber mode, 8-bit bit-reversal,
|
|
56
|
+
// and Int32Array polys because ordinary-form coefficients can be negative / centered.
|
|
57
|
+
const crystals = /* @__PURE__ */ genCrystals({
|
|
40
58
|
N,
|
|
41
59
|
Q,
|
|
42
60
|
F,
|
|
@@ -46,45 +64,61 @@ const { mod, smod, NTT, bitsCoder } = genCrystals({
|
|
|
46
64
|
brvBits: 8,
|
|
47
65
|
});
|
|
48
66
|
const id = (n) => n;
|
|
49
|
-
|
|
67
|
+
// compress()/verify() must be compatible in both directions:
|
|
68
|
+
// wrap the shared d-bit packer with the FIPS 204 SimpleBitPack / BitPack coefficient maps.
|
|
69
|
+
// malformed-input rejection only happens through the optional verify hook.
|
|
70
|
+
const polyCoder = (d, compress = id, verify = id) => crystals.bitsCoder(d, {
|
|
50
71
|
encode: (i) => compress(verify(i)),
|
|
51
72
|
decode: (i) => verify(compress(i)),
|
|
52
73
|
});
|
|
53
|
-
|
|
74
|
+
// Mutates `a` in place; callers must pass same-length polynomials.
|
|
75
|
+
const polyAdd = (a_, b_) => {
|
|
76
|
+
const a = a_;
|
|
77
|
+
const b = b_;
|
|
54
78
|
for (let i = 0; i < a.length; i++)
|
|
55
|
-
a[i] = mod(a[i] + b[i]);
|
|
79
|
+
a[i] = crystals.mod(a[i] + b[i]);
|
|
56
80
|
return a;
|
|
57
81
|
};
|
|
58
|
-
|
|
82
|
+
// Mutates `a` in place; callers must pass same-length polynomials.
|
|
83
|
+
const polySub = (a_, b_) => {
|
|
84
|
+
const a = a_;
|
|
85
|
+
const b = b_;
|
|
59
86
|
for (let i = 0; i < a.length; i++)
|
|
60
|
-
a[i] = mod(a[i] - b[i]);
|
|
87
|
+
a[i] = crystals.mod(a[i] - b[i]);
|
|
61
88
|
return a;
|
|
62
89
|
};
|
|
63
|
-
|
|
90
|
+
// Mutates `p` in place and assumes it is a decoded `t1`-range polynomial.
|
|
91
|
+
const polyShiftl = (p_) => {
|
|
92
|
+
const p = p_;
|
|
64
93
|
for (let i = 0; i < N; i++)
|
|
65
94
|
p[i] <<= D;
|
|
66
95
|
return p;
|
|
67
96
|
};
|
|
68
|
-
const polyChknorm = (
|
|
69
|
-
|
|
97
|
+
const polyChknorm = (p_, B) => {
|
|
98
|
+
const p = p_;
|
|
99
|
+
// FIPS 204 Algorithms 7 and 8 express the same centered-norm check with explicit inequalities.
|
|
70
100
|
for (let i = 0; i < N; i++)
|
|
71
|
-
if (Math.abs(smod(p[i])) >= B)
|
|
101
|
+
if (Math.abs(crystals.smod(p[i])) >= B)
|
|
72
102
|
return true;
|
|
73
103
|
return false;
|
|
74
104
|
};
|
|
75
|
-
|
|
105
|
+
// Both inputs must already be in NTT / `T_q` form.
|
|
106
|
+
const MultiplyNTTs = (a_, b_) => {
|
|
107
|
+
const a = a_;
|
|
108
|
+
const b = b_;
|
|
76
109
|
// NOTE: we don't use montgomery reduction in code, since it requires 64 bit ints,
|
|
77
110
|
// which is not available in JS. mod(a[i] * b[i]) is ok, since Q is 23 bit,
|
|
78
111
|
// which means a[i] * b[i] is 46 bit, which is safe to use in JS. (number is 53 bits).
|
|
79
112
|
// Barrett reduction is slower than mod :(
|
|
80
113
|
const c = newPoly(N);
|
|
81
114
|
for (let i = 0; i < a.length; i++)
|
|
82
|
-
c[i] = mod(a[i] * b[i]);
|
|
115
|
+
c[i] = crystals.mod(a[i] * b[i]);
|
|
83
116
|
return c;
|
|
84
117
|
};
|
|
85
118
|
// Return poly in NTT representation
|
|
86
|
-
function RejNTTPoly(
|
|
87
|
-
|
|
119
|
+
function RejNTTPoly(xof_) {
|
|
120
|
+
const xof = xof_;
|
|
121
|
+
// Samples a polynomial ∈ Tq. xof() must return byte lengths divisible by 3.
|
|
88
122
|
const r = newPoly(N);
|
|
89
123
|
// NOTE: we can represent 3xu24 as 4xu32, but it doesn't improve perf :(
|
|
90
124
|
for (let j = 0; j < N;) {
|
|
@@ -92,6 +126,7 @@ function RejNTTPoly(xof) {
|
|
|
92
126
|
if (b.length % 3)
|
|
93
127
|
throw new Error('RejNTTPoly: unaligned block');
|
|
94
128
|
for (let i = 0; j < N && i <= b.length - 3; i += 3) {
|
|
129
|
+
// FIPS 204 Algorithm 14 clears the top bit of b2 before forming the 23-bit candidate.
|
|
95
130
|
const t = (b[i + 0] | (b[i + 1] << 8) | (b[i + 2] << 16)) & 0x7fffff; // 3 bytes
|
|
96
131
|
if (t < Q)
|
|
97
132
|
r[j++] = t;
|
|
@@ -99,7 +134,10 @@ function RejNTTPoly(xof) {
|
|
|
99
134
|
}
|
|
100
135
|
return r;
|
|
101
136
|
}
|
|
102
|
-
|
|
137
|
+
// Instantiate one ML-DSA parameter set from the Table 1 lattice constants plus the
|
|
138
|
+
// Table 2 byte lengths / hash-width choices used by the public wrappers below.
|
|
139
|
+
function getDilithium(opts_) {
|
|
140
|
+
const opts = opts_;
|
|
103
141
|
const { K, L, GAMMA1, GAMMA2, TAU, ETA, OMEGA } = opts;
|
|
104
142
|
const { CRH_BYTES, TR_BYTES, C_TILDE_BYTES, XOF128, XOF256, securityLevel } = opts;
|
|
105
143
|
if (![2, 4].includes(ETA))
|
|
@@ -111,8 +149,9 @@ function getDilithium(opts) {
|
|
|
111
149
|
const BETA = TAU * ETA;
|
|
112
150
|
const decompose = (r) => {
|
|
113
151
|
// Decomposes r into (r1, r0) such that r ≡ r1(2γ2) + r0 mod q.
|
|
114
|
-
const rPlus = mod(r);
|
|
115
|
-
const r0 = smod(rPlus, 2 * GAMMA2) | 0;
|
|
152
|
+
const rPlus = crystals.mod(r);
|
|
153
|
+
const r0 = crystals.smod(rPlus, 2 * GAMMA2) | 0;
|
|
154
|
+
// FIPS 204 Algorithm 36 folds the top bucket `q-1` back to `(r1, r0) = (0, r0-1)`.
|
|
116
155
|
if (rPlus - r0 === Q - 1)
|
|
117
156
|
return { r1: 0 | 0, r0: (r0 - 1) | 0 };
|
|
118
157
|
const r1 = Math.floor((rPlus - r0) / (2 * GAMMA2)) | 0;
|
|
@@ -122,6 +161,10 @@ function getDilithium(opts) {
|
|
|
122
161
|
const LowBits = (r) => decompose(r).r0;
|
|
123
162
|
const MakeHint = (z, r) => {
|
|
124
163
|
// Compute hint bit indicating whether adding z to r alters the high bits of r.
|
|
164
|
+
// FIPS 204 §6.2 also permits the Section 5.1 alternative from [6], which uses the
|
|
165
|
+
// transformed low-bits/high-bits state at this call site instead of Algorithm 39 literally.
|
|
166
|
+
// This optimized predicate only applies to those transformed Section 5.1 inputs; it is
|
|
167
|
+
// not a drop-in replacement for Algorithm 39 on arbitrary `(z, r)` pairs.
|
|
125
168
|
// From dilithium code
|
|
126
169
|
const res0 = z <= GAMMA2 || z > Q - GAMMA2 || (z === Q - GAMMA2 && r === 0) ? 0 : 1;
|
|
127
170
|
// from FIPS204:
|
|
@@ -131,8 +174,9 @@ function getDilithium(opts) {
|
|
|
131
174
|
// But they return different results! However, decompose is same.
|
|
132
175
|
// So, either there is a bug in Dilithium ref implementation or in FIPS204.
|
|
133
176
|
// For now, lets use dilithium one, so test vectors can be passed.
|
|
134
|
-
//
|
|
135
|
-
//
|
|
177
|
+
// The round-3 Dilithium / ML-DSA code uses the same low-bits / high-bits convention after
|
|
178
|
+
// `r0 += ct0`.
|
|
179
|
+
// See dilithium-py README section "Optimising decomposition and making hints".
|
|
136
180
|
return res0;
|
|
137
181
|
};
|
|
138
182
|
const UseHint = (h, r) => {
|
|
@@ -142,18 +186,19 @@ function getDilithium(opts) {
|
|
|
142
186
|
// 3: if h = 1 and r0 > 0 return (r1 + 1) mod m
|
|
143
187
|
// 4: if h = 1 and r0 ≤ 0 return (r1 − 1) mod m
|
|
144
188
|
if (h === 1)
|
|
145
|
-
return r0 > 0 ? mod(r1 + 1, m) | 0 : mod(r1 - 1, m) | 0;
|
|
189
|
+
return r0 > 0 ? crystals.mod(r1 + 1, m) | 0 : crystals.mod(r1 - 1, m) | 0;
|
|
146
190
|
return r1 | 0;
|
|
147
191
|
};
|
|
148
192
|
const Power2Round = (r) => {
|
|
149
193
|
// Decomposes r into (r1, r0) such that r ≡ r1*(2**d) + r0 mod q.
|
|
150
|
-
const rPlus = mod(r);
|
|
151
|
-
const r0 = smod(rPlus, 2 ** D) | 0;
|
|
194
|
+
const rPlus = crystals.mod(r);
|
|
195
|
+
const r0 = crystals.smod(rPlus, 2 ** D) | 0;
|
|
152
196
|
return { r1: Math.floor((rPlus - r0) / 2 ** D) | 0, r0 };
|
|
153
197
|
};
|
|
154
198
|
const hintCoder = {
|
|
155
199
|
bytesLen: OMEGA + K,
|
|
156
|
-
encode: (
|
|
200
|
+
encode: (h_) => {
|
|
201
|
+
const h = h_;
|
|
157
202
|
if (h === false)
|
|
158
203
|
throw new Error('hint.encode: hint is false'); // should never happen
|
|
159
204
|
const res = new Uint8Array(OMEGA + K);
|
|
@@ -194,7 +239,7 @@ function getDilithium(opts) {
|
|
|
194
239
|
const T0Coder = polyCoder(13, (i) => (1 << (D - 1)) - i);
|
|
195
240
|
const T1Coder = polyCoder(10);
|
|
196
241
|
// Requires smod. Need to fix!
|
|
197
|
-
const ZCoder = polyCoder(GAMMA1 === 1 << 17 ? 18 : 20, (i) => smod(GAMMA1 - i));
|
|
242
|
+
const ZCoder = polyCoder(GAMMA1 === 1 << 17 ? 18 : 20, (i) => crystals.smod(GAMMA1 - i));
|
|
198
243
|
const W1Coder = polyCoder(GAMMA2 === GAMMA2_1 ? 6 : 4);
|
|
199
244
|
const W1Vec = vecCoder(W1Coder, K);
|
|
200
245
|
// Main structures
|
|
@@ -204,8 +249,11 @@ function getDilithium(opts) {
|
|
|
204
249
|
const CoefFromHalfByte = ETA === 2
|
|
205
250
|
? (n) => (n < 15 ? 2 - (n % 5) : false)
|
|
206
251
|
: (n) => (n < 9 ? 4 - n : false);
|
|
207
|
-
// Return poly in
|
|
208
|
-
|
|
252
|
+
// Return poly in ordinary representation.
|
|
253
|
+
// This helper returns ordinary-form `[-ETA, ETA]` coefficients for ExpandS; callers apply
|
|
254
|
+
// `NTT.encode()` later when needed.
|
|
255
|
+
function RejBoundedPoly(xof_) {
|
|
256
|
+
const xof = xof_;
|
|
209
257
|
// Samples an element a ∈ Rq with coeffcients in [−η, η] computed via rejection sampling from ρ.
|
|
210
258
|
const r = newPoly(N);
|
|
211
259
|
for (let j = 0; j < N;) {
|
|
@@ -228,6 +276,8 @@ function getDilithium(opts) {
|
|
|
228
276
|
const s = shake256.create({}).update(seed);
|
|
229
277
|
const buf = new Uint8Array(shake256.blockLen);
|
|
230
278
|
s.xofInto(buf);
|
|
279
|
+
// FIPS 204 Algorithm 29 uses the first 8 squeezed bytes as the 64 sign bits `h`,
|
|
280
|
+
// then rejection-samples coefficient positions from the remaining XOF stream.
|
|
231
281
|
const masks = buf.slice(0, 8);
|
|
232
282
|
for (let i = N - TAU, pos = 8, maskPos = 0, maskBit = 0; i < N; i++) {
|
|
233
283
|
let b = i + 1;
|
|
@@ -247,7 +297,8 @@ function getDilithium(opts) {
|
|
|
247
297
|
}
|
|
248
298
|
return pre;
|
|
249
299
|
};
|
|
250
|
-
const polyPowerRound = (
|
|
300
|
+
const polyPowerRound = (p_) => {
|
|
301
|
+
const p = p_;
|
|
251
302
|
const res0 = newPoly(N);
|
|
252
303
|
const res1 = newPoly(N);
|
|
253
304
|
for (let i = 0; i < p.length; i++) {
|
|
@@ -257,12 +308,18 @@ function getDilithium(opts) {
|
|
|
257
308
|
}
|
|
258
309
|
return { r0: res0, r1: res1 };
|
|
259
310
|
};
|
|
260
|
-
const polyUseHint = (
|
|
311
|
+
const polyUseHint = (u_, h_) => {
|
|
312
|
+
const u = u_;
|
|
313
|
+
const h = h_;
|
|
314
|
+
// In-place on `u`: verification only needs the recovered high bits, so reuse the
|
|
315
|
+
// temporary `wApprox` buffer instead of allocating another polynomial.
|
|
261
316
|
for (let i = 0; i < N; i++)
|
|
262
317
|
u[i] = UseHint(h[i], u[i]);
|
|
263
318
|
return u;
|
|
264
319
|
};
|
|
265
|
-
const polyMakeHint = (
|
|
320
|
+
const polyMakeHint = (a_, b_) => {
|
|
321
|
+
const a = a_;
|
|
322
|
+
const b = b_;
|
|
266
323
|
const v = newPoly(N);
|
|
267
324
|
let cnt = 0;
|
|
268
325
|
for (let i = 0; i < N; i++) {
|
|
@@ -275,15 +332,15 @@ function getDilithium(opts) {
|
|
|
275
332
|
const signRandBytes = 32;
|
|
276
333
|
const seedCoder = splitCoder('seed', 32, 64, 32);
|
|
277
334
|
// API & argument positions are exactly as in FIPS204.
|
|
278
|
-
const internal = {
|
|
279
|
-
info: { type: 'internal-ml-dsa' },
|
|
280
|
-
lengths: {
|
|
335
|
+
const internal = Object.freeze({
|
|
336
|
+
info: Object.freeze({ type: 'internal-ml-dsa' }),
|
|
337
|
+
lengths: Object.freeze({
|
|
281
338
|
secretKey: secretCoder.bytesLen,
|
|
282
339
|
publicKey: publicCoder.bytesLen,
|
|
283
340
|
seed: 32,
|
|
284
341
|
signature: sigCoder.bytesLen,
|
|
285
342
|
signRand: signRandBytes,
|
|
286
|
-
},
|
|
343
|
+
}),
|
|
287
344
|
keygen: (seed) => {
|
|
288
345
|
// H(𝜉||IntegerToBytes(𝑘, 1)||IntegerToBytes(ℓ, 1), 128) 2: ▷ expand seed
|
|
289
346
|
const seedDst = new Uint8Array(32 + 2);
|
|
@@ -304,7 +361,7 @@ function getDilithium(opts) {
|
|
|
304
361
|
const s2 = [];
|
|
305
362
|
for (let i = L; i < L + K; i++)
|
|
306
363
|
s2.push(RejBoundedPoly(xofPrime.get(i & 0xff, (i >> 8) & 0xff)));
|
|
307
|
-
const s1Hat = s1.map((i) => NTT.encode(i.slice()));
|
|
364
|
+
const s1Hat = s1.map((i) => crystals.NTT.encode(i.slice()));
|
|
308
365
|
const t0 = [];
|
|
309
366
|
const t1 = [];
|
|
310
367
|
const xof = XOF128(rho);
|
|
@@ -316,26 +373,33 @@ function getDilithium(opts) {
|
|
|
316
373
|
const aij = RejNTTPoly(xof.get(j, i)); // super slow!
|
|
317
374
|
polyAdd(t, MultiplyNTTs(aij, s1Hat[j]));
|
|
318
375
|
}
|
|
319
|
-
NTT.decode(t);
|
|
376
|
+
crystals.NTT.decode(t);
|
|
320
377
|
const { r0, r1 } = polyPowerRound(polyAdd(t, s2[i])); // (t1, t0) ← Power2Round(t, d)
|
|
321
378
|
t0.push(r0);
|
|
322
379
|
t1.push(r1);
|
|
323
380
|
}
|
|
324
381
|
const publicKey = publicCoder.encode([rho, t1]); // pk ← pkEncode(ρ, t1)
|
|
325
382
|
const tr = shake256(publicKey, { dkLen: TR_BYTES }); // tr ← H(BytesToBits(pk), 512)
|
|
326
|
-
|
|
383
|
+
// sk ← skEncode(ρ, K,tr, s1, s2, t0)
|
|
384
|
+
const secretKey = secretCoder.encode([rho, K_, tr, s1, s2, t0]);
|
|
327
385
|
xof.clean();
|
|
328
386
|
xofPrime.clean();
|
|
329
387
|
// STATS
|
|
330
|
-
// Kyber512:
|
|
331
|
-
//
|
|
388
|
+
// Kyber512: { calls: 4, xofs: 12 }, Kyber768: { calls: 9, xofs: 27 },
|
|
389
|
+
// Kyber1024: { calls: 16, xofs: 48 }
|
|
390
|
+
// DSA44: { calls: 24, xofs: 24 }, DSA65: { calls: 41, xofs: 41 },
|
|
391
|
+
// DSA87: { calls: 71, xofs: 71 }
|
|
332
392
|
cleanBytes(rho, rhoPrime, K_, s1, s2, s1Hat, t, t0, t1, tr, seedDst);
|
|
333
|
-
return {
|
|
393
|
+
return {
|
|
394
|
+
publicKey: publicKey,
|
|
395
|
+
secretKey: secretKey,
|
|
396
|
+
};
|
|
334
397
|
},
|
|
335
398
|
getPublicKey: (secretKey) => {
|
|
336
|
-
|
|
399
|
+
// (ρ, K,tr, s1, s2, t0) ← skDecode(sk)
|
|
400
|
+
const [rho, _K, _tr, s1, s2, _t0] = secretCoder.decode(secretKey);
|
|
337
401
|
const xof = XOF128(rho);
|
|
338
|
-
const s1Hat = s1.map((p) => NTT.encode(p.slice()));
|
|
402
|
+
const s1Hat = s1.map((p) => crystals.NTT.encode(p.slice()));
|
|
339
403
|
const t1 = [];
|
|
340
404
|
const tmp = newPoly(N);
|
|
341
405
|
for (let i = 0; i < K; i++) {
|
|
@@ -344,7 +408,7 @@ function getDilithium(opts) {
|
|
|
344
408
|
const aij = RejNTTPoly(xof.get(j, i)); // A_ij in NTT
|
|
345
409
|
polyAdd(tmp, MultiplyNTTs(aij, s1Hat[j])); // += A_ij * s1_j
|
|
346
410
|
}
|
|
347
|
-
NTT.decode(tmp); // NTT⁻¹
|
|
411
|
+
crystals.NTT.decode(tmp); // NTT⁻¹
|
|
348
412
|
polyAdd(tmp, s2[i]); // t_i = A·s1 + s2
|
|
349
413
|
const { r1 } = polyPowerRound(tmp); // r1 = t1, r0 ≈ t0
|
|
350
414
|
t1.push(r1);
|
|
@@ -360,7 +424,8 @@ function getDilithium(opts) {
|
|
|
360
424
|
let { extraEntropy: random, externalMu = false } = opts;
|
|
361
425
|
// This part can be pre-cached per secretKey, but there is only minor performance improvement,
|
|
362
426
|
// since we re-use a lot of variables to computation.
|
|
363
|
-
|
|
427
|
+
// (ρ, K,tr, s1, s2, t0) ← skDecode(sk)
|
|
428
|
+
const [rho, _K, tr, s1, s2, t0] = secretCoder.decode(secretKey);
|
|
364
429
|
// Cache matrix to avoid re-compute later
|
|
365
430
|
const A = []; // A ← ExpandA(ρ)
|
|
366
431
|
const xof = XOF128(rho);
|
|
@@ -372,15 +437,17 @@ function getDilithium(opts) {
|
|
|
372
437
|
}
|
|
373
438
|
xof.clean();
|
|
374
439
|
for (let i = 0; i < L; i++)
|
|
375
|
-
NTT.encode(s1[i]); // sˆ1 ← NTT(s1)
|
|
440
|
+
crystals.NTT.encode(s1[i]); // sˆ1 ← NTT(s1)
|
|
376
441
|
for (let i = 0; i < K; i++) {
|
|
377
|
-
NTT.encode(s2[i]); // sˆ2 ← NTT(s2)
|
|
378
|
-
NTT.encode(t0[i]); // tˆ0 ← NTT(t0)
|
|
442
|
+
crystals.NTT.encode(s2[i]); // sˆ2 ← NTT(s2)
|
|
443
|
+
crystals.NTT.encode(t0[i]); // tˆ0 ← NTT(t0)
|
|
379
444
|
}
|
|
380
445
|
// This part is per msg
|
|
381
446
|
const mu = externalMu
|
|
382
447
|
? msg
|
|
383
|
-
:
|
|
448
|
+
: // 6: µ ← H(tr||M, 512)
|
|
449
|
+
// ▷ Compute message representative µ
|
|
450
|
+
shake256.create({ dkLen: CRH_BYTES }).update(tr).update(msg).digest();
|
|
384
451
|
// Compute private random seed
|
|
385
452
|
const rnd = random === false
|
|
386
453
|
? new Uint8Array(32)
|
|
@@ -402,14 +469,14 @@ function getDilithium(opts) {
|
|
|
402
469
|
// y ← ExpandMask(ρ , κ)
|
|
403
470
|
for (let i = 0; i < L; i++, kappa++)
|
|
404
471
|
y.push(ZCoder.decode(x256.get(kappa & 0xff, kappa >> 8)()));
|
|
405
|
-
const z = y.map((i) => NTT.encode(i.slice()));
|
|
472
|
+
const z = y.map((i) => crystals.NTT.encode(i.slice()));
|
|
406
473
|
const w = [];
|
|
407
474
|
for (let i = 0; i < K; i++) {
|
|
408
475
|
// w ← NTT−1(A ◦ NTT(y))
|
|
409
476
|
const wi = newPoly(N);
|
|
410
477
|
for (let j = 0; j < L; j++)
|
|
411
478
|
polyAdd(wi, MultiplyNTTs(A[i][j], z[j]));
|
|
412
|
-
NTT.decode(wi);
|
|
479
|
+
crystals.NTT.decode(wi);
|
|
413
480
|
w.push(wi);
|
|
414
481
|
}
|
|
415
482
|
const w1 = w.map((j) => j.map(HighBits)); // w1 ← HighBits(w)
|
|
@@ -420,11 +487,12 @@ function getDilithium(opts) {
|
|
|
420
487
|
.update(W1Vec.encode(w1))
|
|
421
488
|
.digest();
|
|
422
489
|
// Verifer’s challenge
|
|
423
|
-
|
|
490
|
+
// c ← SampleInBall(c˜1); cˆ ← NTT(c)
|
|
491
|
+
const cHat = crystals.NTT.encode(SampleInBall(cTilde));
|
|
424
492
|
// ⟨⟨cs1⟩⟩ ← NTT−1(cˆ◦ sˆ1)
|
|
425
493
|
const cs1 = s1.map((i) => MultiplyNTTs(i, cHat));
|
|
426
494
|
for (let i = 0; i < L; i++) {
|
|
427
|
-
polyAdd(NTT.decode(cs1[i]), y[i]); // z ← y + ⟨⟨cs1⟩⟩
|
|
495
|
+
polyAdd(crystals.NTT.decode(cs1[i]), y[i]); // z ← y + ⟨⟨cs1⟩⟩
|
|
428
496
|
if (polyChknorm(cs1[i], GAMMA1 - BETA))
|
|
429
497
|
continue main_loop; // ||z||∞ ≥ γ1 − β
|
|
430
498
|
}
|
|
@@ -432,11 +500,11 @@ function getDilithium(opts) {
|
|
|
432
500
|
let cnt = 0;
|
|
433
501
|
const h = [];
|
|
434
502
|
for (let i = 0; i < K; i++) {
|
|
435
|
-
const cs2 = NTT.decode(MultiplyNTTs(s2[i], cHat)); // ⟨⟨cs2⟩⟩ ← NTT−1(cˆ◦ sˆ2)
|
|
503
|
+
const cs2 = crystals.NTT.decode(MultiplyNTTs(s2[i], cHat)); // ⟨⟨cs2⟩⟩ ← NTT−1(cˆ◦ sˆ2)
|
|
436
504
|
const r0 = polySub(w[i], cs2).map(LowBits); // r0 ← LowBits(w − ⟨⟨cs2⟩⟩)
|
|
437
505
|
if (polyChknorm(r0, GAMMA2 - BETA))
|
|
438
506
|
continue main_loop; // ||r0||∞ ≥ γ2 − β
|
|
439
|
-
const ct0 = NTT.decode(MultiplyNTTs(t0[i], cHat)); // ⟨⟨ct0⟩⟩ ← NTT−1(cˆ◦ tˆ0)
|
|
507
|
+
const ct0 = crystals.NTT.decode(MultiplyNTTs(t0[i], cHat)); // ⟨⟨ct0⟩⟩ ← NTT−1(cˆ◦ tˆ0)
|
|
440
508
|
if (polyChknorm(ct0, GAMMA2))
|
|
441
509
|
continue main_loop;
|
|
442
510
|
polyAdd(r0, ct0);
|
|
@@ -450,7 +518,12 @@ function getDilithium(opts) {
|
|
|
450
518
|
x256.clean();
|
|
451
519
|
const res = sigCoder.encode([cTilde, cs1, h]); // σ ← sigEncode(c˜, z mod±q, h)
|
|
452
520
|
// rho, _K, tr is subarray of secretKey, cannot clean.
|
|
453
|
-
cleanBytes(cTilde, cs1, h, cHat, w1, w, z, y, rhoprime,
|
|
521
|
+
cleanBytes(cTilde, cs1, h, cHat, w1, w, z, y, rhoprime, s1, s2, t0, ...A);
|
|
522
|
+
// `externalMu` hands ownership of `mu` to the caller,
|
|
523
|
+
// so only wipe the internally derived digest form here;
|
|
524
|
+
// zeroizing caller memory would break the caller's own reuse / verify path.
|
|
525
|
+
if (!externalMu)
|
|
526
|
+
cleanBytes(mu);
|
|
454
527
|
return res;
|
|
455
528
|
}
|
|
456
529
|
// @ts-ignore
|
|
@@ -464,7 +537,9 @@ function getDilithium(opts) {
|
|
|
464
537
|
const tr = shake256(publicKey, { dkLen: TR_BYTES }); // 6: tr ← H(BytesToBits(pk), 512)
|
|
465
538
|
if (sig.length !== sigCoder.bytesLen)
|
|
466
539
|
return false; // return false instead of exception
|
|
467
|
-
|
|
540
|
+
// (c˜, z, h) ← sigDecode(σ)
|
|
541
|
+
// ▷ Signer’s commitment hash c ˜, response z and hint
|
|
542
|
+
const [cTilde, z, h] = sigCoder.decode(sig);
|
|
468
543
|
if (h === false)
|
|
469
544
|
return false; // if h = ⊥ then return false
|
|
470
545
|
for (let i = 0; i < L; i++)
|
|
@@ -472,23 +547,24 @@ function getDilithium(opts) {
|
|
|
472
547
|
return false;
|
|
473
548
|
const mu = externalMu
|
|
474
549
|
? msg
|
|
475
|
-
:
|
|
550
|
+
: // 7: µ ← H(tr||M, 512)
|
|
551
|
+
shake256.create({ dkLen: CRH_BYTES }).update(tr).update(msg).digest();
|
|
476
552
|
// Compute verifer’s challenge from c˜
|
|
477
|
-
const c = NTT.encode(SampleInBall(cTilde)); // c ← SampleInBall(c˜1)
|
|
553
|
+
const c = crystals.NTT.encode(SampleInBall(cTilde)); // c ← SampleInBall(c˜1)
|
|
478
554
|
const zNtt = z.map((i) => i.slice()); // zNtt = NTT(z)
|
|
479
555
|
for (let i = 0; i < L; i++)
|
|
480
|
-
NTT.encode(zNtt[i]);
|
|
556
|
+
crystals.NTT.encode(zNtt[i]);
|
|
481
557
|
const wTick1 = [];
|
|
482
558
|
const xof = XOF128(rho);
|
|
483
559
|
for (let i = 0; i < K; i++) {
|
|
484
|
-
const ct12d = MultiplyNTTs(NTT.encode(polyShiftl(t1[i])), c); //c * t1 * (2**d)
|
|
560
|
+
const ct12d = MultiplyNTTs(crystals.NTT.encode(polyShiftl(t1[i])), c); //c * t1 * (2**d)
|
|
485
561
|
const Az = newPoly(N); // // A * z
|
|
486
562
|
for (let j = 0; j < L; j++) {
|
|
487
563
|
const aij = RejNTTPoly(xof.get(j, i)); // A[i][j] inplace
|
|
488
564
|
polyAdd(Az, MultiplyNTTs(aij, zNtt[j]));
|
|
489
565
|
}
|
|
490
566
|
// wApprox = A*z - c*t1 * (2**d)
|
|
491
|
-
const wApprox = NTT.decode(polySub(Az, ct12d));
|
|
567
|
+
const wApprox = crystals.NTT.decode(polySub(Az, ct12d));
|
|
492
568
|
// Reconstruction of signer’s commitment
|
|
493
569
|
wTick1.push(polyUseHint(wApprox, h[i])); // w ′ ← UseHint(h, w'approx )
|
|
494
570
|
}
|
|
@@ -511,9 +587,9 @@ function getDilithium(opts) {
|
|
|
511
587
|
return false;
|
|
512
588
|
return equalBytes(cTilde, c2);
|
|
513
589
|
},
|
|
514
|
-
};
|
|
515
|
-
return {
|
|
516
|
-
info: { type: 'ml-dsa' },
|
|
590
|
+
});
|
|
591
|
+
return Object.freeze({
|
|
592
|
+
info: Object.freeze({ type: 'ml-dsa' }),
|
|
517
593
|
internal,
|
|
518
594
|
securityLevel: securityLevel,
|
|
519
595
|
keygen: internal.keygen,
|
|
@@ -532,8 +608,8 @@ function getDilithium(opts) {
|
|
|
532
608
|
},
|
|
533
609
|
prehash: (hash) => {
|
|
534
610
|
checkHash(hash, securityLevel);
|
|
535
|
-
return {
|
|
536
|
-
info: { type: 'hashml-dsa' },
|
|
611
|
+
return Object.freeze({
|
|
612
|
+
info: Object.freeze({ type: 'hashml-dsa' }),
|
|
537
613
|
securityLevel: securityLevel,
|
|
538
614
|
lengths: internal.lengths,
|
|
539
615
|
keygen: internal.keygen,
|
|
@@ -549,12 +625,12 @@ function getDilithium(opts) {
|
|
|
549
625
|
validateVerOpts(opts);
|
|
550
626
|
return internal.verify(sig, getMessagePrehash(hash, msg, opts.context), publicKey);
|
|
551
627
|
},
|
|
552
|
-
};
|
|
628
|
+
});
|
|
553
629
|
},
|
|
554
|
-
};
|
|
630
|
+
});
|
|
555
631
|
}
|
|
556
632
|
/** ML-DSA-44 for 128-bit security level. Not recommended after 2030, as per ASD. */
|
|
557
|
-
export const ml_dsa44 = /* @__PURE__ */ getDilithium({
|
|
633
|
+
export const ml_dsa44 = /* @__PURE__ */ (() => getDilithium({
|
|
558
634
|
...PARAMS[2],
|
|
559
635
|
CRH_BYTES: 64,
|
|
560
636
|
TR_BYTES: 64,
|
|
@@ -562,9 +638,9 @@ export const ml_dsa44 = /* @__PURE__ */ getDilithium({
|
|
|
562
638
|
XOF128,
|
|
563
639
|
XOF256,
|
|
564
640
|
securityLevel: 128,
|
|
565
|
-
});
|
|
641
|
+
}))();
|
|
566
642
|
/** ML-DSA-65 for 192-bit security level. Not recommended after 2030, as per ASD. */
|
|
567
|
-
export const ml_dsa65 = /* @__PURE__ */ getDilithium({
|
|
643
|
+
export const ml_dsa65 = /* @__PURE__ */ (() => getDilithium({
|
|
568
644
|
...PARAMS[3],
|
|
569
645
|
CRH_BYTES: 64,
|
|
570
646
|
TR_BYTES: 64,
|
|
@@ -572,9 +648,9 @@ export const ml_dsa65 = /* @__PURE__ */ getDilithium({
|
|
|
572
648
|
XOF128,
|
|
573
649
|
XOF256,
|
|
574
650
|
securityLevel: 192,
|
|
575
|
-
});
|
|
651
|
+
}))();
|
|
576
652
|
/** ML-DSA-87 for 256-bit security level. OK after 2030, as per ASD. */
|
|
577
|
-
export const ml_dsa87 = /* @__PURE__ */ getDilithium({
|
|
653
|
+
export const ml_dsa87 = /* @__PURE__ */ (() => getDilithium({
|
|
578
654
|
...PARAMS[5],
|
|
579
655
|
CRH_BYTES: 64,
|
|
580
656
|
TR_BYTES: 64,
|
|
@@ -582,5 +658,5 @@ export const ml_dsa87 = /* @__PURE__ */ getDilithium({
|
|
|
582
658
|
XOF128,
|
|
583
659
|
XOF256,
|
|
584
660
|
securityLevel: 256,
|
|
585
|
-
});
|
|
661
|
+
}))();
|
|
586
662
|
//# sourceMappingURL=ml-dsa.js.map
|