@noble/post-quantum 0.5.3 → 0.6.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 +70 -39
- package/_crystals.d.ts +84 -0
- package/_crystals.d.ts.map +1 -1
- package/_crystals.js +64 -3
- package/_crystals.js.map +1 -1
- package/falcon.d.ts +84 -0
- package/falcon.d.ts.map +1 -0
- package/falcon.js +2378 -0
- package/falcon.js.map +1 -0
- package/hybrid.d.ts +181 -5
- package/hybrid.d.ts.map +1 -1
- package/hybrid.js +375 -53
- package/hybrid.js.map +1 -1
- package/ml-dsa.d.ts +22 -1
- package/ml-dsa.d.ts.map +1 -1
- package/ml-dsa.js +101 -51
- package/ml-dsa.js.map +1 -1
- package/ml-kem.d.ts +27 -3
- package/ml-kem.d.ts.map +1 -1
- package/ml-kem.js +154 -52
- package/ml-kem.js.map +1 -1
- package/package.json +12 -5
- package/slh-dsa.d.ts +116 -13
- package/slh-dsa.d.ts.map +1 -1
- package/slh-dsa.js +134 -35
- package/slh-dsa.js.map +1 -1
- package/src/_crystals.ts +101 -7
- package/src/falcon.ts +2470 -0
- package/src/hybrid.ts +406 -72
- package/src/ml-dsa.ts +144 -74
- package/src/ml-kem.ts +168 -54
- package/src/slh-dsa.ts +203 -44
- package/src/utils.ts +320 -15
- package/utils.d.ts +283 -4
- package/utils.d.ts.map +1 -1
- package/utils.js +245 -14
- package/utils.js.map +1 -1
package/src/ml-dsa.ts
CHANGED
|
@@ -32,13 +32,21 @@ import {
|
|
|
32
32
|
type VerOpts,
|
|
33
33
|
} from './utils.ts';
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
/** Internal ML-DSA options. */
|
|
36
|
+
export type DSAInternalOpts = {
|
|
37
|
+
/**
|
|
38
|
+
* Whether `internal.sign` / `internal.verify` receive a caller-supplied 64-byte `mu`
|
|
39
|
+
* instead of the usual FIPS 204 formatted message `M'` / prehash-formatted message.
|
|
40
|
+
* validateInternalOpts() only checks this flag; callers still must supply the right input length.
|
|
41
|
+
*/
|
|
42
|
+
externalMu?: boolean;
|
|
43
|
+
};
|
|
36
44
|
function validateInternalOpts(opts: DSAInternalOpts) {
|
|
37
45
|
validateOpts(opts);
|
|
38
46
|
if (opts.externalMu !== undefined) abool(opts.externalMu, 'opts.externalMu');
|
|
39
47
|
}
|
|
40
48
|
|
|
41
|
-
/**
|
|
49
|
+
/** ML-DSA signer surface with access to the internal message formatting mode. */
|
|
42
50
|
export type DSAInternal = CryptoKeys & {
|
|
43
51
|
lengths: Signer['lengths'];
|
|
44
52
|
sign: (msg: Uint8Array, secretKey: Uint8Array, opts?: SigOpts & DSAInternalOpts) => Uint8Array;
|
|
@@ -49,16 +57,22 @@ export type DSAInternal = CryptoKeys & {
|
|
|
49
57
|
opts?: VerOpts & DSAInternalOpts
|
|
50
58
|
) => boolean;
|
|
51
59
|
};
|
|
60
|
+
/** Public ML-DSA signer surface. */
|
|
52
61
|
export type DSA = Signer & { internal: DSAInternal };
|
|
53
62
|
|
|
54
63
|
// Constants
|
|
64
|
+
// FIPS 204 fixes ML-DSA over R = Z[X]/(X^256 + 1), so every polynomial has 256 coefficients.
|
|
55
65
|
const N = 256;
|
|
56
66
|
// 2**23 − 2**13 + 1, 23 bits: multiply will be 46. We have enough precision in JS to avoid bigints
|
|
57
67
|
const Q = 8380417;
|
|
68
|
+
// FIPS 204 §2.5 / Table 1 fixes zeta = 1753 as the 512th root of unity used by ML-DSA's NTT.
|
|
58
69
|
const ROOT_OF_UNITY = 1753;
|
|
59
70
|
// f = 256**−1 mod q, pow(256, -1, q) = 8347681 (python3)
|
|
60
71
|
const F = 8347681;
|
|
72
|
+
// FIPS 204 Table 1 / §7.4 fixes d = 13 dropped low bits for Power2Round on t.
|
|
61
73
|
const D = 13;
|
|
74
|
+
// FIPS 204 Table 1 fixes gamma2 to (q-1)/88 for ML-DSA-44 and (q-1)/32 for ML-DSA-65/87;
|
|
75
|
+
// §7.4 then uses alpha = 2*gamma2 for Decompose / MakeHint / UseHint.
|
|
62
76
|
// Dilithium is kinda parametrized over GAMMA2, but everything will break with any other value.
|
|
63
77
|
const GAMMA2_1 = Math.floor((Q - 1) / 88) | 0;
|
|
64
78
|
const GAMMA2_2 = Math.floor((Q - 1) / 32) | 0;
|
|
@@ -66,29 +80,45 @@ const GAMMA2_2 = Math.floor((Q - 1) / 32) | 0;
|
|
|
66
80
|
type XofGet = ReturnType<ReturnType<XOF>['get']>;
|
|
67
81
|
|
|
68
82
|
/** Various lattice params. */
|
|
83
|
+
/** Public ML-DSA parameter-set description. */
|
|
69
84
|
export type DSAParam = {
|
|
85
|
+
/** Matrix row count. */
|
|
70
86
|
K: number;
|
|
87
|
+
/** Matrix column count. */
|
|
71
88
|
L: number;
|
|
89
|
+
/** Bit width used when rounding `t`. */
|
|
72
90
|
D: number;
|
|
91
|
+
/** Bound used for the `y` sampling range. */
|
|
73
92
|
GAMMA1: number;
|
|
93
|
+
/** Bound used during decomposition and hints. */
|
|
74
94
|
GAMMA2: number;
|
|
95
|
+
/** Number of non-zero challenge coefficients. */
|
|
75
96
|
TAU: number;
|
|
97
|
+
/** Centered-binomial noise parameter. */
|
|
76
98
|
ETA: number;
|
|
99
|
+
/** Maximum number of hint bits in a signature. */
|
|
77
100
|
OMEGA: number;
|
|
78
101
|
};
|
|
79
102
|
/** Internal params for different versions of ML-DSA */
|
|
80
103
|
// prettier-ignore
|
|
81
|
-
|
|
104
|
+
/** Built-in ML-DSA parameter presets keyed by security categories `2/3/5`
|
|
105
|
+
* for `ml_dsa44` / `ml_dsa65` / `ml_dsa87`.
|
|
106
|
+
* This is only the Table 1 subset used directly here: `BETA = TAU * ETA` is derived later,
|
|
107
|
+
* while `C_TILDE_BYTES`, `TR_BYTES`, `CRH_BYTES`, and `securityLevel` live in the preset wrappers.
|
|
108
|
+
*/
|
|
109
|
+
export const PARAMS: Record<string, DSAParam> = /* @__PURE__ */ (() => ({
|
|
82
110
|
2: { K: 4, L: 4, D, GAMMA1: 2 ** 17, GAMMA2: GAMMA2_1, TAU: 39, ETA: 2, OMEGA: 80 },
|
|
83
111
|
3: { K: 6, L: 5, D, GAMMA1: 2 ** 19, GAMMA2: GAMMA2_2, TAU: 49, ETA: 4, OMEGA: 55 },
|
|
84
112
|
5: { K: 8, L: 7, D, GAMMA1: 2 ** 19, GAMMA2: GAMMA2_2, TAU: 60, ETA: 2, OMEGA: 75 },
|
|
85
|
-
} as const;
|
|
113
|
+
} as const))();
|
|
86
114
|
|
|
87
115
|
// NOTE: there is a lot cases where negative numbers used (with smod instead of mod).
|
|
88
116
|
type Poly = Int32Array;
|
|
89
117
|
const newPoly = (n: number): Int32Array => new Int32Array(n);
|
|
90
118
|
|
|
91
|
-
|
|
119
|
+
// Shared CRYSTALS helper in the ML-DSA branch: non-Kyber mode, 8-bit bit-reversal,
|
|
120
|
+
// and Int32Array polys because ordinary-form coefficients can be negative / centered.
|
|
121
|
+
const crystals = /* @__PURE__ */ genCrystals({
|
|
92
122
|
N,
|
|
93
123
|
Q,
|
|
94
124
|
F,
|
|
@@ -101,51 +131,59 @@ const { mod, smod, NTT, bitsCoder } = genCrystals({
|
|
|
101
131
|
const id = <T>(n: T): T => n;
|
|
102
132
|
type IdNum = (n: number) => number;
|
|
103
133
|
|
|
134
|
+
// compress()/verify() must be compatible in both directions:
|
|
135
|
+
// wrap the shared d-bit packer with the FIPS 204 SimpleBitPack / BitPack coefficient maps.
|
|
136
|
+
// malformed-input rejection only happens through the optional verify hook.
|
|
104
137
|
const polyCoder = (d: number, compress: IdNum = id, verify: IdNum = id) =>
|
|
105
|
-
bitsCoder(d, {
|
|
138
|
+
crystals.bitsCoder(d, {
|
|
106
139
|
encode: (i: number) => compress(verify(i)),
|
|
107
140
|
decode: (i: number) => verify(compress(i)),
|
|
108
141
|
});
|
|
109
142
|
|
|
143
|
+
// Mutates `a` in place; callers must pass same-length polynomials.
|
|
110
144
|
const polyAdd = (a: Poly, b: Poly) => {
|
|
111
|
-
for (let i = 0; i < a.length; i++) a[i] = mod(a[i] + b[i]);
|
|
145
|
+
for (let i = 0; i < a.length; i++) a[i] = crystals.mod(a[i] + b[i]);
|
|
112
146
|
return a;
|
|
113
147
|
};
|
|
148
|
+
// Mutates `a` in place; callers must pass same-length polynomials.
|
|
114
149
|
const polySub = (a: Poly, b: Poly): Poly => {
|
|
115
|
-
for (let i = 0; i < a.length; i++) a[i] = mod(a[i] - b[i]);
|
|
150
|
+
for (let i = 0; i < a.length; i++) a[i] = crystals.mod(a[i] - b[i]);
|
|
116
151
|
return a;
|
|
117
152
|
};
|
|
118
153
|
|
|
154
|
+
// Mutates `p` in place and assumes it is a decoded `t1`-range polynomial.
|
|
119
155
|
const polyShiftl = (p: Poly): Poly => {
|
|
120
156
|
for (let i = 0; i < N; i++) p[i] <<= D;
|
|
121
157
|
return p;
|
|
122
158
|
};
|
|
123
159
|
|
|
124
160
|
const polyChknorm = (p: Poly, B: number): boolean => {
|
|
125
|
-
//
|
|
126
|
-
for (let i = 0; i < N; i++) if (Math.abs(smod(p[i])) >= B) return true;
|
|
161
|
+
// FIPS 204 Algorithms 7 and 8 express the same centered-norm check with explicit inequalities.
|
|
162
|
+
for (let i = 0; i < N; i++) if (Math.abs(crystals.smod(p[i])) >= B) return true;
|
|
127
163
|
return false;
|
|
128
164
|
};
|
|
129
165
|
|
|
166
|
+
// Both inputs must already be in NTT / `T_q` form.
|
|
130
167
|
const MultiplyNTTs = (a: Poly, b: Poly): Poly => {
|
|
131
168
|
// NOTE: we don't use montgomery reduction in code, since it requires 64 bit ints,
|
|
132
169
|
// which is not available in JS. mod(a[i] * b[i]) is ok, since Q is 23 bit,
|
|
133
170
|
// which means a[i] * b[i] is 46 bit, which is safe to use in JS. (number is 53 bits).
|
|
134
171
|
// Barrett reduction is slower than mod :(
|
|
135
172
|
const c = newPoly(N);
|
|
136
|
-
for (let i = 0; i < a.length; i++) c[i] = mod(a[i] * b[i]);
|
|
173
|
+
for (let i = 0; i < a.length; i++) c[i] = crystals.mod(a[i] * b[i]);
|
|
137
174
|
return c;
|
|
138
175
|
};
|
|
139
176
|
|
|
140
177
|
// Return poly in NTT representation
|
|
141
178
|
function RejNTTPoly(xof: XofGet) {
|
|
142
|
-
// Samples a polynomial ∈ Tq.
|
|
179
|
+
// Samples a polynomial ∈ Tq. xof() must return byte lengths divisible by 3.
|
|
143
180
|
const r = newPoly(N);
|
|
144
181
|
// NOTE: we can represent 3xu24 as 4xu32, but it doesn't improve perf :(
|
|
145
182
|
for (let j = 0; j < N; ) {
|
|
146
183
|
const b = xof();
|
|
147
184
|
if (b.length % 3) throw new Error('RejNTTPoly: unaligned block');
|
|
148
185
|
for (let i = 0; j < N && i <= b.length - 3; i += 3) {
|
|
186
|
+
// FIPS 204 Algorithm 14 clears the top bit of b2 before forming the 23-bit candidate.
|
|
149
187
|
const t = (b[i + 0] | (b[i + 1] << 8) | (b[i + 2] << 16)) & 0x7fffff; // 3 bytes
|
|
150
188
|
if (t < Q) r[j++] = t;
|
|
151
189
|
}
|
|
@@ -169,6 +207,8 @@ type DilithiumOpts = {
|
|
|
169
207
|
securityLevel: number;
|
|
170
208
|
};
|
|
171
209
|
|
|
210
|
+
// Instantiate one ML-DSA parameter set from the Table 1 lattice constants plus the
|
|
211
|
+
// Table 2 byte lengths / hash-width choices used by the public wrappers below.
|
|
172
212
|
function getDilithium(opts: DilithiumOpts) {
|
|
173
213
|
const { K, L, GAMMA1, GAMMA2, TAU, ETA, OMEGA } = opts;
|
|
174
214
|
const { CRH_BYTES, TR_BYTES, C_TILDE_BYTES, XOF128, XOF256, securityLevel } = opts;
|
|
@@ -180,8 +220,9 @@ function getDilithium(opts: DilithiumOpts) {
|
|
|
180
220
|
|
|
181
221
|
const decompose = (r: number) => {
|
|
182
222
|
// Decomposes r into (r1, r0) such that r ≡ r1(2γ2) + r0 mod q.
|
|
183
|
-
const rPlus = mod(r);
|
|
184
|
-
const r0 = smod(rPlus, 2 * GAMMA2) | 0;
|
|
223
|
+
const rPlus = crystals.mod(r);
|
|
224
|
+
const r0 = crystals.smod(rPlus, 2 * GAMMA2) | 0;
|
|
225
|
+
// FIPS 204 Algorithm 36 folds the top bucket `q-1` back to `(r1, r0) = (0, r0-1)`.
|
|
185
226
|
if (rPlus - r0 === Q - 1) return { r1: 0 | 0, r0: (r0 - 1) | 0 };
|
|
186
227
|
const r1 = Math.floor((rPlus - r0) / (2 * GAMMA2)) | 0;
|
|
187
228
|
return { r1, r0 }; // r1 = HighBits, r0 = LowBits
|
|
@@ -191,6 +232,10 @@ function getDilithium(opts: DilithiumOpts) {
|
|
|
191
232
|
const LowBits = (r: number) => decompose(r).r0;
|
|
192
233
|
const MakeHint = (z: number, r: number) => {
|
|
193
234
|
// Compute hint bit indicating whether adding z to r alters the high bits of r.
|
|
235
|
+
// FIPS 204 §6.2 also permits the Section 5.1 alternative from [6], which uses the
|
|
236
|
+
// transformed low-bits/high-bits state at this call site instead of Algorithm 39 literally.
|
|
237
|
+
// This optimized predicate only applies to those transformed Section 5.1 inputs; it is
|
|
238
|
+
// not a drop-in replacement for Algorithm 39 on arbitrary `(z, r)` pairs.
|
|
194
239
|
|
|
195
240
|
// From dilithium code
|
|
196
241
|
const res0 = z <= GAMMA2 || z > Q - GAMMA2 || (z === Q - GAMMA2 && r === 0) ? 0 : 1;
|
|
@@ -201,8 +246,9 @@ function getDilithium(opts: DilithiumOpts) {
|
|
|
201
246
|
// But they return different results! However, decompose is same.
|
|
202
247
|
// So, either there is a bug in Dilithium ref implementation or in FIPS204.
|
|
203
248
|
// For now, lets use dilithium one, so test vectors can be passed.
|
|
204
|
-
//
|
|
205
|
-
//
|
|
249
|
+
// The round-3 Dilithium / ML-DSA code uses the same low-bits / high-bits convention after
|
|
250
|
+
// `r0 += ct0`.
|
|
251
|
+
// See dilithium-py README section "Optimising decomposition and making hints".
|
|
206
252
|
return res0;
|
|
207
253
|
};
|
|
208
254
|
|
|
@@ -212,13 +258,13 @@ function getDilithium(opts: DilithiumOpts) {
|
|
|
212
258
|
const { r1, r0 } = decompose(r);
|
|
213
259
|
// 3: if h = 1 and r0 > 0 return (r1 + 1) mod m
|
|
214
260
|
// 4: if h = 1 and r0 ≤ 0 return (r1 − 1) mod m
|
|
215
|
-
if (h === 1) return r0 > 0 ? mod(r1 + 1, m) | 0 : mod(r1 - 1, m) | 0;
|
|
261
|
+
if (h === 1) return r0 > 0 ? crystals.mod(r1 + 1, m) | 0 : crystals.mod(r1 - 1, m) | 0;
|
|
216
262
|
return r1 | 0;
|
|
217
263
|
};
|
|
218
264
|
const Power2Round = (r: number) => {
|
|
219
265
|
// Decomposes r into (r1, r0) such that r ≡ r1*(2**d) + r0 mod q.
|
|
220
|
-
const rPlus = mod(r);
|
|
221
|
-
const r0 = smod(rPlus, 2 ** D) | 0;
|
|
266
|
+
const rPlus = crystals.mod(r);
|
|
267
|
+
const r0 = crystals.smod(rPlus, 2 ** D) | 0;
|
|
222
268
|
return { r1: Math.floor((rPlus - r0) / 2 ** D) | 0, r0 };
|
|
223
269
|
};
|
|
224
270
|
|
|
@@ -263,7 +309,7 @@ function getDilithium(opts: DilithiumOpts) {
|
|
|
263
309
|
const T0Coder = polyCoder(13, (i: number) => (1 << (D - 1)) - i);
|
|
264
310
|
const T1Coder = polyCoder(10);
|
|
265
311
|
// Requires smod. Need to fix!
|
|
266
|
-
const ZCoder = polyCoder(GAMMA1 === 1 << 17 ? 18 : 20, (i: number) => smod(GAMMA1 - i));
|
|
312
|
+
const ZCoder = polyCoder(GAMMA1 === 1 << 17 ? 18 : 20, (i: number) => crystals.smod(GAMMA1 - i));
|
|
267
313
|
const W1Coder = polyCoder(GAMMA2 === GAMMA2_1 ? 6 : 4);
|
|
268
314
|
const W1Vec = vecCoder(W1Coder, K);
|
|
269
315
|
// Main structures
|
|
@@ -283,7 +329,9 @@ function getDilithium(opts: DilithiumOpts) {
|
|
|
283
329
|
? (n: number) => (n < 15 ? 2 - (n % 5) : false)
|
|
284
330
|
: (n: number) => (n < 9 ? 4 - n : false);
|
|
285
331
|
|
|
286
|
-
// Return poly in
|
|
332
|
+
// Return poly in ordinary representation.
|
|
333
|
+
// This helper returns ordinary-form `[-ETA, ETA]` coefficients for ExpandS; callers apply
|
|
334
|
+
// `NTT.encode()` later when needed.
|
|
287
335
|
function RejBoundedPoly(xof: XofGet) {
|
|
288
336
|
// Samples an element a ∈ Rq with coeffcients in [−η, η] computed via rejection sampling from ρ.
|
|
289
337
|
const r: Poly = newPoly(N);
|
|
@@ -306,6 +354,8 @@ function getDilithium(opts: DilithiumOpts) {
|
|
|
306
354
|
const s = shake256.create({}).update(seed);
|
|
307
355
|
const buf = new Uint8Array(shake256.blockLen);
|
|
308
356
|
s.xofInto(buf);
|
|
357
|
+
// FIPS 204 Algorithm 29 uses the first 8 squeezed bytes as the 64 sign bits `h`,
|
|
358
|
+
// then rejection-samples coefficient positions from the remaining XOF stream.
|
|
309
359
|
const masks = buf.slice(0, 8);
|
|
310
360
|
for (let i = N - TAU, pos = 8, maskPos = 0, maskBit = 0; i < N; i++) {
|
|
311
361
|
let b = i + 1;
|
|
@@ -336,6 +386,8 @@ function getDilithium(opts: DilithiumOpts) {
|
|
|
336
386
|
return { r0: res0, r1: res1 };
|
|
337
387
|
};
|
|
338
388
|
const polyUseHint = (u: Poly, h: Poly): Poly => {
|
|
389
|
+
// In-place on `u`: verification only needs the recovered high bits, so reuse the
|
|
390
|
+
// temporary `wApprox` buffer instead of allocating another polynomial.
|
|
339
391
|
for (let i = 0; i < N; i++) u[i] = UseHint(h[i], u[i]);
|
|
340
392
|
return u;
|
|
341
393
|
};
|
|
@@ -381,7 +433,7 @@ function getDilithium(opts: DilithiumOpts) {
|
|
|
381
433
|
const s2 = [];
|
|
382
434
|
for (let i = L; i < L + K; i++)
|
|
383
435
|
s2.push(RejBoundedPoly(xofPrime.get(i & 0xff, (i >> 8) & 0xff)));
|
|
384
|
-
const s1Hat = s1.map((i) => NTT.encode(i.slice()));
|
|
436
|
+
const s1Hat = s1.map((i) => crystals.NTT.encode(i.slice()));
|
|
385
437
|
const t0 = [];
|
|
386
438
|
const t1 = [];
|
|
387
439
|
const xof = XOF128(rho);
|
|
@@ -393,26 +445,30 @@ function getDilithium(opts: DilithiumOpts) {
|
|
|
393
445
|
const aij = RejNTTPoly(xof.get(j, i)); // super slow!
|
|
394
446
|
polyAdd(t, MultiplyNTTs(aij, s1Hat[j]));
|
|
395
447
|
}
|
|
396
|
-
NTT.decode(t);
|
|
448
|
+
crystals.NTT.decode(t);
|
|
397
449
|
const { r0, r1 } = polyPowerRound(polyAdd(t, s2[i])); // (t1, t0) ← Power2Round(t, d)
|
|
398
450
|
t0.push(r0);
|
|
399
451
|
t1.push(r1);
|
|
400
452
|
}
|
|
401
453
|
const publicKey = publicCoder.encode([rho, t1]); // pk ← pkEncode(ρ, t1)
|
|
402
454
|
const tr = shake256(publicKey, { dkLen: TR_BYTES }); // tr ← H(BytesToBits(pk), 512)
|
|
403
|
-
|
|
455
|
+
// sk ← skEncode(ρ, K,tr, s1, s2, t0)
|
|
456
|
+
const secretKey = secretCoder.encode([rho, K_, tr, s1, s2, t0]);
|
|
404
457
|
xof.clean();
|
|
405
458
|
xofPrime.clean();
|
|
406
459
|
// STATS
|
|
407
|
-
// Kyber512:
|
|
408
|
-
//
|
|
460
|
+
// Kyber512: { calls: 4, xofs: 12 }, Kyber768: { calls: 9, xofs: 27 },
|
|
461
|
+
// Kyber1024: { calls: 16, xofs: 48 }
|
|
462
|
+
// DSA44: { calls: 24, xofs: 24 }, DSA65: { calls: 41, xofs: 41 },
|
|
463
|
+
// DSA87: { calls: 71, xofs: 71 }
|
|
409
464
|
cleanBytes(rho, rhoPrime, K_, s1, s2, s1Hat, t, t0, t1, tr, seedDst);
|
|
410
465
|
return { publicKey, secretKey };
|
|
411
466
|
},
|
|
412
467
|
getPublicKey: (secretKey: Uint8Array) => {
|
|
413
|
-
|
|
468
|
+
// (ρ, K,tr, s1, s2, t0) ← skDecode(sk)
|
|
469
|
+
const [rho, _K, _tr, s1, s2, _t0] = secretCoder.decode(secretKey);
|
|
414
470
|
const xof = XOF128(rho);
|
|
415
|
-
const s1Hat = s1.map((p) => NTT.encode(p.slice()));
|
|
471
|
+
const s1Hat = s1.map((p) => crystals.NTT.encode(p.slice()));
|
|
416
472
|
const t1: Poly[] = [];
|
|
417
473
|
const tmp = newPoly(N);
|
|
418
474
|
for (let i = 0; i < K; i++) {
|
|
@@ -421,7 +477,7 @@ function getDilithium(opts: DilithiumOpts) {
|
|
|
421
477
|
const aij = RejNTTPoly(xof.get(j, i)); // A_ij in NTT
|
|
422
478
|
polyAdd(tmp, MultiplyNTTs(aij, s1Hat[j])); // += A_ij * s1_j
|
|
423
479
|
}
|
|
424
|
-
NTT.decode(tmp); // NTT⁻¹
|
|
480
|
+
crystals.NTT.decode(tmp); // NTT⁻¹
|
|
425
481
|
polyAdd(tmp, s2[i]); // t_i = A·s1 + s2
|
|
426
482
|
const { r1 } = polyPowerRound(tmp); // r1 = t1, r0 ≈ t0
|
|
427
483
|
t1.push(r1);
|
|
@@ -437,7 +493,8 @@ function getDilithium(opts: DilithiumOpts) {
|
|
|
437
493
|
let { extraEntropy: random, externalMu = false } = opts;
|
|
438
494
|
// This part can be pre-cached per secretKey, but there is only minor performance improvement,
|
|
439
495
|
// since we re-use a lot of variables to computation.
|
|
440
|
-
|
|
496
|
+
// (ρ, K,tr, s1, s2, t0) ← skDecode(sk)
|
|
497
|
+
const [rho, _K, tr, s1, s2, t0] = secretCoder.decode(secretKey);
|
|
441
498
|
// Cache matrix to avoid re-compute later
|
|
442
499
|
const A: Poly[][] = []; // A ← ExpandA(ρ)
|
|
443
500
|
const xof = XOF128(rho);
|
|
@@ -447,15 +504,17 @@ function getDilithium(opts: DilithiumOpts) {
|
|
|
447
504
|
A.push(pv);
|
|
448
505
|
}
|
|
449
506
|
xof.clean();
|
|
450
|
-
for (let i = 0; i < L; i++) NTT.encode(s1[i]); // sˆ1 ← NTT(s1)
|
|
507
|
+
for (let i = 0; i < L; i++) crystals.NTT.encode(s1[i]); // sˆ1 ← NTT(s1)
|
|
451
508
|
for (let i = 0; i < K; i++) {
|
|
452
|
-
NTT.encode(s2[i]); // sˆ2 ← NTT(s2)
|
|
453
|
-
NTT.encode(t0[i]); // tˆ0 ← NTT(t0)
|
|
509
|
+
crystals.NTT.encode(s2[i]); // sˆ2 ← NTT(s2)
|
|
510
|
+
crystals.NTT.encode(t0[i]); // tˆ0 ← NTT(t0)
|
|
454
511
|
}
|
|
455
512
|
// This part is per msg
|
|
456
513
|
const mu = externalMu
|
|
457
514
|
? msg
|
|
458
|
-
:
|
|
515
|
+
: // 6: µ ← H(tr||M, 512)
|
|
516
|
+
// ▷ Compute message representative µ
|
|
517
|
+
shake256.create({ dkLen: CRH_BYTES }).update(tr).update(msg).digest();
|
|
459
518
|
|
|
460
519
|
// Compute private random seed
|
|
461
520
|
const rnd =
|
|
@@ -480,13 +539,13 @@ function getDilithium(opts: DilithiumOpts) {
|
|
|
480
539
|
// y ← ExpandMask(ρ , κ)
|
|
481
540
|
for (let i = 0; i < L; i++, kappa++)
|
|
482
541
|
y.push(ZCoder.decode(x256.get(kappa & 0xff, kappa >> 8)()));
|
|
483
|
-
const z = y.map((i) => NTT.encode(i.slice()));
|
|
542
|
+
const z = y.map((i) => crystals.NTT.encode(i.slice()));
|
|
484
543
|
const w = [];
|
|
485
544
|
for (let i = 0; i < K; i++) {
|
|
486
545
|
// w ← NTT−1(A ◦ NTT(y))
|
|
487
546
|
const wi = newPoly(N);
|
|
488
547
|
for (let j = 0; j < L; j++) polyAdd(wi, MultiplyNTTs(A[i][j], z[j]));
|
|
489
|
-
NTT.decode(wi);
|
|
548
|
+
crystals.NTT.decode(wi);
|
|
490
549
|
w.push(wi);
|
|
491
550
|
}
|
|
492
551
|
const w1 = w.map((j) => j.map(HighBits)); // w1 ← HighBits(w)
|
|
@@ -497,21 +556,22 @@ function getDilithium(opts: DilithiumOpts) {
|
|
|
497
556
|
.update(W1Vec.encode(w1))
|
|
498
557
|
.digest();
|
|
499
558
|
// Verifer’s challenge
|
|
500
|
-
|
|
559
|
+
// c ← SampleInBall(c˜1); cˆ ← NTT(c)
|
|
560
|
+
const cHat = crystals.NTT.encode(SampleInBall(cTilde));
|
|
501
561
|
// ⟨⟨cs1⟩⟩ ← NTT−1(cˆ◦ sˆ1)
|
|
502
562
|
const cs1 = s1.map((i) => MultiplyNTTs(i, cHat));
|
|
503
563
|
for (let i = 0; i < L; i++) {
|
|
504
|
-
polyAdd(NTT.decode(cs1[i]), y[i]); // z ← y + ⟨⟨cs1⟩⟩
|
|
564
|
+
polyAdd(crystals.NTT.decode(cs1[i]), y[i]); // z ← y + ⟨⟨cs1⟩⟩
|
|
505
565
|
if (polyChknorm(cs1[i], GAMMA1 - BETA)) continue main_loop; // ||z||∞ ≥ γ1 − β
|
|
506
566
|
}
|
|
507
567
|
// cs1 is now z (▷ Signer’s response)
|
|
508
568
|
let cnt = 0;
|
|
509
569
|
const h = [];
|
|
510
570
|
for (let i = 0; i < K; i++) {
|
|
511
|
-
const cs2 = NTT.decode(MultiplyNTTs(s2[i], cHat)); // ⟨⟨cs2⟩⟩ ← NTT−1(cˆ◦ sˆ2)
|
|
571
|
+
const cs2 = crystals.NTT.decode(MultiplyNTTs(s2[i], cHat)); // ⟨⟨cs2⟩⟩ ← NTT−1(cˆ◦ sˆ2)
|
|
512
572
|
const r0 = polySub(w[i], cs2).map(LowBits); // r0 ← LowBits(w − ⟨⟨cs2⟩⟩)
|
|
513
573
|
if (polyChknorm(r0, GAMMA2 - BETA)) continue main_loop; // ||r0||∞ ≥ γ2 − β
|
|
514
|
-
const ct0 = NTT.decode(MultiplyNTTs(t0[i], cHat)); // ⟨⟨ct0⟩⟩ ← NTT−1(cˆ◦ tˆ0)
|
|
574
|
+
const ct0 = crystals.NTT.decode(MultiplyNTTs(t0[i], cHat)); // ⟨⟨ct0⟩⟩ ← NTT−1(cˆ◦ tˆ0)
|
|
515
575
|
if (polyChknorm(ct0, GAMMA2)) continue main_loop;
|
|
516
576
|
polyAdd(r0, ct0);
|
|
517
577
|
// ▷ Signer’s hint
|
|
@@ -523,7 +583,11 @@ function getDilithium(opts: DilithiumOpts) {
|
|
|
523
583
|
x256.clean();
|
|
524
584
|
const res = sigCoder.encode([cTilde, cs1, h]); // σ ← sigEncode(c˜, z mod±q, h)
|
|
525
585
|
// rho, _K, tr is subarray of secretKey, cannot clean.
|
|
526
|
-
cleanBytes(cTilde, cs1, h, cHat, w1, w, z, y, rhoprime,
|
|
586
|
+
cleanBytes(cTilde, cs1, h, cHat, w1, w, z, y, rhoprime, s1, s2, t0, ...A);
|
|
587
|
+
// `externalMu` hands ownership of `mu` to the caller,
|
|
588
|
+
// so only wipe the internally derived digest form here;
|
|
589
|
+
// zeroizing caller memory would break the caller's own reuse / verify path.
|
|
590
|
+
if (!externalMu) cleanBytes(mu);
|
|
527
591
|
return res;
|
|
528
592
|
}
|
|
529
593
|
// @ts-ignore
|
|
@@ -542,27 +606,30 @@ function getDilithium(opts: DilithiumOpts) {
|
|
|
542
606
|
const tr = shake256(publicKey, { dkLen: TR_BYTES }); // 6: tr ← H(BytesToBits(pk), 512)
|
|
543
607
|
|
|
544
608
|
if (sig.length !== sigCoder.bytesLen) return false; // return false instead of exception
|
|
545
|
-
|
|
609
|
+
// (c˜, z, h) ← sigDecode(σ)
|
|
610
|
+
// ▷ Signer’s commitment hash c ˜, response z and hint
|
|
611
|
+
const [cTilde, z, h] = sigCoder.decode(sig);
|
|
546
612
|
if (h === false) return false; // if h = ⊥ then return false
|
|
547
613
|
for (let i = 0; i < L; i++) if (polyChknorm(z[i], GAMMA1 - BETA)) return false;
|
|
548
614
|
const mu = externalMu
|
|
549
615
|
? msg
|
|
550
|
-
:
|
|
616
|
+
: // 7: µ ← H(tr||M, 512)
|
|
617
|
+
shake256.create({ dkLen: CRH_BYTES }).update(tr).update(msg).digest();
|
|
551
618
|
// Compute verifer’s challenge from c˜
|
|
552
|
-
const c = NTT.encode(SampleInBall(cTilde)); // c ← SampleInBall(c˜1)
|
|
619
|
+
const c = crystals.NTT.encode(SampleInBall(cTilde)); // c ← SampleInBall(c˜1)
|
|
553
620
|
const zNtt = z.map((i) => i.slice()); // zNtt = NTT(z)
|
|
554
|
-
for (let i = 0; i < L; i++) NTT.encode(zNtt[i]);
|
|
621
|
+
for (let i = 0; i < L; i++) crystals.NTT.encode(zNtt[i]);
|
|
555
622
|
const wTick1 = [];
|
|
556
623
|
const xof = XOF128(rho);
|
|
557
624
|
for (let i = 0; i < K; i++) {
|
|
558
|
-
const ct12d = MultiplyNTTs(NTT.encode(polyShiftl(t1[i])), c); //c * t1 * (2**d)
|
|
625
|
+
const ct12d = MultiplyNTTs(crystals.NTT.encode(polyShiftl(t1[i])), c); //c * t1 * (2**d)
|
|
559
626
|
const Az = newPoly(N); // // A * z
|
|
560
627
|
for (let j = 0; j < L; j++) {
|
|
561
628
|
const aij = RejNTTPoly(xof.get(j, i)); // A[i][j] inplace
|
|
562
629
|
polyAdd(Az, MultiplyNTTs(aij, zNtt[j]));
|
|
563
630
|
}
|
|
564
631
|
// wApprox = A*z - c*t1 * (2**d)
|
|
565
|
-
const wApprox = NTT.decode(polySub(Az, ct12d));
|
|
632
|
+
const wApprox = crystals.NTT.decode(polySub(Az, ct12d));
|
|
566
633
|
// Reconstruction of signer’s commitment
|
|
567
634
|
wTick1.push(polyUseHint(wApprox, h[i])); // w ′ ← UseHint(h, w'approx )
|
|
568
635
|
}
|
|
@@ -626,34 +693,37 @@ function getDilithium(opts: DilithiumOpts) {
|
|
|
626
693
|
}
|
|
627
694
|
|
|
628
695
|
/** ML-DSA-44 for 128-bit security level. Not recommended after 2030, as per ASD. */
|
|
629
|
-
export const ml_dsa44: DSA = /* @__PURE__ */
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
696
|
+
export const ml_dsa44: DSA = /* @__PURE__ */ (() =>
|
|
697
|
+
getDilithium({
|
|
698
|
+
...PARAMS[2],
|
|
699
|
+
CRH_BYTES: 64,
|
|
700
|
+
TR_BYTES: 64,
|
|
701
|
+
C_TILDE_BYTES: 32,
|
|
702
|
+
XOF128,
|
|
703
|
+
XOF256,
|
|
704
|
+
securityLevel: 128,
|
|
705
|
+
}))();
|
|
638
706
|
|
|
639
707
|
/** ML-DSA-65 for 192-bit security level. Not recommended after 2030, as per ASD. */
|
|
640
|
-
export const ml_dsa65: DSA = /* @__PURE__ */
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
708
|
+
export const ml_dsa65: DSA = /* @__PURE__ */ (() =>
|
|
709
|
+
getDilithium({
|
|
710
|
+
...PARAMS[3],
|
|
711
|
+
CRH_BYTES: 64,
|
|
712
|
+
TR_BYTES: 64,
|
|
713
|
+
C_TILDE_BYTES: 48,
|
|
714
|
+
XOF128,
|
|
715
|
+
XOF256,
|
|
716
|
+
securityLevel: 192,
|
|
717
|
+
}))();
|
|
649
718
|
|
|
650
719
|
/** ML-DSA-87 for 256-bit security level. OK after 2030, as per ASD. */
|
|
651
|
-
export const ml_dsa87: DSA = /* @__PURE__ */
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
720
|
+
export const ml_dsa87: DSA = /* @__PURE__ */ (() =>
|
|
721
|
+
getDilithium({
|
|
722
|
+
...PARAMS[5],
|
|
723
|
+
CRH_BYTES: 64,
|
|
724
|
+
TR_BYTES: 64,
|
|
725
|
+
C_TILDE_BYTES: 64,
|
|
726
|
+
XOF128,
|
|
727
|
+
XOF256,
|
|
728
|
+
securityLevel: 256,
|
|
729
|
+
}))();
|