@noble/post-quantum 0.6.0 → 0.7.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 +106 -80
- package/_crystals.d.ts +23 -16
- package/_crystals.js +50 -12
- package/falcon.d.ts +7 -8
- package/falcon.js +109 -74
- package/hybrid.d.ts +21 -32
- package/hybrid.js +157 -78
- package/index.d.ts +0 -1
- package/index.js +8 -1
- package/ml-dsa.d.ts +35 -9
- package/ml-dsa.js +116 -43
- package/ml-kem.d.ts +46 -5
- package/ml-kem.js +175 -67
- package/package.json +10 -18
- package/slh-dsa.d.ts +44 -24
- package/slh-dsa.js +150 -84
- package/src/_crystals.ts +86 -35
- package/src/falcon.ts +230 -169
- package/src/hybrid.ts +243 -129
- package/src/index.ts +8 -0
- package/src/ml-dsa.ts +194 -87
- package/src/ml-kem.ts +283 -122
- package/src/slh-dsa.ts +310 -182
- package/src/utils.ts +244 -48
- package/utils.d.ts +99 -24
- package/utils.js +92 -24
- package/_crystals.d.ts.map +0 -1
- package/_crystals.js.map +0 -1
- package/falcon.d.ts.map +0 -1
- package/falcon.js.map +0 -1
- package/hybrid.d.ts.map +0 -1
- package/hybrid.js.map +0 -1
- package/index.d.ts.map +0 -1
- package/index.js.map +0 -1
- package/ml-dsa.d.ts.map +0 -1
- package/ml-dsa.js.map +0 -1
- package/ml-kem.d.ts.map +0 -1
- package/ml-kem.js.map +0 -1
- package/slh-dsa.d.ts.map +0 -1
- package/slh-dsa.js.map +0 -1
- package/utils.d.ts.map +0 -1
- package/utils.js.map +0 -1
package/src/index.ts
CHANGED
|
@@ -14,6 +14,14 @@ import {
|
|
|
14
14
|
slh_dsa_shake_192f, slh_dsa_shake_192s,
|
|
15
15
|
slh_dsa_shake_256f, slh_dsa_shake_256s,
|
|
16
16
|
} from '@noble/post-quantum/slh-dsa.js';
|
|
17
|
+
import {
|
|
18
|
+
falcon512, falcon512padded, falcon1024, falcon1024padded,
|
|
19
|
+
} from '@noble/post-quantum/falcon.js';
|
|
20
|
+
import {
|
|
21
|
+
ml_kem768_x25519, ml_kem768_p256, ml_kem1024_p384,
|
|
22
|
+
KitchenSink_ml_kem768_x25519, XWing,
|
|
23
|
+
QSF_ml_kem768_p256, QSF_ml_kem1024_p384,
|
|
24
|
+
} from '@noble/post-quantum/hybrid.js';
|
|
17
25
|
```
|
|
18
26
|
*/
|
|
19
27
|
throw new Error('root module cannot be imported: import submodules instead. Check out README');
|
package/src/ml-dsa.ts
CHANGED
|
@@ -25,6 +25,8 @@ import {
|
|
|
25
25
|
type Signer,
|
|
26
26
|
type SigOpts,
|
|
27
27
|
splitCoder,
|
|
28
|
+
type TArg,
|
|
29
|
+
type TRet,
|
|
28
30
|
validateOpts,
|
|
29
31
|
validateSigOpts,
|
|
30
32
|
validateVerOpts,
|
|
@@ -41,7 +43,7 @@ export type DSAInternalOpts = {
|
|
|
41
43
|
*/
|
|
42
44
|
externalMu?: boolean;
|
|
43
45
|
};
|
|
44
|
-
function validateInternalOpts(opts: DSAInternalOpts) {
|
|
46
|
+
function validateInternalOpts(opts: TArg<DSAInternalOpts>) {
|
|
45
47
|
validateOpts(opts);
|
|
46
48
|
if (opts.externalMu !== undefined) abool(opts.externalMu, 'opts.externalMu');
|
|
47
49
|
}
|
|
@@ -49,16 +51,29 @@ function validateInternalOpts(opts: DSAInternalOpts) {
|
|
|
49
51
|
/** ML-DSA signer surface with access to the internal message formatting mode. */
|
|
50
52
|
export type DSAInternal = CryptoKeys & {
|
|
51
53
|
lengths: Signer['lengths'];
|
|
52
|
-
sign: (
|
|
54
|
+
sign: (
|
|
55
|
+
msg: TArg<Uint8Array>,
|
|
56
|
+
secretKey: TArg<Uint8Array>,
|
|
57
|
+
opts?: TArg<SigOpts & DSAInternalOpts>
|
|
58
|
+
) => TRet<Uint8Array>;
|
|
53
59
|
verify: (
|
|
54
|
-
sig: Uint8Array
|
|
55
|
-
msg: Uint8Array
|
|
56
|
-
pubKey: Uint8Array
|
|
57
|
-
opts?: VerOpts & DSAInternalOpts
|
|
60
|
+
sig: TArg<Uint8Array>,
|
|
61
|
+
msg: TArg<Uint8Array>,
|
|
62
|
+
pubKey: TArg<Uint8Array>,
|
|
63
|
+
opts?: TArg<VerOpts & DSAInternalOpts>
|
|
58
64
|
) => boolean;
|
|
59
65
|
};
|
|
60
66
|
/** Public ML-DSA signer surface. */
|
|
61
|
-
export type DSA = Signer & {
|
|
67
|
+
export type DSA = Signer & {
|
|
68
|
+
internal: TRet<DSAInternal>;
|
|
69
|
+
securityLevel: number;
|
|
70
|
+
/**
|
|
71
|
+
* HashML-DSA (FIPS 204 §5.4) variant which signs a pre-hashed message.
|
|
72
|
+
* @param hash - Approved hash, checked against the parameter set security level.
|
|
73
|
+
* @returns Signer which pre-hashes `msg` before formatting `M'`.
|
|
74
|
+
*/
|
|
75
|
+
prehash: (hash: TArg<CHash>) => TRet<Signer>;
|
|
76
|
+
};
|
|
62
77
|
|
|
63
78
|
// Constants
|
|
64
79
|
// FIPS 204 fixes ML-DSA over R = Z[X]/(X^256 + 1), so every polynomial has 256 coefficients.
|
|
@@ -106,15 +121,22 @@ export type DSAParam = {
|
|
|
106
121
|
* This is only the Table 1 subset used directly here: `BETA = TAU * ETA` is derived later,
|
|
107
122
|
* while `C_TILDE_BYTES`, `TR_BYTES`, `CRH_BYTES`, and `securityLevel` live in the preset wrappers.
|
|
108
123
|
*/
|
|
109
|
-
export const PARAMS: Record<string, DSAParam> = /* @__PURE__ */ (() =>
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
}
|
|
124
|
+
export const PARAMS: Record<string, DSAParam> = /* @__PURE__ */ (() =>
|
|
125
|
+
Object.freeze({
|
|
126
|
+
2: Object.freeze({
|
|
127
|
+
K: 4, L: 4, D, GAMMA1: 2 ** 17, GAMMA2: GAMMA2_1, TAU: 39, ETA: 2, OMEGA: 80
|
|
128
|
+
}),
|
|
129
|
+
3: Object.freeze({
|
|
130
|
+
K: 6, L: 5, D, GAMMA1: 2 ** 19, GAMMA2: GAMMA2_2, TAU: 49, ETA: 4, OMEGA: 55
|
|
131
|
+
}),
|
|
132
|
+
5: Object.freeze({
|
|
133
|
+
K: 8, L: 7, D, GAMMA1: 2 ** 19, GAMMA2: GAMMA2_2, TAU: 60, ETA: 2, OMEGA: 75
|
|
134
|
+
}),
|
|
135
|
+
} as const))();
|
|
114
136
|
|
|
115
137
|
// NOTE: there is a lot cases where negative numbers used (with smod instead of mod).
|
|
116
138
|
type Poly = Int32Array;
|
|
117
|
-
const newPoly = (n: number): Int32Array => new Int32Array(n)
|
|
139
|
+
const newPoly = (n: number): TRet<Int32Array> => new Int32Array(n) as TRet<Int32Array>;
|
|
118
140
|
|
|
119
141
|
// Shared CRYSTALS helper in the ML-DSA branch: non-Kyber mode, 8-bit bit-reversal,
|
|
120
142
|
// and Int32Array polys because ordinary-form coefficients can be negative / centered.
|
|
@@ -141,41 +163,52 @@ const polyCoder = (d: number, compress: IdNum = id, verify: IdNum = id) =>
|
|
|
141
163
|
});
|
|
142
164
|
|
|
143
165
|
// Mutates `a` in place; callers must pass same-length polynomials.
|
|
144
|
-
|
|
166
|
+
// NOTE: conditional-reduction variants (as in ml-kem) were measured performance-neutral here —
|
|
167
|
+
// int32 `%` with 23-bit Q is already cheap — so the simpler mod() form is kept for audit.
|
|
168
|
+
const polyAdd = (a_: TArg<Poly>, b_: TArg<Poly>): TRet<Poly> => {
|
|
169
|
+
const a = a_ as Poly;
|
|
170
|
+
const b = b_ as Poly;
|
|
145
171
|
for (let i = 0; i < a.length; i++) a[i] = crystals.mod(a[i] + b[i]);
|
|
146
|
-
return a
|
|
172
|
+
return a as TRet<Poly>;
|
|
147
173
|
};
|
|
148
174
|
// Mutates `a` in place; callers must pass same-length polynomials.
|
|
149
|
-
const polySub = (
|
|
175
|
+
const polySub = (a_: TArg<Poly>, b_: TArg<Poly>): TRet<Poly> => {
|
|
176
|
+
const a = a_ as Poly;
|
|
177
|
+
const b = b_ as Poly;
|
|
150
178
|
for (let i = 0; i < a.length; i++) a[i] = crystals.mod(a[i] - b[i]);
|
|
151
|
-
return a
|
|
179
|
+
return a as TRet<Poly>;
|
|
152
180
|
};
|
|
153
181
|
|
|
154
182
|
// Mutates `p` in place and assumes it is a decoded `t1`-range polynomial.
|
|
155
|
-
const polyShiftl = (
|
|
183
|
+
const polyShiftl = (p_: TArg<Poly>): TRet<Poly> => {
|
|
184
|
+
const p = p_ as Poly;
|
|
156
185
|
for (let i = 0; i < N; i++) p[i] <<= D;
|
|
157
|
-
return p
|
|
186
|
+
return p as TRet<Poly>;
|
|
158
187
|
};
|
|
159
188
|
|
|
160
|
-
const polyChknorm = (
|
|
189
|
+
const polyChknorm = (p_: TArg<Poly>, B: number): boolean => {
|
|
190
|
+
const p = p_ as Poly;
|
|
161
191
|
// FIPS 204 Algorithms 7 and 8 express the same centered-norm check with explicit inequalities.
|
|
162
192
|
for (let i = 0; i < N; i++) if (Math.abs(crystals.smod(p[i])) >= B) return true;
|
|
163
193
|
return false;
|
|
164
194
|
};
|
|
165
195
|
|
|
166
196
|
// Both inputs must already be in NTT / `T_q` form.
|
|
167
|
-
const MultiplyNTTs = (
|
|
197
|
+
const MultiplyNTTs = (a_: TArg<Poly>, b_: TArg<Poly>): TRet<Poly> => {
|
|
198
|
+
const a = a_ as Poly;
|
|
199
|
+
const b = b_ as Poly;
|
|
168
200
|
// NOTE: we don't use montgomery reduction in code, since it requires 64 bit ints,
|
|
169
201
|
// which is not available in JS. mod(a[i] * b[i]) is ok, since Q is 23 bit,
|
|
170
202
|
// which means a[i] * b[i] is 46 bit, which is safe to use in JS. (number is 53 bits).
|
|
171
203
|
// Barrett reduction is slower than mod :(
|
|
172
204
|
const c = newPoly(N);
|
|
173
205
|
for (let i = 0; i < a.length; i++) c[i] = crystals.mod(a[i] * b[i]);
|
|
174
|
-
return c
|
|
206
|
+
return c as TRet<Poly>;
|
|
175
207
|
};
|
|
176
208
|
|
|
177
209
|
// Return poly in NTT representation
|
|
178
|
-
function RejNTTPoly(
|
|
210
|
+
function RejNTTPoly(xof_: TArg<XofGet>): TRet<Poly> {
|
|
211
|
+
const xof = xof_ as XofGet;
|
|
179
212
|
// Samples a polynomial ∈ Tq. xof() must return byte lengths divisible by 3.
|
|
180
213
|
const r = newPoly(N);
|
|
181
214
|
// NOTE: we can represent 3xu24 as 4xu32, but it doesn't improve perf :(
|
|
@@ -188,7 +221,7 @@ function RejNTTPoly(xof: XofGet) {
|
|
|
188
221
|
if (t < Q) r[j++] = t;
|
|
189
222
|
}
|
|
190
223
|
}
|
|
191
|
-
return r
|
|
224
|
+
return r as TRet<Poly>;
|
|
192
225
|
}
|
|
193
226
|
|
|
194
227
|
type DilithiumOpts = {
|
|
@@ -209,7 +242,8 @@ type DilithiumOpts = {
|
|
|
209
242
|
|
|
210
243
|
// Instantiate one ML-DSA parameter set from the Table 1 lattice constants plus the
|
|
211
244
|
// Table 2 byte lengths / hash-width choices used by the public wrappers below.
|
|
212
|
-
function getDilithium(
|
|
245
|
+
function getDilithium(opts_: TArg<DilithiumOpts>): TRet<DSA> {
|
|
246
|
+
const opts = opts_ as DilithiumOpts;
|
|
213
247
|
const { K, L, GAMMA1, GAMMA2, TAU, ETA, OMEGA } = opts;
|
|
214
248
|
const { CRH_BYTES, TR_BYTES, C_TILDE_BYTES, XOF128, XOF256, securityLevel } = opts;
|
|
215
249
|
|
|
@@ -252,13 +286,16 @@ function getDilithium(opts: DilithiumOpts) {
|
|
|
252
286
|
return res0;
|
|
253
287
|
};
|
|
254
288
|
|
|
289
|
+
// m = (q-1)/(2γ2): 44 for ML-DSA-44, 16 for 65/87. Hoisted out of UseHint, which runs
|
|
290
|
+
// per coefficient during verification.
|
|
291
|
+
const HINT_M = Math.floor((Q - 1) / (2 * GAMMA2));
|
|
255
292
|
const UseHint = (h: number, r: number) => {
|
|
256
293
|
// Returns the high bits of r adjusted according to hint h
|
|
257
|
-
const m = Math.floor((Q - 1) / (2 * GAMMA2));
|
|
258
294
|
const { r1, r0 } = decompose(r);
|
|
259
295
|
// 3: if h = 1 and r0 > 0 return (r1 + 1) mod m
|
|
260
296
|
// 4: if h = 1 and r0 ≤ 0 return (r1 − 1) mod m
|
|
261
|
-
if (h === 1)
|
|
297
|
+
if (h === 1)
|
|
298
|
+
return r0 > 0 ? crystals.mod(r1 + 1, HINT_M) | 0 : crystals.mod(r1 - 1, HINT_M) | 0;
|
|
262
299
|
return r1 | 0;
|
|
263
300
|
};
|
|
264
301
|
const Power2Round = (r: number) => {
|
|
@@ -270,30 +307,31 @@ function getDilithium(opts: DilithiumOpts) {
|
|
|
270
307
|
|
|
271
308
|
const hintCoder: BytesCoderLen<Poly[] | false> = {
|
|
272
309
|
bytesLen: OMEGA + K,
|
|
273
|
-
encode: (
|
|
310
|
+
encode: (h_: TArg<Poly[] | false>): TRet<Uint8Array> => {
|
|
311
|
+
const h = h_ as Poly[] | false;
|
|
274
312
|
if (h === false) throw new Error('hint.encode: hint is false'); // should never happen
|
|
275
313
|
const res = new Uint8Array(OMEGA + K);
|
|
276
314
|
for (let i = 0, k = 0; i < K; i++) {
|
|
277
315
|
for (let j = 0; j < N; j++) if (h[i][j] !== 0) res[k++] = j;
|
|
278
316
|
res[OMEGA + i] = k;
|
|
279
317
|
}
|
|
280
|
-
return res
|
|
318
|
+
return res as TRet<Uint8Array>;
|
|
281
319
|
},
|
|
282
|
-
decode: (buf: Uint8Array) => {
|
|
320
|
+
decode: (buf: TArg<Uint8Array>): TRet<Poly[] | false> => {
|
|
283
321
|
const h = [];
|
|
284
322
|
let k = 0;
|
|
285
323
|
for (let i = 0; i < K; i++) {
|
|
286
324
|
const hi = newPoly(N);
|
|
287
|
-
if (buf[OMEGA + i] < k || buf[OMEGA + i] > OMEGA) return false
|
|
325
|
+
if (buf[OMEGA + i] < k || buf[OMEGA + i] > OMEGA) return false as TRet<false>;
|
|
288
326
|
for (let j = k; j < buf[OMEGA + i]; j++) {
|
|
289
|
-
if (j > k && buf[j] <= buf[j - 1]) return false
|
|
327
|
+
if (j > k && buf[j] <= buf[j - 1]) return false as TRet<false>;
|
|
290
328
|
hi[buf[j]] = 1;
|
|
291
329
|
}
|
|
292
330
|
k = buf[OMEGA + i];
|
|
293
331
|
h.push(hi);
|
|
294
332
|
}
|
|
295
|
-
for (let j = k; j < OMEGA; j++) if (buf[j] !== 0) return false
|
|
296
|
-
return h
|
|
333
|
+
for (let j = k; j < OMEGA; j++) if (buf[j] !== 0) return false as TRet<false>;
|
|
334
|
+
return h as TRet<Poly[]>;
|
|
297
335
|
},
|
|
298
336
|
};
|
|
299
337
|
|
|
@@ -332,7 +370,8 @@ function getDilithium(opts: DilithiumOpts) {
|
|
|
332
370
|
// Return poly in ordinary representation.
|
|
333
371
|
// This helper returns ordinary-form `[-ETA, ETA]` coefficients for ExpandS; callers apply
|
|
334
372
|
// `NTT.encode()` later when needed.
|
|
335
|
-
function RejBoundedPoly(
|
|
373
|
+
function RejBoundedPoly(xof_: TArg<XofGet>): TRet<Poly> {
|
|
374
|
+
const xof = xof_ as XofGet;
|
|
336
375
|
// Samples an element a ∈ Rq with coeffcients in [−η, η] computed via rejection sampling from ρ.
|
|
337
376
|
const r: Poly = newPoly(N);
|
|
338
377
|
for (let j = 0; j < N; ) {
|
|
@@ -345,10 +384,10 @@ function getDilithium(opts: DilithiumOpts) {
|
|
|
345
384
|
if (j < N && d2 !== false) r[j++] = d2;
|
|
346
385
|
}
|
|
347
386
|
}
|
|
348
|
-
return r
|
|
387
|
+
return r as TRet<Poly>;
|
|
349
388
|
}
|
|
350
389
|
|
|
351
|
-
const SampleInBall = (seed: Uint8Array) => {
|
|
390
|
+
const SampleInBall = (seed: TArg<Uint8Array>): TRet<Poly> => {
|
|
352
391
|
// Samples a polynomial c ∈ Rq with coeffcients from {−1, 0, 1} and Hamming weight τ
|
|
353
392
|
const pre = newPoly(N);
|
|
354
393
|
const s = shake256.create({}).update(seed);
|
|
@@ -372,10 +411,11 @@ function getDilithium(opts: DilithiumOpts) {
|
|
|
372
411
|
maskBit = 0;
|
|
373
412
|
}
|
|
374
413
|
}
|
|
375
|
-
return pre
|
|
414
|
+
return pre as TRet<Poly>;
|
|
376
415
|
};
|
|
377
416
|
|
|
378
|
-
const polyPowerRound = (
|
|
417
|
+
const polyPowerRound = (p_: TArg<Poly>) => {
|
|
418
|
+
const p = p_ as Poly;
|
|
379
419
|
const res0 = newPoly(N);
|
|
380
420
|
const res1 = newPoly(N);
|
|
381
421
|
for (let i = 0; i < p.length; i++) {
|
|
@@ -385,13 +425,17 @@ function getDilithium(opts: DilithiumOpts) {
|
|
|
385
425
|
}
|
|
386
426
|
return { r0: res0, r1: res1 };
|
|
387
427
|
};
|
|
388
|
-
const polyUseHint = (
|
|
428
|
+
const polyUseHint = (u_: TArg<Poly>, h_: TArg<Poly>): TRet<Poly> => {
|
|
429
|
+
const u = u_ as Poly;
|
|
430
|
+
const h = h_ as Poly;
|
|
389
431
|
// In-place on `u`: verification only needs the recovered high bits, so reuse the
|
|
390
432
|
// temporary `wApprox` buffer instead of allocating another polynomial.
|
|
391
433
|
for (let i = 0; i < N; i++) u[i] = UseHint(h[i], u[i]);
|
|
392
|
-
return u
|
|
434
|
+
return u as TRet<Poly>;
|
|
393
435
|
};
|
|
394
|
-
const polyMakeHint = (
|
|
436
|
+
const polyMakeHint = (a_: TArg<Poly>, b_: TArg<Poly>) => {
|
|
437
|
+
const a = a_ as Poly;
|
|
438
|
+
const b = b_ as Poly;
|
|
395
439
|
const v = newPoly(N);
|
|
396
440
|
let cnt = 0;
|
|
397
441
|
for (let i = 0; i < N; i++) {
|
|
@@ -405,16 +449,16 @@ function getDilithium(opts: DilithiumOpts) {
|
|
|
405
449
|
const signRandBytes = 32;
|
|
406
450
|
const seedCoder = splitCoder('seed', 32, 64, 32);
|
|
407
451
|
// API & argument positions are exactly as in FIPS204.
|
|
408
|
-
const internal: DSAInternal = {
|
|
409
|
-
info: { type: 'internal-ml-dsa' },
|
|
410
|
-
lengths: {
|
|
452
|
+
const internal: TRet<DSAInternal> = Object.freeze({
|
|
453
|
+
info: Object.freeze({ type: 'internal-ml-dsa' }),
|
|
454
|
+
lengths: Object.freeze({
|
|
411
455
|
secretKey: secretCoder.bytesLen,
|
|
412
456
|
publicKey: publicCoder.bytesLen,
|
|
413
457
|
seed: 32,
|
|
414
458
|
signature: sigCoder.bytesLen,
|
|
415
459
|
signRand: signRandBytes,
|
|
416
|
-
},
|
|
417
|
-
keygen: (seed?: Uint8Array) => {
|
|
460
|
+
}),
|
|
461
|
+
keygen: (seed?: TArg<Uint8Array>) => {
|
|
418
462
|
// H(𝜉||IntegerToBytes(𝑘, 1)||IntegerToBytes(ℓ, 1), 128) 2: ▷ expand seed
|
|
419
463
|
const seedDst = new Uint8Array(32 + 2);
|
|
420
464
|
const randSeed = seed === undefined;
|
|
@@ -462,9 +506,12 @@ function getDilithium(opts: DilithiumOpts) {
|
|
|
462
506
|
// DSA44: { calls: 24, xofs: 24 }, DSA65: { calls: 41, xofs: 41 },
|
|
463
507
|
// DSA87: { calls: 71, xofs: 71 }
|
|
464
508
|
cleanBytes(rho, rhoPrime, K_, s1, s2, s1Hat, t, t0, t1, tr, seedDst);
|
|
465
|
-
return {
|
|
509
|
+
return {
|
|
510
|
+
publicKey: publicKey as TRet<Uint8Array>,
|
|
511
|
+
secretKey: secretKey as TRet<Uint8Array>,
|
|
512
|
+
};
|
|
466
513
|
},
|
|
467
|
-
getPublicKey: (secretKey: Uint8Array) => {
|
|
514
|
+
getPublicKey: (secretKey: TArg<Uint8Array>): TRet<Uint8Array> => {
|
|
468
515
|
// (ρ, K,tr, s1, s2, t0) ← skDecode(sk)
|
|
469
516
|
const [rho, _K, _tr, s1, s2, _t0] = secretCoder.decode(secretKey);
|
|
470
517
|
const xof = XOF128(rho);
|
|
@@ -487,14 +534,39 @@ function getDilithium(opts: DilithiumOpts) {
|
|
|
487
534
|
return publicCoder.encode([rho, t1]);
|
|
488
535
|
},
|
|
489
536
|
// NOTE: random is optional.
|
|
490
|
-
sign: (
|
|
537
|
+
sign: (
|
|
538
|
+
msg: TArg<Uint8Array>,
|
|
539
|
+
secretKey: TArg<Uint8Array>,
|
|
540
|
+
opts: TArg<SigOpts & DSAInternalOpts> = {}
|
|
541
|
+
): TRet<Uint8Array> => {
|
|
491
542
|
validateSigOpts(opts);
|
|
492
543
|
validateInternalOpts(opts);
|
|
493
|
-
|
|
544
|
+
const { extraEntropy: random, externalMu = false } = opts;
|
|
545
|
+
// FIPS 204 external-mu mode expects the 64-byte message representative µ = H(tr || M).
|
|
546
|
+
if (externalMu) abytes(msg, CRH_BYTES, 'mu');
|
|
547
|
+
// Prepare entropy before touching decoded secrets: randomBytes() may throw, and an RNG
|
|
548
|
+
// failure must not leave expanded secret-polynomial copies behind.
|
|
549
|
+
const ownRnd = random === false || random === undefined;
|
|
550
|
+
const rnd =
|
|
551
|
+
random === false
|
|
552
|
+
? new Uint8Array(32)
|
|
553
|
+
: random === undefined
|
|
554
|
+
? randomBytes(signRandBytes)
|
|
555
|
+
: (random as Uint8Array);
|
|
556
|
+
abytes(rnd, 32, 'extraEntropy');
|
|
494
557
|
// This part can be pre-cached per secretKey, but there is only minor performance improvement,
|
|
495
558
|
// since we re-use a lot of variables to computation.
|
|
496
559
|
// (ρ, K,tr, s1, s2, t0) ← skDecode(sk)
|
|
497
|
-
const
|
|
560
|
+
const decoded = (() => {
|
|
561
|
+
try {
|
|
562
|
+
return secretCoder.decode(secretKey);
|
|
563
|
+
} catch (error) {
|
|
564
|
+
// A malformed key must not strand entropy owned by the library.
|
|
565
|
+
if (ownRnd) cleanBytes(rnd);
|
|
566
|
+
throw error;
|
|
567
|
+
}
|
|
568
|
+
})();
|
|
569
|
+
const [rho, _K, tr, s1, s2, t0] = decoded;
|
|
498
570
|
// Cache matrix to avoid re-compute later
|
|
499
571
|
const A: Poly[][] = []; // A ← ExpandA(ρ)
|
|
500
572
|
const xof = XOF128(rho);
|
|
@@ -516,21 +588,14 @@ function getDilithium(opts: DilithiumOpts) {
|
|
|
516
588
|
// ▷ Compute message representative µ
|
|
517
589
|
shake256.create({ dkLen: CRH_BYTES }).update(tr).update(msg).digest();
|
|
518
590
|
|
|
519
|
-
// Compute private random seed
|
|
520
|
-
const rnd =
|
|
521
|
-
random === false
|
|
522
|
-
? new Uint8Array(32)
|
|
523
|
-
: random === undefined
|
|
524
|
-
? randomBytes(signRandBytes)
|
|
525
|
-
: random;
|
|
526
|
-
abytes(rnd, 32, 'extraEntropy');
|
|
527
591
|
const rhoprime = shake256
|
|
528
592
|
.create({ dkLen: CRH_BYTES })
|
|
529
593
|
.update(_K)
|
|
530
594
|
.update(rnd)
|
|
531
595
|
.update(mu)
|
|
532
596
|
.digest(); // ρ′← H(K||rnd||µ, 512)
|
|
533
|
-
|
|
597
|
+
// Only wipe entropy we generated; caller-provided extraEntropy stays caller-owned.
|
|
598
|
+
if (ownRnd) cleanBytes(rnd);
|
|
534
599
|
abytes(rhoprime, CRH_BYTES);
|
|
535
600
|
const x256 = XOF256(rhoprime, ZCoder.bytesLen);
|
|
536
601
|
// Rejection sampling loop
|
|
@@ -588,19 +653,21 @@ function getDilithium(opts: DilithiumOpts) {
|
|
|
588
653
|
// so only wipe the internally derived digest form here;
|
|
589
654
|
// zeroizing caller memory would break the caller's own reuse / verify path.
|
|
590
655
|
if (!externalMu) cleanBytes(mu);
|
|
591
|
-
return res
|
|
656
|
+
return res as TRet<Uint8Array>;
|
|
592
657
|
}
|
|
593
658
|
// @ts-ignore
|
|
594
659
|
throw new Error('Unreachable code path reached, report this error');
|
|
595
660
|
},
|
|
596
661
|
verify: (
|
|
597
|
-
sig: Uint8Array
|
|
598
|
-
msg: Uint8Array
|
|
599
|
-
publicKey: Uint8Array
|
|
600
|
-
opts: DSAInternalOpts = {}
|
|
662
|
+
sig: TArg<Uint8Array>,
|
|
663
|
+
msg: TArg<Uint8Array>,
|
|
664
|
+
publicKey: TArg<Uint8Array>,
|
|
665
|
+
opts: TArg<DSAInternalOpts> = {}
|
|
601
666
|
) => {
|
|
602
667
|
validateInternalOpts(opts);
|
|
603
668
|
const { externalMu = false } = opts;
|
|
669
|
+
// FIPS 204 external-mu mode expects the 64-byte message representative µ = H(tr || M).
|
|
670
|
+
if (externalMu) abytes(msg, CRH_BYTES, 'mu');
|
|
604
671
|
// ML-DSA.Verify(pk, M, σ): Verifes a signature σ for a message M.
|
|
605
672
|
const [rho, t1] = publicCoder.decode(publicKey); // (ρ, t1) ← pkDecode(pk)
|
|
606
673
|
const tr = shake256(publicKey, { dkLen: TR_BYTES }); // 6: tr ← H(BytesToBits(pk), 512)
|
|
@@ -649,51 +716,91 @@ function getDilithium(opts: DilithiumOpts) {
|
|
|
649
716
|
for (const t of z) if (polyChknorm(t, GAMMA1 - BETA)) return false;
|
|
650
717
|
return equalBytes(cTilde, c2);
|
|
651
718
|
},
|
|
652
|
-
};
|
|
653
|
-
return {
|
|
654
|
-
info: { type: 'ml-dsa' },
|
|
719
|
+
});
|
|
720
|
+
return Object.freeze({
|
|
721
|
+
info: Object.freeze({ type: 'ml-dsa' }),
|
|
655
722
|
internal,
|
|
656
723
|
securityLevel: securityLevel,
|
|
657
724
|
keygen: internal.keygen,
|
|
658
725
|
lengths: internal.lengths,
|
|
659
726
|
getPublicKey: internal.getPublicKey,
|
|
660
|
-
sign: (
|
|
727
|
+
sign: (
|
|
728
|
+
msg: TArg<Uint8Array>,
|
|
729
|
+
secretKey: TArg<Uint8Array>,
|
|
730
|
+
opts: TArg<SigOpts> = {}
|
|
731
|
+
): TRet<Uint8Array> => {
|
|
661
732
|
validateSigOpts(opts);
|
|
662
733
|
const M = getMessage(msg, opts.context);
|
|
663
734
|
const res = internal.sign(M, secretKey, opts);
|
|
664
735
|
cleanBytes(M);
|
|
665
|
-
return res
|
|
736
|
+
return res as TRet<Uint8Array>;
|
|
666
737
|
},
|
|
667
|
-
verify: (
|
|
738
|
+
verify: (
|
|
739
|
+
sig: TArg<Uint8Array>,
|
|
740
|
+
msg: TArg<Uint8Array>,
|
|
741
|
+
publicKey: TArg<Uint8Array>,
|
|
742
|
+
opts: TArg<VerOpts> = {}
|
|
743
|
+
) => {
|
|
668
744
|
validateVerOpts(opts);
|
|
745
|
+
abytes(sig, undefined, 'signature');
|
|
669
746
|
return internal.verify(sig, getMessage(msg, opts.context), publicKey);
|
|
670
747
|
},
|
|
671
|
-
prehash: (hash: CHash) => {
|
|
672
|
-
checkHash(hash, securityLevel);
|
|
673
|
-
|
|
674
|
-
|
|
748
|
+
prehash: (hash: TArg<CHash>): TRet<Signer> => {
|
|
749
|
+
checkHash(hash as CHash, securityLevel);
|
|
750
|
+
const rawHash = hash as CHash;
|
|
751
|
+
return Object.freeze({
|
|
752
|
+
info: Object.freeze({ type: 'hashml-dsa' }),
|
|
675
753
|
securityLevel: securityLevel,
|
|
676
754
|
lengths: internal.lengths,
|
|
677
755
|
keygen: internal.keygen,
|
|
678
756
|
getPublicKey: internal.getPublicKey,
|
|
679
|
-
sign: (
|
|
757
|
+
sign: (
|
|
758
|
+
msg: TArg<Uint8Array>,
|
|
759
|
+
secretKey: TArg<Uint8Array>,
|
|
760
|
+
opts: TArg<SigOpts> = {}
|
|
761
|
+
): TRet<Uint8Array> => {
|
|
680
762
|
validateSigOpts(opts);
|
|
681
|
-
const M = getMessagePrehash(
|
|
763
|
+
const M = getMessagePrehash(rawHash, msg, opts.context);
|
|
682
764
|
const res = internal.sign(M, secretKey, opts);
|
|
683
765
|
cleanBytes(M);
|
|
684
|
-
return res
|
|
766
|
+
return res as TRet<Uint8Array>;
|
|
685
767
|
},
|
|
686
|
-
verify: (
|
|
768
|
+
verify: (
|
|
769
|
+
sig: TArg<Uint8Array>,
|
|
770
|
+
msg: TArg<Uint8Array>,
|
|
771
|
+
publicKey: TArg<Uint8Array>,
|
|
772
|
+
opts: TArg<VerOpts> = {}
|
|
773
|
+
) => {
|
|
687
774
|
validateVerOpts(opts);
|
|
688
|
-
|
|
775
|
+
abytes(sig, undefined, 'signature');
|
|
776
|
+
return internal.verify(sig, getMessagePrehash(rawHash, msg, opts.context), publicKey);
|
|
689
777
|
},
|
|
690
|
-
};
|
|
778
|
+
});
|
|
691
779
|
},
|
|
692
|
-
};
|
|
780
|
+
});
|
|
693
781
|
}
|
|
694
782
|
|
|
695
|
-
/**
|
|
696
|
-
|
|
783
|
+
/**
|
|
784
|
+
* ML-DSA-44 for 128-bit security level. Not recommended after 2030, as per ASD.
|
|
785
|
+
* @example
|
|
786
|
+
* Generate deterministic ML-DSA-44 keys, sign one message, and verify the signature.
|
|
787
|
+
* ```ts
|
|
788
|
+
* import { sha256 } from '@noble/hashes/sha2.js';
|
|
789
|
+
* import { ml_dsa44 } from '@noble/post-quantum/ml-dsa.js';
|
|
790
|
+
* const seed = new Uint8Array(ml_dsa44.lengths.seed!);
|
|
791
|
+
* const { secretKey, publicKey } = ml_dsa44.keygen(seed);
|
|
792
|
+
* const msg = new TextEncoder().encode('hello noble');
|
|
793
|
+
* const sig = ml_dsa44.sign(msg, secretKey);
|
|
794
|
+
* const isValid = ml_dsa44.verify(sig, msg, publicKey);
|
|
795
|
+
* const recovered = ml_dsa44.getPublicKey(secretKey);
|
|
796
|
+
* const context = new Uint8Array([1, 2, 3]);
|
|
797
|
+
* const prehash = ml_dsa44.prehash(sha256);
|
|
798
|
+
* const preSig = prehash.sign(msg, secretKey, { context });
|
|
799
|
+
* const preValid = prehash.verify(preSig, msg, publicKey, { context });
|
|
800
|
+
* const internalSig = ml_dsa44.internal.sign(msg, secretKey);
|
|
801
|
+
* ```
|
|
802
|
+
*/
|
|
803
|
+
export const ml_dsa44: TRet<DSA> = /* @__PURE__ */ (() =>
|
|
697
804
|
getDilithium({
|
|
698
805
|
...PARAMS[2],
|
|
699
806
|
CRH_BYTES: 64,
|
|
@@ -705,7 +812,7 @@ export const ml_dsa44: DSA = /* @__PURE__ */ (() =>
|
|
|
705
812
|
}))();
|
|
706
813
|
|
|
707
814
|
/** ML-DSA-65 for 192-bit security level. Not recommended after 2030, as per ASD. */
|
|
708
|
-
export const ml_dsa65: DSA = /* @__PURE__ */ (() =>
|
|
815
|
+
export const ml_dsa65: TRet<DSA> = /* @__PURE__ */ (() =>
|
|
709
816
|
getDilithium({
|
|
710
817
|
...PARAMS[3],
|
|
711
818
|
CRH_BYTES: 64,
|
|
@@ -717,7 +824,7 @@ export const ml_dsa65: DSA = /* @__PURE__ */ (() =>
|
|
|
717
824
|
}))();
|
|
718
825
|
|
|
719
826
|
/** ML-DSA-87 for 256-bit security level. OK after 2030, as per ASD. */
|
|
720
|
-
export const ml_dsa87: DSA = /* @__PURE__ */ (() =>
|
|
827
|
+
export const ml_dsa87: TRet<DSA> = /* @__PURE__ */ (() =>
|
|
721
828
|
getDilithium({
|
|
722
829
|
...PARAMS[5],
|
|
723
830
|
CRH_BYTES: 64,
|