1use constant_time_eq::constant_time_eq;
8#[cfg(feature = "zeroize")]
9use zeroize::{Zeroize, ZeroizeOnDrop};
10
11use crate::{
12 RandomError, Xof,
13 sha3::{Shake128, Shake256},
14};
15
16pub(crate) const N: usize = 256;
18pub(crate) const SEED_SIZE: usize = 32;
20pub(crate) const CONTEXT_MAX_LEN: usize = 255;
22
23const Q: u32 = 8380417;
24const D: u32 = 13;
25const ONE: u32 = 4193792;
26const MINUS_ONE: u32 = 4186625;
27const RR: u32 = 2365951;
28const QINV: u32 = 4236238847;
29const N_INV: u32 = 16382;
30
31const MAX_LAMBDA_OVER_4: usize = 64;
32const MAX_W1_BYTES: usize = 8 * N * 6 / 8;
33const MAX_POLYZ_BYTES: usize = 20 * N / 8;
34const MAX_SIGN_ITERATIONS: usize = 814;
41
42#[derive(Debug, Clone, Copy, PartialEq, Eq)]
43pub enum MlDsaError {
44 ContextTooLong,
45 InvalidSignature,
46 InvalidPublicKey,
47 InvalidSignatureLength,
48 RetryLimitExceeded,
52 Random(RandomError),
53}
54
55#[cfg(feature = "alloc")]
56impl core::fmt::Display for MlDsaError {
57 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
58 match self {
59 MlDsaError::ContextTooLong => write!(f, "context length exceeds 255 bytes"),
60 MlDsaError::InvalidSignature => write!(f, "signature is not valid"),
61 MlDsaError::InvalidPublicKey => write!(f, "public key is not valid"),
62 MlDsaError::InvalidSignatureLength => write!(f, "signature length is not valid"),
63 MlDsaError::RetryLimitExceeded => write!(f, "signing retry limit exceeded"),
64 MlDsaError::Random(err) => write!(f, "{err}"),
65 }
66 }
67}
68
69impl From<RandomError> for MlDsaError {
70 fn from(err: RandomError) -> Self {
71 MlDsaError::Random(err)
72 }
73}
74
75#[derive(Clone, Copy)]
81pub(crate) struct MlDsaParams {
82 pub(crate) eta: u32,
84 pub(crate) tau: usize,
86 pub(crate) beta: u32,
88 pub(crate) gamma1: u32,
90 pub(crate) gamma1_bits: usize,
92 pub(crate) gamma2: u32,
94 pub(crate) gamma2_den: u32,
96 pub(crate) omega: usize,
98 pub(crate) lambda_over_4: usize,
100 pub(crate) polyz_bytes: usize,
102 pub(crate) public_key_size: usize,
104 pub(crate) signature_size: usize,
106}
107
108pub(crate) const PARAMS_44: MlDsaParams = MlDsaParams {
109 eta: 2,
110 tau: 39,
111 beta: 78,
112 gamma1: 1 << 17,
113 gamma1_bits: 17,
114 gamma2: (Q - 1) / 88,
115 gamma2_den: 88,
116 omega: 80,
117 lambda_over_4: 32,
118 polyz_bytes: 18 * N / 8,
119 public_key_size: 1312,
120 signature_size: 2420,
121};
122
123pub(crate) const PARAMS_65: MlDsaParams = MlDsaParams {
124 eta: 4,
125 tau: 49,
126 beta: 196,
127 gamma1: 1 << 19,
128 gamma1_bits: 19,
129 gamma2: (Q - 1) / 32,
130 gamma2_den: 32,
131 omega: 55,
132 lambda_over_4: 48,
133 polyz_bytes: 20 * N / 8,
134 public_key_size: 1952,
135 signature_size: 3309,
136};
137
138pub(crate) const PARAMS_87: MlDsaParams = MlDsaParams {
139 eta: 2,
140 tau: 60,
141 beta: 120,
142 gamma1: 1 << 19,
143 gamma1_bits: 19,
144 gamma2: (Q - 1) / 32,
145 gamma2_den: 32,
146 omega: 75,
147 lambda_over_4: 64,
148 polyz_bytes: 20 * N / 8,
149 public_key_size: 2592,
150 signature_size: 4627,
151};
152
153type FieldElement = u32;
154
155#[inline]
161fn wipe(bytes: &mut [u8]) {
162 #[cfg(feature = "zeroize")]
163 {
164 use zeroize::Zeroize;
165 bytes.zeroize();
166 }
167 #[cfg(not(feature = "zeroize"))]
168 {
169 let _ = bytes;
170 }
171}
172
173fn field_to_montgomery(a: u32) -> FieldElement {
174 debug_assert!(a < Q);
175 field_montgomery_mul(a, RR)
176}
177
178fn field_from_montgomery(a: FieldElement) -> u32 {
179 field_montgomery_reduce(a as u64)
180}
181
182fn field_montgomery_reduce(x: u64) -> u32 {
183 let t = (x as u32).wrapping_mul(QINV);
184 let u = (x + (t as u64) * (Q as u64)) >> 32;
185 field_reduce_once(u as u32)
186}
187
188fn field_montgomery_mul(a: FieldElement, b: FieldElement) -> FieldElement {
189 field_montgomery_reduce(a as u64 * b as u64)
190}
191
192fn field_reduce_once(x: u32) -> FieldElement {
193 let t = x.wrapping_sub(Q);
194 let mask = ((t as i32) >> 31) as u32;
195 t.wrapping_add(Q & mask)
196}
197
198fn field_add(a: FieldElement, b: FieldElement) -> FieldElement {
199 field_reduce_once(a.wrapping_add(b))
200}
201
202fn field_sub(a: FieldElement, b: FieldElement) -> FieldElement {
203 field_reduce_once(a.wrapping_sub(b).wrapping_add(Q))
204}
205
206fn field_sub_to_montgomery(a: u32, b: u32) -> FieldElement {
207 let x = a.wrapping_sub(b).wrapping_add(Q);
208 field_montgomery_mul(x, RR)
209}
210
211fn field_infinity_norm(r: FieldElement) -> u32 {
212 let x = field_from_montgomery(r);
213 let q_minus_x = Q - x;
214 let half_q = Q / 2;
215 let mask = ((half_q.wrapping_sub(x)) as i32 >> 31) as u32;
216 (mask & q_minus_x) | (!mask & x)
217}
218
219fn field_centered_mod(r: FieldElement) -> i32 {
220 let x = field_from_montgomery(r);
221 let x = x as i32;
222 let half_q = (Q / 2) as i32;
223 let mask = ((half_q - x) >> 31) as i32;
224 (mask & (x - Q as i32)) | (!mask & x)
225}
226
227fn power2round(r: FieldElement) -> (u16, FieldElement) {
228 let rr = field_from_montgomery(r);
229 let r1 = (rr + (1 << 12) - 1) >> 13;
230 let r0 = field_sub_to_montgomery(rr, r1 << 13);
231 (r1 as u16, r0)
232}
233
234fn highbits32(x: u32) -> u8 {
235 let r1 = (x + 127) >> 7;
236 let r1 = (r1 * 1025 + (1 << 21)) >> 22;
237 (r1 & 0b1111) as u8
238}
239
240fn highbits88(x: u32) -> u8 {
241 let r1 = (x + 127) >> 7;
242 let r1 = (r1 * 11275 + (1 << 23)) >> 24;
243 let d = r1 ^ 44;
245 let not_eq = (d | d.wrapping_neg()) >> 31;
246 (r1 * not_eq) as u8
247}
248
249fn highbits(x: u32, gamma2_den: u32) -> u8 {
250 match gamma2_den {
251 32 => highbits32(x),
252 88 => highbits88(x),
253 _ => unreachable!(),
254 }
255}
256
257fn decompose32(r: FieldElement) -> (u8, i32) {
258 let x = field_from_montgomery(r) as i32;
259 let r1 = highbits32(x as u32);
260 let r0 = x - (r1 as i32) * 2 * (Q as i32 - 1) / 32;
261 let half_q = (Q / 2) as i32;
262 let mask = ((half_q - r0) >> 31) as i32;
263 let r0 = (mask & (r0 - Q as i32)) | (!mask & r0);
264 (r1, r0)
265}
266
267fn decompose88(r: FieldElement) -> (u8, i32) {
268 let x = field_from_montgomery(r) as i32;
269 let r1 = highbits88(x as u32);
270 let r0 = x - (r1 as i32) * 2 * (Q as i32 - 1) / 88;
271 let half_q = (Q / 2) as i32;
272 let mask = ((half_q - r0) >> 31) as i32;
273 let r0 = (mask & (r0 - Q as i32)) | (!mask & r0);
274 (r1, r0)
275}
276
277fn decompose(r: FieldElement, gamma2_den: u32) -> (u8, i32) {
278 match gamma2_den {
279 32 => decompose32(r),
280 88 => decompose88(r),
281 _ => unreachable!(),
282 }
283}
284
285fn make_hint32(ct0: FieldElement, w: FieldElement, cs2: FieldElement) -> u8 {
286 let r_plus_z = field_sub(w, cs2);
287 let v1 = highbits32(field_from_montgomery(r_plus_z));
288 let r = field_add(r_plus_z, ct0);
289 let r1 = highbits32(field_from_montgomery(r));
290 (v1 != r1) as u8
291}
292
293fn make_hint88(ct0: FieldElement, w: FieldElement, cs2: FieldElement) -> u8 {
294 let r_plus_z = field_sub(w, cs2);
295 let v1 = highbits88(field_from_montgomery(r_plus_z));
296 let r = field_add(r_plus_z, ct0);
297 let r1 = highbits88(field_from_montgomery(r));
298 (v1 != r1) as u8
299}
300
301fn make_hint(ct0: FieldElement, w: FieldElement, cs2: FieldElement, gamma2_den: u32) -> u8 {
302 match gamma2_den {
303 32 => make_hint32(ct0, w, cs2),
304 88 => make_hint88(ct0, w, cs2),
305 _ => unreachable!(),
306 }
307}
308
309fn use_hint32(r: FieldElement, hint: u8) -> u8 {
310 let (r1, r0) = decompose32(r);
311 if hint == 0 {
312 return r1;
313 }
314 let r0_gt_0 = !(r0.wrapping_sub(1) >> 31) as u8;
315 let r1_plus = r1.wrapping_add(1) & 0x0F;
316 let r1_minus = r1.wrapping_sub(1) & 0x0F;
317 (r0_gt_0 & r1_plus) | ((!r0_gt_0) & r1_minus)
318}
319
320fn use_hint88(r: FieldElement, hint: u8) -> u8 {
321 const M: u8 = 44;
322 let (mut r1, r0) = decompose88(r);
323 if hint == 0 {
324 return r1;
325 }
326 if r0 > 0 {
327 if r1 == M - 1 {
328 r1 = 0;
329 } else {
330 r1 += 1;
331 }
332 } else if r1 == 0 {
333 r1 = M - 1;
334 } else {
335 r1 -= 1;
336 }
337 r1
338}
339
340fn use_hint(r: FieldElement, hint: u8, gamma2_den: u32) -> u8 {
341 match gamma2_den {
342 32 => use_hint32(r, hint),
343 88 => use_hint88(r, hint),
344 _ => unreachable!(),
345 }
346}
347
348#[derive(Clone, Debug, PartialEq, Eq)]
349#[cfg_attr(feature = "zeroize", derive(Zeroize, ZeroizeOnDrop))]
350struct Poly {
351 coeffs: [FieldElement; N],
352}
353
354impl Default for Poly {
355 fn default() -> Self {
356 Self {
357 coeffs: [0u32; N],
358 }
359 }
360}
361
362#[derive(Clone, Debug, PartialEq, Eq)]
363#[cfg_attr(feature = "zeroize", derive(Zeroize, ZeroizeOnDrop))]
364struct NttPoly {
365 coeffs: [FieldElement; N],
366}
367
368impl Default for NttPoly {
369 fn default() -> Self {
370 Self {
371 coeffs: [0u32; N],
372 }
373 }
374}
375
376fn poly_add(a: &Poly, b: &Poly) -> Poly {
377 let mut r = Poly::default();
378 for i in 0..N {
379 r.coeffs[i] = field_add(a.coeffs[i], b.coeffs[i]);
380 }
381 r
382}
383
384fn poly_sub(a: &Poly, b: &Poly) -> Poly {
385 let mut r = Poly::default();
386 for i in 0..N {
387 r.coeffs[i] = field_sub(a.coeffs[i], b.coeffs[i]);
388 }
389 r
390}
391
392fn ntt_add(a: &NttPoly, b: &NttPoly) -> NttPoly {
393 let mut r = NttPoly::default();
394 for i in 0..N {
395 r.coeffs[i] = field_add(a.coeffs[i], b.coeffs[i]);
396 }
397 r
398}
399
400fn ntt_sub(a: &NttPoly, b: &NttPoly) -> NttPoly {
401 let mut r = NttPoly::default();
402 for i in 0..N {
403 r.coeffs[i] = field_sub(a.coeffs[i], b.coeffs[i]);
404 }
405 r
406}
407
408fn ntt_mul(a: &NttPoly, b: &NttPoly) -> NttPoly {
409 #[cfg(all(target_arch = "aarch64", target_feature = "neon"))]
410 {
411 neon::ntt_mul(a, b)
412 }
413 #[cfg(all(target_arch = "x86_64", feature = "std"))]
414 {
415 if is_x86_feature_detected!("avx2") {
416 unsafe { avx2::ntt_mul(a, b) }
418 } else {
419 ntt_mul_scalar(a, b)
420 }
421 }
422 #[cfg(not(any(
423 all(target_arch = "aarch64", target_feature = "neon"),
424 all(target_arch = "x86_64", feature = "std"),
425 )))]
426 {
427 ntt_mul_scalar(a, b)
428 }
429}
430
431#[cfg(any(test, not(all(target_arch = "aarch64", target_feature = "neon"))))]
432fn ntt_mul_scalar(a: &NttPoly, b: &NttPoly) -> NttPoly {
433 let mut r = NttPoly::default();
434 for i in 0..N {
435 r.coeffs[i] = field_montgomery_mul(a.coeffs[i], b.coeffs[i]);
436 }
437 r
438}
439
440const ZETAS: [FieldElement; 256] = [
441 4193792, 25847, 5771523, 7861508, 237124, 7602457, 7504169, 466468, 1826347, 2353451, 8021166, 6288512, 3119733,
442 5495562, 3111497, 2680103, 2725464, 1024112, 7300517, 3585928, 7830929, 7260833, 2619752, 6271868, 6262231,
443 4520680, 6980856, 5102745, 1757237, 8360995, 4010497, 280005, 2706023, 95776, 3077325, 3530437, 6718724, 4788269,
444 5842901, 3915439, 4519302, 5336701, 3574422, 5512770, 3539968, 8079950, 2348700, 7841118, 6681150, 6736599,
445 3505694, 4558682, 3507263, 6239768, 6779997, 3699596, 811944, 531354, 954230, 3881043, 3900724, 5823537, 2071892,
446 5582638, 4450022, 6851714, 4702672, 5339162, 6927966, 3475950, 2176455, 6795196, 7122806, 1939314, 4296819,
447 7380215, 5190273, 5223087, 4747489, 126922, 3412210, 7396998, 2147896, 2715295, 5412772, 4686924, 7969390, 5903370,
448 7709315, 7151892, 8357436, 7072248, 7998430, 1349076, 1852771, 6949987, 5037034, 264944, 508951, 3097992, 44288,
449 7280319, 904516, 3958618, 4656075, 8371839, 1653064, 5130689, 2389356, 8169440, 759969, 7063561, 189548, 4827145,
450 3159746, 6529015, 5971092, 8202977, 1315589, 1341330, 1285669, 6795489, 7567685, 6940675, 5361315, 4499357,
451 4751448, 3839961, 2091667, 3407706, 2316500, 3817976, 5037939, 2244091, 5933984, 4817955, 266997, 2434439, 7144689,
452 3513181, 4860065, 4621053, 7183191, 5187039, 900702, 1859098, 909542, 819034, 495491, 6767243, 8337157, 7857917,
453 7725090, 5257975, 2031748, 3207046, 4823422, 7855319, 7611795, 4784579, 342297, 286988, 5942594, 4108315, 3437287,
454 5038140, 1735879, 203044, 2842341, 2691481, 5790267, 1265009, 4055324, 1247620, 2486353, 1595974, 4613401, 1250494,
455 2635921, 4832145, 5386378, 1869119, 1903435, 7329447, 7047359, 1237275, 5062207, 6950192, 7929317, 1312455,
456 3306115, 6417775, 7100756, 1917081, 5834105, 7005614, 1500165, 777191, 2235880, 3406031, 7838005, 5548557, 6709241,
457 6533464, 5796124, 4656147, 594136, 4603424, 6366809, 2432395, 2454455, 8215696, 1957272, 3369112, 185531, 7173032,
458 5196991, 162844, 1616392, 3014001, 810149, 1652634, 4686184, 6581310, 5341501, 3523897, 3866901, 269760, 2213111,
459 7404533, 1717735, 472078, 7953734, 1723600, 6577327, 1910376, 6712985, 7276084, 8119771, 4546524, 5441381, 6144432,
460 7959518, 6094090, 183443, 7403526, 1612842, 4834730, 7826001, 3919660, 8332111, 7018208, 3937738, 1400424, 7534263,
461 1976782,
462];
463
464fn ntt(f: &Poly) -> NttPoly {
465 #[cfg(all(target_arch = "aarch64", target_feature = "neon"))]
466 {
467 neon::ntt(f)
468 }
469 #[cfg(all(target_arch = "x86_64", feature = "std"))]
470 {
471 if is_x86_feature_detected!("avx2") {
472 unsafe { avx2::ntt(f) }
474 } else {
475 ntt_scalar(f)
476 }
477 }
478 #[cfg(not(any(
479 all(target_arch = "aarch64", target_feature = "neon"),
480 all(target_arch = "x86_64", feature = "std"),
481 )))]
482 {
483 ntt_scalar(f)
484 }
485}
486
487#[cfg(any(test, not(all(target_arch = "aarch64", target_feature = "neon"))))]
488fn ntt_scalar(f: &Poly) -> NttPoly {
489 let mut f = NttPoly {
490 coeffs: f.coeffs,
491 };
492 let mut m: usize = 0;
493
494 let mut len: usize = 128;
495 while len >= 8 {
496 let mut start: usize = 0;
497 while start < N {
498 m += 1;
499 let zeta = ZETAS[m];
500 let mid = start + len;
501 for j in (start..mid).step_by(2) {
502 let t = field_montgomery_mul(zeta, f.coeffs[j + len]);
503 f.coeffs[j + len] = field_sub(f.coeffs[j], t);
504 f.coeffs[j] = field_add(f.coeffs[j], t);
505 let t = field_montgomery_mul(zeta, f.coeffs[j + len + 1]);
506 f.coeffs[j + len + 1] = field_sub(f.coeffs[j + 1], t);
507 f.coeffs[j + 1] = field_add(f.coeffs[j + 1], t);
508 }
509 start += 2 * len;
510 }
511 len /= 2;
512 }
513
514 let mut start: usize = 0;
515 while start < N {
516 m += 1;
517 let zeta = ZETAS[m];
518 let t = field_montgomery_mul(zeta, f.coeffs[start + 4]);
519 f.coeffs[start + 4] = field_sub(f.coeffs[start], t);
520 f.coeffs[start] = field_add(f.coeffs[start], t);
521 let t = field_montgomery_mul(zeta, f.coeffs[start + 5]);
522 f.coeffs[start + 5] = field_sub(f.coeffs[start + 1], t);
523 f.coeffs[start + 1] = field_add(f.coeffs[start + 1], t);
524 let t = field_montgomery_mul(zeta, f.coeffs[start + 6]);
525 f.coeffs[start + 6] = field_sub(f.coeffs[start + 2], t);
526 f.coeffs[start + 2] = field_add(f.coeffs[start + 2], t);
527 let t = field_montgomery_mul(zeta, f.coeffs[start + 7]);
528 f.coeffs[start + 7] = field_sub(f.coeffs[start + 3], t);
529 f.coeffs[start + 3] = field_add(f.coeffs[start + 3], t);
530 start += 8;
531 }
532
533 start = 0;
534 while start < N {
535 m += 1;
536 let zeta = ZETAS[m];
537 let t = field_montgomery_mul(zeta, f.coeffs[start + 2]);
538 f.coeffs[start + 2] = field_sub(f.coeffs[start], t);
539 f.coeffs[start] = field_add(f.coeffs[start], t);
540 let t = field_montgomery_mul(zeta, f.coeffs[start + 3]);
541 f.coeffs[start + 3] = field_sub(f.coeffs[start + 1], t);
542 f.coeffs[start + 1] = field_add(f.coeffs[start + 1], t);
543 start += 4;
544 }
545
546 start = 0;
547 while start < N {
548 m += 1;
549 let zeta = ZETAS[m];
550 let t = field_montgomery_mul(zeta, f.coeffs[start + 1]);
551 f.coeffs[start + 1] = field_sub(f.coeffs[start], t);
552 f.coeffs[start] = field_add(f.coeffs[start], t);
553 start += 2;
554 }
555
556 f
557}
558
559fn invntt(f: &NttPoly) -> Poly {
560 #[cfg(all(target_arch = "aarch64", target_feature = "neon"))]
561 {
562 neon::invntt(f)
563 }
564 #[cfg(all(target_arch = "x86_64", feature = "std"))]
565 {
566 if is_x86_feature_detected!("avx2") {
567 unsafe { avx2::invntt(f) }
569 } else {
570 invntt_scalar(f)
571 }
572 }
573 #[cfg(not(any(
574 all(target_arch = "aarch64", target_feature = "neon"),
575 all(target_arch = "x86_64", feature = "std"),
576 )))]
577 {
578 invntt_scalar(f)
579 }
580}
581
582#[cfg(any(test, not(all(target_arch = "aarch64", target_feature = "neon"))))]
583fn invntt_scalar(f: &NttPoly) -> Poly {
584 let mut f = NttPoly {
585 coeffs: f.coeffs,
586 };
587 let mut m: usize = 255;
588
589 let mut start: usize = 0;
590 while start < N {
591 let zeta = ZETAS[m];
592 m -= 1;
593 let t = f.coeffs[start];
594 f.coeffs[start] = field_add(t, f.coeffs[start + 1]);
595 f.coeffs[start + 1] = field_montgomery_mul(zeta, field_sub(f.coeffs[start + 1], t));
596 start += 2;
597 }
598
599 start = 0;
600 while start < N {
601 let zeta = ZETAS[m];
602 m -= 1;
603 let t = f.coeffs[start];
604 f.coeffs[start] = field_add(t, f.coeffs[start + 2]);
605 f.coeffs[start + 2] = field_montgomery_mul(zeta, field_sub(f.coeffs[start + 2], t));
606 let t = f.coeffs[start + 1];
607 f.coeffs[start + 1] = field_add(t, f.coeffs[start + 3]);
608 f.coeffs[start + 3] = field_montgomery_mul(zeta, field_sub(f.coeffs[start + 3], t));
609 start += 4;
610 }
611
612 start = 0;
613 while start < N {
614 let zeta = ZETAS[m];
615 m -= 1;
616 let t = f.coeffs[start];
617 f.coeffs[start] = field_add(t, f.coeffs[start + 4]);
618 f.coeffs[start + 4] = field_montgomery_mul(zeta, field_sub(f.coeffs[start + 4], t));
619 let t = f.coeffs[start + 1];
620 f.coeffs[start + 1] = field_add(t, f.coeffs[start + 5]);
621 f.coeffs[start + 5] = field_montgomery_mul(zeta, field_sub(f.coeffs[start + 5], t));
622 let t = f.coeffs[start + 2];
623 f.coeffs[start + 2] = field_add(t, f.coeffs[start + 6]);
624 f.coeffs[start + 6] = field_montgomery_mul(zeta, field_sub(f.coeffs[start + 6], t));
625 let t = f.coeffs[start + 3];
626 f.coeffs[start + 3] = field_add(t, f.coeffs[start + 7]);
627 f.coeffs[start + 7] = field_montgomery_mul(zeta, field_sub(f.coeffs[start + 7], t));
628 start += 8;
629 }
630
631 let mut len: usize = 8;
632 while len < N {
633 let mut start: usize = 0;
634 while start < N {
635 let zeta = ZETAS[m];
636 m -= 1;
637 let mid = start + len;
638 for j in (start..mid).step_by(2) {
639 let t = f.coeffs[j];
640 f.coeffs[j] = field_add(t, f.coeffs[j + len]);
641 let diff = field_sub(f.coeffs[j + len], t);
642 f.coeffs[j + len] = field_montgomery_mul(zeta, diff);
643 let t = f.coeffs[j + 1];
644 f.coeffs[j + 1] = field_add(t, f.coeffs[j + len + 1]);
645 let diff = field_sub(f.coeffs[j + len + 1], t);
646 f.coeffs[j + len + 1] = field_montgomery_mul(zeta, diff);
647 }
648 start += 2 * len;
649 }
650 len *= 2;
651 }
652
653 let mut r = Poly::default();
654 for i in 0..N {
655 r.coeffs[i] = field_montgomery_mul(f.coeffs[i], N_INV);
656 }
657 r
658}
659
660fn sample_ntt(rho: &[u8; 32], s: u8, r: u8) -> NttPoly {
661 let mut shake = Shake128::new();
662 shake.absorb(rho);
663 shake.absorb(&[s, r]);
664
665 let mut a = NttPoly::default();
666 let mut j: usize = 0;
667 let mut buf = [0u8; 168];
668 let mut off: usize = 168;
669
670 loop {
671 if off >= 168 {
672 shake.squeeze(&mut buf);
673 off = 0;
674 }
675 let v = (buf[off] as u32) | ((buf[off + 1] as u32) << 8) | ((buf[off + 2] as u32) << 16);
676 off += 3;
677 let v = v & 0x7FFFFF;
678 if v < Q {
679 a.coeffs[j] = field_to_montgomery(v);
680 j += 1;
681 if j >= N {
682 break;
683 }
684 }
685 }
686 a
687}
688
689fn coeff_from_half_byte(b: u8, eta: u32) -> Option<FieldElement> {
690 match eta {
691 2 => {
692 if b > 14 {
693 None
694 } else {
695 Some(field_sub_to_montgomery(2, (b % 5) as u32))
696 }
697 }
698 4 => {
699 if b > 8 {
700 None
701 } else {
702 Some(field_sub_to_montgomery(4, b as u32))
703 }
704 }
705 _ => unreachable!(),
706 }
707}
708
709fn sample_bounded_poly(rho: &[u8], r: u8, eta: u32) -> Poly {
710 let mut shake = Shake256::new();
711 shake.absorb(rho);
712 shake.absorb(&[r, 0]);
713
714 let mut a = Poly::default();
715 let mut j: usize = 0;
716 let mut buf = [0u8; 136];
717 let mut off: usize = 136;
718
719 loop {
720 if off >= 136 {
721 shake.squeeze(&mut buf);
722 off = 0;
723 }
724 let z0 = buf[off] & 0x0F;
725 let z1 = buf[off] >> 4;
726 off += 1;
727
728 if let Some(c) = coeff_from_half_byte(z0, eta) {
729 a.coeffs[j] = c;
730 j += 1;
731 if j >= N {
732 break;
733 }
734 }
735 if let Some(c) = coeff_from_half_byte(z1, eta) {
736 a.coeffs[j] = c;
737 j += 1;
738 if j >= N {
739 break;
740 }
741 }
742 }
743 a
744}
745
746fn sample_in_ball(rho: &[u8], tau: usize) -> Poly {
747 let mut shake = Shake256::new();
748 shake.absorb(rho);
749 let mut s = [0u8; 8];
750 shake.squeeze(&mut s);
751
752 let mut c = Poly::default();
753 let mut signs: u64 = u64::from_le_bytes(s);
754
755 for i in (N - tau)..N {
756 let mut jb = [0u8; 1];
757 loop {
758 shake.squeeze(&mut jb);
759 if jb[0] as usize <= i {
760 break;
761 }
762 }
763 let j = jb[0] as usize;
764 c.coeffs[i] = c.coeffs[j];
765 if (signs & 1) == 0 {
766 c.coeffs[j] = ONE;
767 } else {
768 c.coeffs[j] = MINUS_ONE;
769 }
770 signs >>= 1;
771 }
772
773 c
774}
775
776fn expand_mask(nonce: &[u8; 64], kappa: usize, params: &MlDsaParams) -> Poly {
777 let mut shake = Shake256::new();
778 shake.absorb(nonce);
779 shake.absorb(&(kappa as u16).to_le_bytes());
780
781 let mut buf = [0u8; MAX_POLYZ_BYTES];
782 shake.squeeze(&mut buf[..params.polyz_bytes]);
783 bitunpack(&buf[..params.polyz_bytes], params.gamma1_bits)
784}
785
786fn highbits_poly(w: &Poly, params: &MlDsaParams) -> [u8; N] {
787 let mut r = [0u8; N];
788 for i in 0..N {
789 r[i] = highbits(field_from_montgomery(w.coeffs[i]), params.gamma2_den);
790 }
791 r
792}
793
794fn make_hint_poly(ct0: &Poly, w: &Poly, cs2: &Poly, params: &MlDsaParams) -> ([u8; N], usize) {
795 let mut h = [0u8; N];
796 let mut count = 0usize;
797 for i in 0..N {
798 h[i] = make_hint(ct0.coeffs[i], w.coeffs[i], cs2.coeffs[i], params.gamma2_den);
799 count += h[i] as usize;
800 }
801 (h, count)
802}
803
804fn use_hint_poly(r: &Poly, h: &[u8; N], params: &MlDsaParams) -> [u8; N] {
805 let mut w = [0u8; N];
806 for i in 0..N {
807 w[i] = use_hint(r.coeffs[i], h[i], params.gamma2_den);
808 }
809 w
810}
811
812fn coefficients_exceed_bound(w: &Poly, bound: u32) -> bool {
818 let mut exceeded = 0u32;
819 for &c in w.coeffs.iter() {
820 exceeded |= (field_infinity_norm(c) >= bound) as u32;
821 }
822 exceeded != 0
823}
824
825fn lowbits_exceed_bound(w: &Poly, bound: u32, gamma2_den: u32) -> bool {
832 let mut exceeded = 0u32;
833 for &c in w.coeffs.iter() {
834 let (_, r0) = decompose(c, gamma2_den);
835 let abs_r0 = (r0 ^ (r0 >> 31)).wrapping_sub(r0 >> 31) as u32;
836 exceeded |= (abs_r0 >= bound) as u32;
837 }
838 exceeded != 0
839}
840
841fn pk_encode<const K: usize>(rho: &[u8; 32], t1: &[[u16; N]; K], out: &mut [u8]) {
842 debug_assert_eq!(out.len(), 32 + K * N * 10 / 8);
843 out[..32].copy_from_slice(rho);
844 let mut pos = 32;
845
846 for w in t1.iter() {
847 for i in (0..N).step_by(4) {
848 let c0 = w[i] as u32;
849 let c1 = w[i + 1] as u32;
850 let c2 = w[i + 2] as u32;
851 let c3 = w[i + 3] as u32;
852 out[pos] = (c0 & 0xFF) as u8;
853 out[pos + 1] = ((c0 >> 8) | (c1 << 2)) as u8;
854 out[pos + 2] = ((c1 >> 6) | (c2 << 4)) as u8;
855 out[pos + 3] = ((c2 >> 4) | (c3 << 6)) as u8;
856 out[pos + 4] = (c3 >> 2) as u8;
857 pos += 5;
858 }
859 }
860}
861
862fn pk_decode<const K: usize>(params: &MlDsaParams, pk: &[u8]) -> Result<([u8; 32], [[u16; N]; K]), MlDsaError> {
863 if pk.len() != params.public_key_size {
864 return Err(MlDsaError::InvalidPublicKey);
865 }
866 let mut rho = [0u8; 32];
867 rho.copy_from_slice(&pk[..32]);
868 let mut t1 = [[0u16; N]; K];
869 let mut pos = 32;
870
871 for r in 0..K {
872 for i in (0..N).step_by(4) {
873 let b0 = pk[pos] as u16;
874 let b1 = pk[pos + 1] as u16;
875 let b2 = pk[pos + 2] as u16;
876 let b3 = pk[pos + 3] as u16;
877 let b4 = pk[pos + 4] as u16;
878 t1[r][i] = b0 | ((b1 & 0b0000_0011) << 8);
879 t1[r][i + 1] = (b1 >> 2) | ((b2 & 0b0000_1111) << 6);
880 t1[r][i + 2] = (b2 >> 4) | ((b3 & 0b0011_1111) << 4);
881 t1[r][i + 3] = (b3 >> 6) | ((b4 & 0b1111_1111) << 2);
882 pos += 5;
883 }
884 }
885 Ok((rho, t1))
886}
887
888fn bitpack_18(z: &Poly, out: &mut [u8]) {
889 const B: u32 = 1 << 17;
890 let mut q = 0usize;
891
892 for i in (0..N).step_by(4) {
893 let w0 = (B as i32 - field_centered_mod(z.coeffs[i])) as u32;
894 out[q] = w0 as u8;
895 out[q + 1] = (w0 >> 8) as u8;
896 out[q + 2] = (w0 >> 16) as u8;
897 let w1 = (B as i32 - field_centered_mod(z.coeffs[i + 1])) as u32;
898 out[q + 2] |= (w1 << 2) as u8;
899 out[q + 3] = (w1 >> 6) as u8;
900 out[q + 4] = (w1 >> 14) as u8;
901 let w2 = (B as i32 - field_centered_mod(z.coeffs[i + 2])) as u32;
902 out[q + 4] |= (w2 << 4) as u8;
903 out[q + 5] = (w2 >> 4) as u8;
904 out[q + 6] = (w2 >> 12) as u8;
905 let w3 = (B as i32 - field_centered_mod(z.coeffs[i + 3])) as u32;
906 out[q + 6] |= (w3 << 6) as u8;
907 out[q + 7] = (w3 >> 2) as u8;
908 out[q + 8] = (w3 >> 10) as u8;
909 q += 9;
910 }
911}
912
913fn bitpack_20(z: &Poly, out: &mut [u8]) {
914 let b = 1u32 << 19;
915 let mut q = 0usize;
916
917 for i in (0..N).step_by(2) {
918 let w0 = (b as i32 - field_centered_mod(z.coeffs[i])) as u32;
919 out[q] = w0 as u8;
920 out[q + 1] = (w0 >> 8) as u8;
921 out[q + 2] = (w0 >> 16) as u8;
922 let w1 = (b as i32 - field_centered_mod(z.coeffs[i + 1])) as u32;
923 out[q + 2] |= ((w1 & 0x0F) << 4) as u8;
924 out[q + 3] = (w1 >> 4) as u8;
925 out[q + 4] = (w1 >> 12) as u8;
926 q += 5;
927 }
928}
929
930fn bitpack(z: &Poly, gamma1_bits: usize, out: &mut [u8]) {
931 match gamma1_bits {
932 17 => bitpack_18(z, out),
933 19 => bitpack_20(z, out),
934 _ => unreachable!(),
935 }
936}
937
938fn bitunpack_18(v: &[u8]) -> Poly {
939 const B: u32 = 1 << 17;
940 const MASK18: u32 = (1 << 18) - 1;
941 let mut r = Poly::default();
942 let mut p = v;
943
944 for i in (0..N).step_by(4) {
945 let w0 = (p[0] as u32) | ((p[1] as u32) << 8) | ((p[2] as u32) << 16);
946 r.coeffs[i] = field_sub_to_montgomery(B, w0 & MASK18);
947 let w1 = ((p[2] as u32) >> 2) | ((p[3] as u32) << 6) | ((p[4] as u32) << 14);
948 r.coeffs[i + 1] = field_sub_to_montgomery(B, w1 & MASK18);
949 let w2 = ((p[4] as u32) >> 4) | ((p[5] as u32) << 4) | ((p[6] as u32) << 12);
950 r.coeffs[i + 2] = field_sub_to_montgomery(B, w2 & MASK18);
951 let w3 = ((p[6] as u32) >> 6) | ((p[7] as u32) << 2) | ((p[8] as u32) << 10);
952 r.coeffs[i + 3] = field_sub_to_montgomery(B, w3 & MASK18);
953 p = &p[9..];
954 }
955 r
956}
957
958fn bitunpack_20(v: &[u8]) -> Poly {
959 let b = 1u32 << 19;
960 let mask20 = (1u32 << 20) - 1;
961 let mut r = Poly::default();
962 let mut p = v;
963
964 for i in (0..N).step_by(2) {
965 let w0 = (p[0] as u32) | ((p[1] as u32) << 8) | ((p[2] as u32) << 16);
966 r.coeffs[i] = field_sub_to_montgomery(b, w0 & mask20);
967 let w1 = ((p[2] as u32) >> 4) | ((p[3] as u32) << 4) | ((p[4] as u32) << 12);
968 r.coeffs[i + 1] = field_sub_to_montgomery(b, w1 & mask20);
969 p = &p[5..];
970 }
971 r
972}
973
974fn bitunpack(v: &[u8], gamma1_bits: usize) -> Poly {
975 match gamma1_bits {
976 17 => bitunpack_18(v),
977 19 => bitunpack_20(v),
978 _ => unreachable!(),
979 }
980}
981
982fn hint_encode<const K: usize>(params: &MlDsaParams, h: &[[u8; N]; K], out: &mut [u8]) {
983 let omega = params.omega;
984 debug_assert_eq!(out.len(), omega + K);
985 out.fill(0);
986 let mut idx: usize = 0;
987
988 for i in 0..K {
989 for j in 0..N {
990 if h[i][j] != 0 {
991 out[idx] = j as u8;
992 idx += 1;
993 }
994 }
995 out[omega + i] = idx as u8;
996 }
997}
998
999fn hint_decode<const K: usize>(params: &MlDsaParams, sig: &[u8]) -> Result<[[u8; N]; K], MlDsaError> {
1000 let omega = params.omega;
1001 debug_assert_eq!(sig.len(), omega + K);
1002 let mut h = [[0u8; N]; K];
1003 let mut idx: usize = 0;
1004
1005 for i in 0..K {
1006 let limit = sig[omega + i] as usize;
1007 if limit < idx || limit > omega {
1008 return Err(MlDsaError::InvalidSignature);
1009 }
1010 let poly_start = idx;
1012 while idx < limit {
1013 let j = sig[idx] as usize;
1014 if idx > poly_start && sig[idx - 1] as usize >= j {
1016 return Err(MlDsaError::InvalidSignature);
1017 }
1018 if j >= N {
1019 return Err(MlDsaError::InvalidSignature);
1020 }
1021 h[i][j] = 1;
1022 idx += 1;
1023 }
1024 }
1025 for k in idx..omega {
1026 if sig[k] != 0 {
1027 return Err(MlDsaError::InvalidSignature);
1028 }
1029 }
1030 Ok(h)
1031}
1032
1033fn sig_encode<const K: usize, const L: usize>(
1034 params: &MlDsaParams,
1035 ch: &[u8],
1036 z: &[Poly; L],
1037 h: &[[u8; N]; K],
1038 out: &mut [u8],
1039) {
1040 debug_assert_eq!(out.len(), params.signature_size);
1041 let lo4 = params.lambda_over_4;
1042 out[..lo4].copy_from_slice(ch);
1043
1044 let mut pos = lo4;
1045 for poly in z.iter() {
1046 let pz = params.polyz_bytes;
1047 bitpack(poly, params.gamma1_bits, &mut out[pos..pos + pz]);
1048 pos += pz;
1049 }
1050
1051 hint_encode::<K>(params, h, &mut out[pos..]);
1052}
1053
1054fn sig_decode<const K: usize, const L: usize>(
1055 params: &MlDsaParams,
1056 sig: &[u8],
1057) -> Result<([u8; MAX_LAMBDA_OVER_4], [Poly; L], [[u8; N]; K]), MlDsaError> {
1058 if sig.len() != params.signature_size {
1059 return Err(MlDsaError::InvalidSignatureLength);
1060 }
1061 let lo4 = params.lambda_over_4;
1062 let mut ch = [0u8; MAX_LAMBDA_OVER_4];
1063 ch[..lo4].copy_from_slice(&sig[..lo4]);
1064
1065 let mut z: [Poly; L] = core::array::from_fn(|_| Poly::default());
1066 let mut pos = lo4;
1067 for poly in z.iter_mut() {
1068 let pz = params.polyz_bytes;
1069 *poly = bitunpack(&sig[pos..pos + pz], params.gamma1_bits);
1070 pos += pz;
1071 }
1072
1073 let h = hint_decode::<K>(params, &sig[pos..])?;
1074
1075 Ok((ch, z, h))
1076}
1077
1078fn w1_encode<const K: usize>(params: &MlDsaParams, w1: &[[u8; N]; K]) -> ([u8; MAX_W1_BYTES], usize) {
1079 let mut buf = [0u8; MAX_W1_BYTES];
1080 let mut pos = 0usize;
1081
1082 match params.gamma2_den {
1083 32 => {
1084 for w in w1.iter() {
1086 for i in (0..N).step_by(2) {
1087 buf[pos] = w[i] | (w[i + 1] << 4);
1088 pos += 1;
1089 }
1090 }
1091 }
1092 88 => {
1093 for w in w1.iter() {
1095 for i in (0..N).step_by(4) {
1096 let (b0, b1, b2, b3) = (w[i], w[i + 1], w[i + 2], w[i + 3]);
1097 buf[pos] = b0 | (b1 << 6);
1098 buf[pos + 1] = (b1 >> 2) | (b2 << 4);
1099 buf[pos + 2] = (b2 >> 4) | (b3 << 2);
1100 pos += 3;
1101 }
1102 }
1103 }
1104 _ => unreachable!(),
1105 }
1106
1107 (buf, pos)
1108}
1109
1110fn compute_matrix_a<const K: usize, const L: usize>(rho: &[u8; 32]) -> [[NttPoly; L]; K] {
1111 core::array::from_fn(|r| core::array::from_fn(|s| sample_ntt(rho, s as u8, r as u8)))
1112}
1113
1114fn compute_matrix_a_into<const K: usize, const L: usize>(a: &mut [[NttPoly; L]; K], rho: &[u8; 32]) {
1115 for r in 0..K {
1116 for s in 0..L {
1117 a[r][s] = sample_ntt(rho, s as u8, r as u8);
1118 }
1119 }
1120}
1121
1122pub(crate) fn compute_pubkey_hash(pk: &[u8]) -> [u8; 64] {
1123 let mut shake = Shake256::new();
1124 shake.absorb(pk);
1125 let mut tr = [0u8; 64];
1126 shake.squeeze(&mut tr);
1127 tr
1128}
1129
1130fn compute_message_hash(tr: &[u8; 64], message: &[u8], ctx: &[u8]) -> Result<[u8; 64], MlDsaError> {
1131 if ctx.len() > CONTEXT_MAX_LEN {
1132 return Err(MlDsaError::ContextTooLong);
1133 }
1134 let mut shake = Shake256::new();
1135 shake.absorb(tr);
1136 shake.absorb(&[0u8]);
1137 shake.absorb(&[ctx.len() as u8]);
1138 shake.absorb(ctx);
1139 shake.absorb(message);
1140 let mut mu = [0u8; 64];
1141 shake.squeeze(&mut mu);
1142 Ok(mu)
1143}
1144
1145fn compute_t1_hat<const K: usize>(t1: &[[u16; N]; K]) -> [NttPoly; K] {
1146 core::array::from_fn(|i| {
1147 let mut w = Poly::default();
1148 for j in 0..N {
1149 w.coeffs[j] = field_to_montgomery((t1[i][j] as u32) << D);
1150 }
1151 ntt(&w)
1152 })
1153}
1154
1155#[cfg_attr(feature = "zeroize", derive(Zeroize, ZeroizeOnDrop))]
1167pub(crate) struct MlDsaKeyMaterial<const K: usize, const L: usize, const PK_SIZE: usize> {
1168 seed: [u8; SEED_SIZE],
1169 key_bytes: [u8; 32],
1170 pk: [u8; PK_SIZE],
1171 tr: [u8; 64],
1172 a: [[NttPoly; L]; K],
1173 s1_hat: [NttPoly; L],
1174 s2_hat: [NttPoly; K],
1175 t0_hat: [NttPoly; K],
1176}
1177
1178impl<const K: usize, const L: usize, const PK_SIZE: usize> MlDsaKeyMaterial<K, L, PK_SIZE> {
1179 pub(crate) const fn new() -> Self {
1185 unsafe { core::mem::zeroed() }
1187 }
1188
1189 pub(crate) fn from_seed(params: &MlDsaParams, seed: &[u8; SEED_SIZE]) -> Self {
1191 let mut this = Self::new();
1192 this.init(params, seed);
1193 this
1194 }
1195
1196 #[cfg(feature = "random")]
1198 pub(crate) fn from_random(params: &MlDsaParams) -> Result<Self, MlDsaError> {
1199 let mut this = Self::new();
1200 this.generate(params)?;
1201 Ok(this)
1202 }
1203
1204 pub(crate) fn init(&mut self, params: &MlDsaParams, seed: &[u8; SEED_SIZE]) {
1206 debug_assert_eq!(PK_SIZE, params.public_key_size);
1207 let mut shake = Shake256::new();
1208 shake.absorb(seed);
1209 shake.absorb(&[K as u8, L as u8]);
1210 let mut rho = [0u8; 32];
1211 let mut rhos = [0u8; 64];
1212 shake.squeeze(&mut rho);
1213 shake.squeeze(&mut rhos);
1214 shake.squeeze(&mut self.key_bytes);
1215
1216 compute_matrix_a_into::<K, L>(&mut self.a, &rho);
1217
1218 for r in 0..L {
1219 let s1 = sample_bounded_poly(&rhos, r as u8, params.eta);
1220 self.s1_hat[r] = ntt(&s1);
1221 }
1222 for r in 0..K {
1223 let s2 = sample_bounded_poly(&rhos, (L + r) as u8, params.eta);
1224 self.s2_hat[r] = ntt(&s2);
1225 }
1226
1227 let mut t1 = [[0u16; N]; K];
1228 for i in 0..K {
1229 let mut t_hat = self.s2_hat[i].clone();
1230 for j in 0..L {
1231 t_hat = ntt_add(&t_hat, &ntt_mul(&self.a[i][j], &self.s1_hat[j]));
1232 }
1233 let t = invntt(&t_hat);
1234 let mut t0 = Poly::default();
1235 for j in 0..N {
1236 (t1[i][j], t0.coeffs[j]) = power2round(t.coeffs[j]);
1237 }
1238 self.t0_hat[i] = ntt(&t0);
1239 }
1240
1241 pk_encode::<K>(&rho, &t1, &mut self.pk);
1242 self.tr = compute_pubkey_hash(&self.pk);
1243 self.seed = *seed;
1244 }
1245
1246 #[cfg(feature = "random")]
1247 pub(crate) fn generate(&mut self, params: &MlDsaParams) -> Result<[u8; SEED_SIZE], MlDsaError> {
1248 let seed: [u8; SEED_SIZE] = crate::random::bytes()?;
1249 self.init(params, &seed);
1250 Ok(seed)
1251 }
1252
1253 pub(crate) fn public_key(&self) -> &[u8; PK_SIZE] {
1254 &self.pk
1255 }
1256
1257 pub(crate) fn seed(&self) -> &[u8; SEED_SIZE] {
1258 &self.seed
1259 }
1260
1261 pub(crate) fn sign_derand_into(
1262 &self,
1263 params: &MlDsaParams,
1264 message: &[u8],
1265 ctx: &[u8],
1266 rnd: &[u8; 32],
1267 sig_out: &mut [u8],
1268 ) -> Result<(), MlDsaError> {
1269 let mut mu = compute_message_hash(&self.tr, message, ctx)?;
1270 let result = self.sign_internal(params, &mu, rnd, sig_out);
1271 wipe(&mut mu);
1273 result
1274 }
1275
1276 pub(crate) fn sign_external_mu_derand_into(
1277 &self,
1278 params: &MlDsaParams,
1279 mu: &[u8; 64],
1280 rnd: &[u8; 32],
1281 sig_out: &mut [u8],
1282 ) -> Result<(), MlDsaError> {
1283 self.sign_internal(params, mu, rnd, sig_out)
1284 }
1285
1286 fn sign_internal(
1287 &self,
1288 params: &MlDsaParams,
1289 mu: &[u8; 64],
1290 rnd: &[u8; 32],
1291 sig_out: &mut [u8],
1292 ) -> Result<(), MlDsaError> {
1293 debug_assert_eq!(sig_out.len(), params.signature_size);
1294 let a = &self.a;
1295 let s1_hat = &self.s1_hat;
1296 let s2_hat = &self.s2_hat;
1297 let t0_hat = &self.t0_hat;
1298
1299 let gamma1beta = params.gamma1 - params.beta;
1300 let gamma2 = params.gamma2;
1301 let gamma2beta = gamma2 - params.beta;
1302 let lo4 = params.lambda_over_4;
1303
1304 let mut h_shake = Shake256::new();
1305 h_shake.absorb(&self.key_bytes);
1306 h_shake.absorb(rnd);
1307 h_shake.absorb(mu);
1308 let mut nonce = [0u8; 64];
1309 h_shake.squeeze(&mut nonce);
1310
1311 let mut kappa: usize = 0;
1312 let mut iterations: usize = 0;
1313
1314 loop {
1315 if iterations >= MAX_SIGN_ITERATIONS {
1316 wipe(&mut nonce);
1318 return Err(MlDsaError::RetryLimitExceeded);
1319 }
1320 iterations += 1;
1321
1322 let mut y: [Poly; L] = core::array::from_fn(|_| Poly::default());
1323 for item in y.iter_mut() {
1324 *item = expand_mask(&nonce, kappa, params);
1325 kappa += 1;
1326 }
1327
1328 let mut y_hat: [NttPoly; L] = core::array::from_fn(|_| NttPoly::default());
1329 for i in 0..L {
1330 y_hat[i] = ntt(&y[i]);
1331 }
1332
1333 let mut w: [Poly; K] = core::array::from_fn(|_| Poly::default());
1334 for i in 0..K {
1335 let mut w_hat = NttPoly::default();
1336 for j in 0..L {
1337 w_hat = ntt_add(&w_hat, &ntt_mul(&a[i][j], &y_hat[j]));
1338 }
1339 w[i] = invntt(&w_hat);
1340 }
1341
1342 let mut w1 = [[0u8; N]; K];
1343 for i in 0..K {
1344 w1[i] = highbits_poly(&w[i], params);
1345 }
1346
1347 let mut ch_shake = Shake256::new();
1348 ch_shake.absorb(mu);
1349 let (w1_bytes, w1_len) = w1_encode::<K>(params, &w1);
1350 ch_shake.absorb(&w1_bytes[..w1_len]);
1351 let mut ct = [0u8; MAX_LAMBDA_OVER_4];
1352 ch_shake.squeeze(&mut ct[..lo4]);
1353
1354 let c = sample_in_ball(&ct[..lo4], params.tau);
1355 let c_hat = ntt(&c);
1356
1357 let mut cs1: [Poly; L] = core::array::from_fn(|_| Poly::default());
1358 for i in 0..L {
1359 cs1[i] = invntt(&ntt_mul(&c_hat, &s1_hat[i]));
1360 }
1361 let mut cs2: [Poly; K] = core::array::from_fn(|_| Poly::default());
1362 for i in 0..K {
1363 cs2[i] = invntt(&ntt_mul(&c_hat, &s2_hat[i]));
1364 }
1365
1366 let mut z: [Poly; L] = core::array::from_fn(|_| Poly::default());
1369 let mut reject = false;
1370 for i in 0..L {
1371 z[i] = poly_add(&y[i], &cs1[i]);
1372 reject |= coefficients_exceed_bound(&z[i], gamma1beta);
1373 }
1374 if reject {
1375 continue;
1376 }
1377
1378 for i in 0..K {
1379 let r0 = poly_sub(&w[i], &cs2[i]);
1380 reject |= lowbits_exceed_bound(&r0, gamma2beta, params.gamma2_den);
1381 }
1382 if reject {
1383 continue;
1384 }
1385
1386 let mut ct0: [Poly; K] = core::array::from_fn(|_| Poly::default());
1387 for i in 0..K {
1388 ct0[i] = invntt(&ntt_mul(&c_hat, &t0_hat[i]));
1389 reject |= coefficients_exceed_bound(&ct0[i], gamma2);
1390 }
1391 if reject {
1392 continue;
1393 }
1394
1395 let mut total_hints: usize = 0;
1396 let mut h = [[0u8; N]; K];
1397 for i in 0..K {
1398 let (hi, count) = make_hint_poly(&ct0[i], &w[i], &cs2[i], params);
1399 h[i] = hi;
1400 total_hints += count;
1401 }
1402 if total_hints > params.omega {
1403 continue;
1404 }
1405
1406 sig_encode::<K, L>(params, &ct[..lo4], &z, &h, sig_out);
1407 wipe(&mut nonce);
1409 return Ok(());
1410 }
1411 }
1412}
1413
1414fn verify_internal<const K: usize, const L: usize, const PK_SIZE: usize, const SIG_SIZE: usize>(
1415 params: &MlDsaParams,
1416 pk: &[u8; PK_SIZE],
1417 mu: &[u8; 64],
1418 sig: &[u8; SIG_SIZE],
1419) -> Result<(), MlDsaError> {
1420 let (rho, t1) = pk_decode::<K>(params, pk)?;
1421 let (ch, z, h) = sig_decode::<K, L>(params, sig)?;
1422
1423 let gamma1beta = params.gamma1 - params.beta;
1424
1425 for item in z.iter() {
1428 if coefficients_exceed_bound(item, gamma1beta) {
1429 return Err(MlDsaError::InvalidSignature);
1430 }
1431 }
1432
1433 let a = compute_matrix_a::<K, L>(&rho);
1434 let t1_hat = compute_t1_hat::<K>(&t1);
1435
1436 let c = sample_in_ball(&ch[..params.lambda_over_4], params.tau);
1437 let c_hat = ntt(&c);
1438
1439 let mut z_hat: [NttPoly; L] = core::array::from_fn(|_| NttPoly::default());
1440 for i in 0..L {
1441 z_hat[i] = ntt(&z[i]);
1442 }
1443
1444 let mut w_approx: [Poly; K] = core::array::from_fn(|_| Poly::default());
1445 for i in 0..K {
1446 let mut w_hat = NttPoly::default();
1447 for j in 0..L {
1448 w_hat = ntt_add(&w_hat, &ntt_mul(&a[i][j], &z_hat[j]));
1449 }
1450 w_hat = ntt_sub(&w_hat, &ntt_mul(&c_hat, &t1_hat[i]));
1451 w_approx[i] = invntt(&w_hat);
1452 }
1453
1454 let mut w1 = [[0u8; N]; K];
1455 for i in 0..K {
1456 w1[i] = use_hint_poly(&w_approx[i], &h[i], params);
1457 }
1458
1459 let mut ch_shake = Shake256::new();
1460 ch_shake.absorb(mu);
1461 let (w1_bytes, w1_len) = w1_encode::<K>(params, &w1);
1462 ch_shake.absorb(&w1_bytes[..w1_len]);
1463 let mut computed_ch = [0u8; MAX_LAMBDA_OVER_4];
1464 ch_shake.squeeze(&mut computed_ch[..params.lambda_over_4]);
1465
1466 if !constant_time_eq(&ch[..params.lambda_over_4], &computed_ch[..params.lambda_over_4]) {
1467 return Err(MlDsaError::InvalidSignature);
1468 }
1469
1470 Ok(())
1471}
1472
1473pub(crate) fn verify_message<const K: usize, const L: usize, const PK_SIZE: usize, const SIG_SIZE: usize>(
1475 params: &MlDsaParams,
1476 pk: &[u8; PK_SIZE],
1477 message: &[u8],
1478 sig: &[u8; SIG_SIZE],
1479 ctx: &[u8],
1480) -> Result<(), MlDsaError> {
1481 let tr = compute_pubkey_hash(pk);
1482 let mu = compute_message_hash(&tr, message, ctx)?;
1483 verify_internal::<K, L, PK_SIZE, SIG_SIZE>(params, pk, &mu, sig)
1484}
1485
1486pub(crate) fn verify_external_mu<const K: usize, const L: usize, const PK_SIZE: usize, const SIG_SIZE: usize>(
1489 params: &MlDsaParams,
1490 pk: &[u8; PK_SIZE],
1491 mu: &[u8; 64],
1492 sig: &[u8; SIG_SIZE],
1493) -> Result<(), MlDsaError> {
1494 verify_internal::<K, L, PK_SIZE, SIG_SIZE>(params, pk, mu, sig)
1495}
1496
1497#[cfg(all(target_arch = "aarch64", target_feature = "neon"))]
1505mod neon {
1506 use core::arch::aarch64::*;
1507
1508 use super::*;
1509
1510 #[inline]
1512 #[target_feature(enable = "neon")]
1513 fn reduce_once4(x: uint32x4_t) -> uint32x4_t {
1514 let q = vdupq_n_u32(Q);
1515 let sub = vsubq_u32(x, q);
1516 let mask = vshrq_n_s32(vreinterpretq_s32_u32(sub), 31);
1517 let masked = vandq_u32(vreinterpretq_u32_s32(mask), q);
1518 vaddq_u32(sub, masked)
1519 }
1520
1521 #[inline]
1522 #[target_feature(enable = "neon")]
1523 fn add4(a: uint32x4_t, b: uint32x4_t) -> uint32x4_t {
1524 reduce_once4(vaddq_u32(a, b))
1525 }
1526
1527 #[inline]
1528 #[target_feature(enable = "neon")]
1529 fn sub4(a: uint32x4_t, b: uint32x4_t) -> uint32x4_t {
1530 reduce_once4(vaddq_u32(vsubq_u32(a, b), vdupq_n_u32(Q)))
1531 }
1532
1533 #[inline]
1535 #[target_feature(enable = "neon")]
1536 fn reduce_once2(x: uint32x2_t) -> uint32x2_t {
1537 let q = vdup_n_u32(Q);
1538 let sub = vsub_u32(x, q);
1539 let mask = vshr_n_s32(vreinterpret_s32_u32(sub), 31);
1540 let masked = vand_u32(vreinterpret_u32_s32(mask), q);
1541 vadd_u32(sub, masked)
1542 }
1543
1544 #[inline]
1546 #[target_feature(enable = "neon")]
1547 fn montgomery_reduce2(x: uint64x2_t) -> uint32x2_t {
1548 let q = vdup_n_u32(Q);
1549 let t = vmul_u32(vmovn_u64(x), vdup_n_u32(QINV));
1550 let tq = vmull_u32(t, q);
1551 let u = vshrq_n_u64(vaddq_u64(x, tq), 32);
1552 reduce_once2(vmovn_u64(u))
1553 }
1554
1555 #[inline]
1557 #[target_feature(enable = "neon")]
1558 fn montgomery_mul4(a: uint32x4_t, b: uint32x4_t) -> uint32x4_t {
1559 let lo = montgomery_reduce2(vmull_u32(vget_low_u32(a), vget_low_u32(b)));
1560 let hi = montgomery_reduce2(vmull_high_u32(a, b));
1561 vcombine_u32(lo, hi)
1562 }
1563
1564 #[inline]
1565 #[target_feature(enable = "neon")]
1566 fn montgomery_mul4_scalar(zeta: u32, y: uint32x4_t) -> uint32x4_t {
1567 montgomery_mul4(vdupq_n_u32(zeta), y)
1568 }
1569
1570 pub(super) fn ntt(f: &Poly) -> NttPoly {
1571 let mut f = NttPoly {
1572 coeffs: f.coeffs,
1573 };
1574 let mut m: usize = 0;
1578 unsafe {
1579 let ptr = f.coeffs.as_mut_ptr();
1580
1581 let mut len: usize = 128;
1582 while len >= 8 {
1583 let mut start: usize = 0;
1584 while start < N {
1585 m += 1;
1586 let zeta = ZETAS[m];
1587 let mut j = start;
1588 while j < start + len {
1589 let x = vld1q_u32(ptr.add(j));
1590 let y = vld1q_u32(ptr.add(j + len));
1591 let t = montgomery_mul4_scalar(zeta, y);
1592 vst1q_u32(ptr.add(j), add4(x, t));
1593 vst1q_u32(ptr.add(j + len), sub4(x, t));
1594 j += 4;
1595 }
1596 start += 2 * len;
1597 }
1598 len /= 2;
1599 }
1600 }
1601
1602 let c = &mut f.coeffs;
1604 let mut start: usize = 0;
1605 while start < N {
1606 m += 1;
1607 let zeta = ZETAS[m];
1608 for k in 0..4 {
1609 let t = field_montgomery_mul(zeta, c[start + 4 + k]);
1610 c[start + 4 + k] = field_sub(c[start + k], t);
1611 c[start + k] = field_add(c[start + k], t);
1612 }
1613 start += 8;
1614 }
1615 start = 0;
1616 while start < N {
1617 m += 1;
1618 let zeta = ZETAS[m];
1619 for k in 0..2 {
1620 let t = field_montgomery_mul(zeta, c[start + 2 + k]);
1621 c[start + 2 + k] = field_sub(c[start + k], t);
1622 c[start + k] = field_add(c[start + k], t);
1623 }
1624 start += 4;
1625 }
1626 start = 0;
1627 while start < N {
1628 m += 1;
1629 let zeta = ZETAS[m];
1630 let t = field_montgomery_mul(zeta, c[start + 1]);
1631 c[start + 1] = field_sub(c[start], t);
1632 c[start] = field_add(c[start], t);
1633 start += 2;
1634 }
1635
1636 f
1637 }
1638
1639 pub(super) fn invntt(f: &NttPoly) -> Poly {
1640 let mut f = NttPoly {
1641 coeffs: f.coeffs,
1642 };
1643 let c = &mut f.coeffs;
1644 let mut m: usize = 255;
1645
1646 let mut start: usize = 0;
1648 while start < N {
1649 let zeta = ZETAS[m];
1650 m -= 1;
1651 let t = c[start];
1652 c[start] = field_add(t, c[start + 1]);
1653 c[start + 1] = field_montgomery_mul(zeta, field_sub(c[start + 1], t));
1654 start += 2;
1655 }
1656 start = 0;
1657 while start < N {
1658 let zeta = ZETAS[m];
1659 m -= 1;
1660 for k in 0..2 {
1661 let t = c[start + k];
1662 c[start + k] = field_add(t, c[start + 2 + k]);
1663 c[start + 2 + k] = field_montgomery_mul(zeta, field_sub(c[start + 2 + k], t));
1664 }
1665 start += 4;
1666 }
1667 start = 0;
1668 while start < N {
1669 let zeta = ZETAS[m];
1670 m -= 1;
1671 for k in 0..4 {
1672 let t = c[start + k];
1673 c[start + k] = field_add(t, c[start + 4 + k]);
1674 c[start + 4 + k] = field_montgomery_mul(zeta, field_sub(c[start + 4 + k], t));
1675 }
1676 start += 8;
1677 }
1678
1679 unsafe {
1681 let ptr = f.coeffs.as_mut_ptr();
1682 let mut len: usize = 8;
1683 while len < N {
1684 let mut start: usize = 0;
1685 while start < N {
1686 let zeta = ZETAS[m];
1687 m -= 1;
1688 let mut j = start;
1689 while j < start + len {
1690 let x = vld1q_u32(ptr.add(j));
1691 let y = vld1q_u32(ptr.add(j + len));
1692 vst1q_u32(ptr.add(j), add4(x, y));
1693 vst1q_u32(ptr.add(j + len), montgomery_mul4_scalar(zeta, sub4(y, x)));
1694 j += 4;
1695 }
1696 start += 2 * len;
1697 }
1698 len *= 2;
1699 }
1700
1701 let ninv = vdupq_n_u32(N_INV);
1703 let mut i = 0;
1704 while i < N {
1705 vst1q_u32(ptr.add(i), montgomery_mul4(vld1q_u32(ptr.add(i)), ninv));
1706 i += 4;
1707 }
1708 }
1709
1710 Poly {
1711 coeffs: f.coeffs,
1712 }
1713 }
1714
1715 pub(super) fn ntt_mul(a: &NttPoly, b: &NttPoly) -> NttPoly {
1716 let mut r = NttPoly::default();
1717 let (ap, bp, rp) = (a.coeffs.as_ptr(), b.coeffs.as_ptr(), r.coeffs.as_mut_ptr());
1718 unsafe {
1720 let mut i = 0;
1721 while i < N {
1722 vst1q_u32(rp.add(i), montgomery_mul4(vld1q_u32(ap.add(i)), vld1q_u32(bp.add(i))));
1723 i += 4;
1724 }
1725 }
1726 r
1727 }
1728}
1729
1730#[cfg(all(target_arch = "x86_64", feature = "std"))]
1737mod avx2 {
1738 use core::arch::x86_64::*;
1739
1740 use super::*;
1741
1742 #[inline]
1744 #[target_feature(enable = "avx2")]
1745 fn reduce_once8(x: __m256i) -> __m256i {
1746 let q = _mm256_set1_epi32(Q as i32);
1747 let sub = _mm256_sub_epi32(x, q);
1748 let mask = _mm256_cmpgt_epi32(q, x); _mm256_blendv_epi8(sub, x, mask)
1750 }
1751
1752 #[inline]
1753 #[target_feature(enable = "avx2")]
1754 fn add8(a: __m256i, b: __m256i) -> __m256i {
1755 reduce_once8(_mm256_add_epi32(a, b))
1756 }
1757
1758 #[inline]
1759 #[target_feature(enable = "avx2")]
1760 fn sub8(a: __m256i, b: __m256i) -> __m256i {
1761 reduce_once8(_mm256_add_epi32(_mm256_sub_epi32(a, b), _mm256_set1_epi32(Q as i32)))
1762 }
1763
1764 #[inline]
1767 #[target_feature(enable = "avx2")]
1768 fn montgomery_reduce_4x64(x: __m256i) -> __m256i {
1769 let t = _mm256_mul_epu32(x, _mm256_set1_epi32(QINV as i32));
1770 let tq = _mm256_mul_epu32(t, _mm256_set1_epi32(Q as i32));
1771 let u = _mm256_srli_epi64(_mm256_add_epi64(x, tq), 32);
1772 reduce_once8(u)
1773 }
1774
1775 #[inline]
1777 #[target_feature(enable = "avx2")]
1778 fn montgomery_mul8(a: __m256i, b: __m256i) -> __m256i {
1779 let even = montgomery_reduce_4x64(_mm256_mul_epu32(a, b));
1780 let odd = montgomery_reduce_4x64(_mm256_mul_epu32(_mm256_srli_epi64(a, 32), _mm256_srli_epi64(b, 32)));
1781 _mm256_or_si256(even, _mm256_slli_epi64(odd, 32))
1782 }
1783
1784 #[inline]
1785 #[target_feature(enable = "avx2")]
1786 fn montgomery_mul8_scalar(zeta: u32, y: __m256i) -> __m256i {
1787 montgomery_mul8(_mm256_set1_epi32(zeta as i32), y)
1788 }
1789
1790 #[target_feature(enable = "avx2")]
1791 pub(super) unsafe fn ntt(f: &Poly) -> NttPoly {
1792 let mut f = NttPoly {
1793 coeffs: f.coeffs,
1794 };
1795 unsafe {
1799 let ptr = f.coeffs.as_mut_ptr();
1800 let mut m: usize = 0;
1801
1802 let mut len: usize = 128;
1803 while len >= 8 {
1804 let mut start: usize = 0;
1805 while start < N {
1806 m += 1;
1807 let zeta = ZETAS[m];
1808 let mut j = start;
1809 while j < start + len {
1810 let x = _mm256_loadu_si256(ptr.add(j).cast());
1811 let y = _mm256_loadu_si256(ptr.add(j + len).cast());
1812 let t = montgomery_mul8_scalar(zeta, y);
1813 _mm256_storeu_si256(ptr.add(j).cast(), add8(x, t));
1814 _mm256_storeu_si256(ptr.add(j + len).cast(), sub8(x, t));
1815 j += 8;
1816 }
1817 start += 2 * len;
1818 }
1819 len /= 2;
1820 }
1821
1822 let c = &mut f.coeffs;
1824 let mut start: usize = 0;
1825 while start < N {
1826 m += 1;
1827 let zeta = ZETAS[m];
1828 for k in 0..4 {
1829 let t = field_montgomery_mul(zeta, c[start + 4 + k]);
1830 c[start + 4 + k] = field_sub(c[start + k], t);
1831 c[start + k] = field_add(c[start + k], t);
1832 }
1833 start += 8;
1834 }
1835 start = 0;
1836 while start < N {
1837 m += 1;
1838 let zeta = ZETAS[m];
1839 for k in 0..2 {
1840 let t = field_montgomery_mul(zeta, c[start + 2 + k]);
1841 c[start + 2 + k] = field_sub(c[start + k], t);
1842 c[start + k] = field_add(c[start + k], t);
1843 }
1844 start += 4;
1845 }
1846 start = 0;
1847 while start < N {
1848 m += 1;
1849 let zeta = ZETAS[m];
1850 let t = field_montgomery_mul(zeta, c[start + 1]);
1851 c[start + 1] = field_sub(c[start], t);
1852 c[start] = field_add(c[start], t);
1853 start += 2;
1854 }
1855 }
1856
1857 f
1858 }
1859
1860 #[target_feature(enable = "avx2")]
1861 pub(super) unsafe fn invntt(f: &NttPoly) -> Poly {
1862 let mut f = NttPoly {
1863 coeffs: f.coeffs,
1864 };
1865 let c = &mut f.coeffs;
1866 let mut m: usize = 255;
1867
1868 let mut start: usize = 0;
1870 while start < N {
1871 let zeta = ZETAS[m];
1872 m -= 1;
1873 let t = c[start];
1874 c[start] = field_add(t, c[start + 1]);
1875 c[start + 1] = field_montgomery_mul(zeta, field_sub(c[start + 1], t));
1876 start += 2;
1877 }
1878 start = 0;
1879 while start < N {
1880 let zeta = ZETAS[m];
1881 m -= 1;
1882 for k in 0..2 {
1883 let t = c[start + k];
1884 c[start + k] = field_add(t, c[start + 2 + k]);
1885 c[start + 2 + k] = field_montgomery_mul(zeta, field_sub(c[start + 2 + k], t));
1886 }
1887 start += 4;
1888 }
1889 start = 0;
1890 while start < N {
1891 let zeta = ZETAS[m];
1892 m -= 1;
1893 for k in 0..4 {
1894 let t = c[start + k];
1895 c[start + k] = field_add(t, c[start + 4 + k]);
1896 c[start + 4 + k] = field_montgomery_mul(zeta, field_sub(c[start + 4 + k], t));
1897 }
1898 start += 8;
1899 }
1900
1901 unsafe {
1903 let ptr = f.coeffs.as_mut_ptr();
1904 let mut len: usize = 8;
1905 while len < N {
1906 let mut start: usize = 0;
1907 while start < N {
1908 let zeta = ZETAS[m];
1909 m -= 1;
1910 let mut j = start;
1911 while j < start + len {
1912 let x = _mm256_loadu_si256(ptr.add(j).cast());
1913 let y = _mm256_loadu_si256(ptr.add(j + len).cast());
1914 _mm256_storeu_si256(ptr.add(j).cast(), add8(x, y));
1915 _mm256_storeu_si256(ptr.add(j + len).cast(), montgomery_mul8_scalar(zeta, sub8(y, x)));
1916 j += 8;
1917 }
1918 start += 2 * len;
1919 }
1920 len *= 2;
1921 }
1922
1923 let ninv = _mm256_set1_epi32(N_INV as i32);
1924 let mut i = 0;
1925 while i < N {
1926 _mm256_storeu_si256(ptr.add(i).cast(), montgomery_mul8(_mm256_loadu_si256(ptr.add(i).cast()), ninv));
1927 i += 8;
1928 }
1929 }
1930
1931 Poly {
1932 coeffs: f.coeffs,
1933 }
1934 }
1935
1936 #[target_feature(enable = "avx2")]
1937 pub(super) unsafe fn ntt_mul(a: &NttPoly, b: &NttPoly) -> NttPoly {
1938 let mut r = NttPoly::default();
1939 unsafe {
1941 let (ap, bp, rp) = (a.coeffs.as_ptr(), b.coeffs.as_ptr(), r.coeffs.as_mut_ptr());
1942 let mut i = 0;
1943 while i < N {
1944 _mm256_storeu_si256(
1945 rp.add(i).cast(),
1946 montgomery_mul8(_mm256_loadu_si256(ap.add(i).cast()), _mm256_loadu_si256(bp.add(i).cast())),
1947 );
1948 i += 8;
1949 }
1950 }
1951 r
1952 }
1953}
1954
1955#[cfg(test)]
1956mod tests {
1957 use super::*;
1958
1959 #[test]
1960 fn ntt_round_trip() {
1961 let mut shake = Shake128::new();
1962 for _ in 0..100 {
1963 let mut poly = Poly::default();
1964 for j in 0..N {
1965 let mut b = [0u8; 4];
1966 shake.squeeze(&mut b);
1967 let x = u32::from_le_bytes(b) % Q;
1968 poly.coeffs[j] = field_to_montgomery(x);
1969 }
1970 let fwd = ntt(&poly);
1971 let back = invntt(&fwd);
1972 for j in 0..N {
1973 assert_eq!(poly.coeffs[j], back.coeffs[j], "NTT round-trip failed at coeff {}", j);
1974 }
1975 }
1976 }
1977
1978 #[test]
1979 #[cfg(not(debug_assertions))]
1980 fn power2round_consistency() {
1981 for x in 0u32..Q {
1982 let mr = field_to_montgomery(x);
1983 let (r1, r0) = power2round(mr);
1984 let recovered = (r1 as u32) << D;
1985
1986 let expected_r0 = if x >= recovered {
1987 x - recovered
1988 } else {
1989 x.wrapping_sub(recovered)
1990 };
1991
1992 assert!(
1993 expected_r0 < (1 << D) || expected_r0 >= Q - (1 << D) + 1,
1994 "power2round: r0 out of range at x={}, r1={}, r0_expected={}",
1995 x,
1996 r1,
1997 expected_r0
1998 );
1999
2000 let got_r0 = field_from_montgomery(r0);
2001 assert!(
2002 got_r0 == expected_r0 || got_r0 == expected_r0.wrapping_add(Q) || got_r0 == expected_r0.wrapping_sub(Q),
2003 "power2round: r0 mismatch at x={}, r1={}, expected_r0={}, got_r0={}",
2004 x,
2005 r1,
2006 expected_r0,
2007 got_r0
2008 );
2009 }
2010 }
2011
2012 #[test]
2013 #[cfg(not(debug_assertions))]
2014 fn highbits32_exhaustive() {
2015 for x in 0u32..Q {
2016 let h = highbits32(x);
2017 assert!(h < 16, "highbits32: h={} out of range at x={}", h, x);
2018 let (r1, _) = decompose32(field_to_montgomery(x));
2019 assert_eq!(h, r1, "highbits32 vs decompose32 r1 mismatch at x={}", x);
2020 }
2021 }
2022
2023 #[test]
2024 #[cfg(not(debug_assertions))]
2025 fn highbits88_exhaustive() {
2026 for x in 0u32..Q {
2027 let h = highbits88(x);
2028 assert!(h < 44, "highbits88: h={} out of range at x={}", h, x);
2029 let (r1, _) = decompose88(field_to_montgomery(x));
2030 assert_eq!(h, r1, "highbits88 vs decompose88 r1 mismatch at x={}", x);
2031 }
2032 }
2033
2034 #[test]
2035 fn make_hint32_correctness() {
2036 let mut shake = Shake128::new();
2037 for _ in 0..5000 {
2038 let mut b = [0u8; 12];
2039 shake.squeeze(&mut b);
2040 let ct0_val = u32::from_le_bytes(b[0..4].try_into().unwrap()) % Q;
2041 let w_val = u32::from_le_bytes(b[4..8].try_into().unwrap()) % Q;
2042 let cs2_val = u32::from_le_bytes(b[8..12].try_into().unwrap()) % Q;
2043 let ct0 = field_to_montgomery(ct0_val);
2044 let w = field_to_montgomery(w_val);
2045 let cs2 = field_to_montgomery(cs2_val);
2046 let h = make_hint32(ct0, w, cs2);
2047 assert!(h == 0 || h == 1, "make_hint32: hint not 0 or 1");
2048 }
2049 }
2050
2051 #[test]
2052 fn make_hint88_correctness() {
2053 let mut shake = Shake128::new();
2054 for _ in 0..5000 {
2055 let mut b = [0u8; 12];
2056 shake.squeeze(&mut b);
2057 let ct0_val = u32::from_le_bytes(b[0..4].try_into().unwrap()) % Q;
2058 let w_val = u32::from_le_bytes(b[4..8].try_into().unwrap()) % Q;
2059 let cs2_val = u32::from_le_bytes(b[8..12].try_into().unwrap()) % Q;
2060 let ct0 = field_to_montgomery(ct0_val);
2061 let w = field_to_montgomery(w_val);
2062 let cs2 = field_to_montgomery(cs2_val);
2063 let h = make_hint88(ct0, w, cs2);
2064 assert!(h == 0 || h == 1, "make_hint88: hint not 0 or 1");
2065 }
2066 }
2067
2068 #[test]
2069 fn pk_encode_decode_round_trip_65() {
2070 let mut t1 = [[0u16; N]; 6];
2071 let mut shake = Shake128::new();
2072 for row in t1.iter_mut() {
2073 for c in row.iter_mut() {
2074 let mut b = [0u8; 2];
2075 shake.squeeze(&mut b);
2076 *c = u16::from_le_bytes(b) & 0x3FF;
2077 }
2078 }
2079 let rho = [7u8; 32];
2080 let mut pk = [0u8; PARAMS_65.public_key_size];
2081 pk_encode::<6>(&rho, &t1, &mut pk);
2082 let (rho2, t1_2) = pk_decode::<6>(&PARAMS_65, &pk).unwrap();
2083 assert_eq!(rho, rho2);
2084 assert_eq!(t1, t1_2);
2085 }
2086
2087 #[test]
2088 fn bitpack_unpack_round_trip_18() {
2089 let mut shake = Shake128::new();
2090 let mut z = Poly::default();
2091 let b = 1i32 << 17;
2092 for c in z.coeffs.iter_mut() {
2093 let mut buf = [0u8; 4];
2094 shake.squeeze(&mut buf);
2095 let v = (u32::from_le_bytes(buf) % (2 * b as u32)) as i32 - (b - 1);
2096 *c = field_to_montgomery(v.rem_euclid(Q as i32) as u32);
2097 }
2098 let mut out = [0u8; 18 * N / 8];
2099 bitpack_18(&z, &mut out);
2100 let back = bitunpack_18(&out);
2101 assert_eq!(z, back);
2102 }
2103
2104 #[test]
2105 fn bitpack_unpack_round_trip_20() {
2106 let mut shake = Shake128::new();
2107 let mut z = Poly::default();
2108 let b = 1i32 << 19;
2109 for c in z.coeffs.iter_mut() {
2110 let mut buf = [0u8; 4];
2111 shake.squeeze(&mut buf);
2112 let v = (u32::from_le_bytes(buf) % (2 * b as u32)) as i32 - (b - 1);
2113 *c = field_to_montgomery(v.rem_euclid(Q as i32) as u32);
2114 }
2115 let mut out = [0u8; 20 * N / 8];
2116 bitpack_20(&z, &mut out);
2117 let back = bitunpack_20(&out);
2118 assert_eq!(z, back);
2119 }
2120
2121 fn spec_decompose(x: i64, gamma2_den: i64) -> (i64, i64) {
2124 let q = Q as i64;
2125 let gamma2 = (q - 1) / gamma2_den;
2126 let two_gamma2 = 2 * gamma2;
2127 let mut r0 = x % two_gamma2;
2128 if r0 > gamma2 {
2129 r0 -= two_gamma2;
2130 }
2131 if r0 <= -gamma2 {
2132 r0 += two_gamma2;
2133 }
2134 if x - r0 == q - 1 {
2135 (0, r0 - 1)
2136 } else {
2137 ((x - r0) / two_gamma2, r0)
2138 }
2139 }
2140
2141 fn spec_highbits(x: i64, gamma2_den: i64) -> i64 {
2142 spec_decompose(x, gamma2_den).0
2143 }
2144
2145 fn spec_use_hint(h: i64, x: i64, gamma2_den: i64) -> i64 {
2146 let (r1, r0) = spec_decompose(x, gamma2_den);
2147 let m = gamma2_den / 2;
2148 if h == 1 {
2149 if r0 > 0 { (r1 + 1) % m } else { (r1 - 1 + m) % m }
2150 } else {
2151 r1
2152 }
2153 }
2154
2155 fn check_field_ops(x: i64) {
2156 for den in [32i64, 88] {
2157 let (sr1, sr0) = spec_decompose(x, den);
2158 let (gr1, gr0) = if den == 32 {
2159 let (a, b) = decompose32(field_to_montgomery(x as u32));
2160 (a as i64, b as i64)
2161 } else {
2162 let (a, b) = decompose88(field_to_montgomery(x as u32));
2163 (a as i64, b as i64)
2164 };
2165 assert_eq!((gr1, gr0), (sr1, sr0), "decompose mismatch for x={x}, gamma2_den={den}");
2166
2167 let gh = if den == 32 {
2168 highbits32(x as u32) as i64
2169 } else {
2170 highbits88(x as u32) as i64
2171 };
2172 assert_eq!(gh, spec_highbits(x, den), "highbits mismatch for x={x}, den={den}");
2173
2174 for h in 0..2u8 {
2175 let gu = if den == 32 {
2176 use_hint32(field_to_montgomery(x as u32), h) as i64
2177 } else {
2178 use_hint88(field_to_montgomery(x as u32), h) as i64
2179 };
2180 assert_eq!(
2181 gu,
2182 spec_use_hint(h as i64, x, den),
2183 "use_hint mismatch for x={x}, h={h}, den={den}"
2184 );
2185 }
2186 }
2187 }
2188
2189 #[test]
2193 #[ignore = "exhaustive over all q field elements; run with --ignored"]
2194 fn decompose_highbits_use_hint_match_specification_exhaustive() {
2195 for x in 0..Q as i64 {
2196 check_field_ops(x);
2197 }
2198 }
2199
2200 #[test]
2202 fn decompose_highbits_use_hint_match_specification_sampled() {
2203 let mut shake = Shake128::new();
2204 shake.absorb(b"field-ops");
2205 for _ in 0..50_000 {
2206 let mut buf = [0u8; 4];
2207 shake.squeeze(&mut buf);
2208 let x = (u32::from_le_bytes(buf) % Q) as i64;
2209 check_field_ops(x);
2210 }
2211 for x in [
2213 0,
2214 1,
2215 Q as i64 - 1,
2216 Q as i64 / 2,
2217 (Q as i64 - 1) / 32,
2218 (Q as i64 - 1) / 88,
2219 ] {
2220 check_field_ops(x);
2221 }
2222 }
2223
2224 #[test]
2228 fn hint_decode_enforces_encoding_rules() {
2229 let params = &PARAMS_65; let omega = params.omega;
2231 const K: usize = 6;
2232
2233 let empty = [0u8; 61];
2234 assert!(hint_decode::<K>(params, &empty).is_ok());
2235
2236 let mut ok = [0u8; 61];
2240 ok[0] = 3;
2241 ok[1] = 10;
2242 ok[2] = 200;
2243 for i in 0..K {
2244 ok[omega + i] = 3;
2245 }
2246 assert!(hint_decode::<K>(params, &ok).is_ok());
2247
2248 let mut dup = [0u8; 61];
2250 dup[0] = 7;
2251 dup[1] = 7;
2252 for i in 0..K {
2253 dup[omega + i] = 2;
2254 }
2255 assert_eq!(hint_decode::<K>(params, &dup), Err(MlDsaError::InvalidSignature));
2256
2257 let mut dec = [0u8; 61];
2259 dec[0] = 9;
2260 dec[1] = 4;
2261 for i in 0..K {
2262 dec[omega + i] = 2;
2263 }
2264 assert_eq!(hint_decode::<K>(params, &dec), Err(MlDsaError::InvalidSignature));
2265
2266 let mut over = [0u8; 61];
2268 over[omega] = (omega + 1) as u8;
2269 assert_eq!(hint_decode::<K>(params, &over), Err(MlDsaError::InvalidSignature));
2270
2271 let mut nonmono = [0u8; 61];
2273 nonmono[0] = 5;
2274 nonmono[omega] = 1;
2275 nonmono[omega + 1] = 0;
2276 assert_eq!(hint_decode::<K>(params, &nonmono), Err(MlDsaError::InvalidSignature));
2277
2278 let mut pad = [0u8; 61];
2280 pad[0] = 5;
2281 pad[1] = 0xff;
2282 for i in 0..K {
2283 pad[omega + i] = 1;
2284 }
2285 assert_eq!(hint_decode::<K>(params, &pad), Err(MlDsaError::InvalidSignature));
2286 }
2287
2288 fn random_poly(shake: &mut Shake128) -> Poly {
2289 let mut p = Poly::default();
2290 let mut buf = [0u8; 4];
2291 for c in p.coeffs.iter_mut() {
2292 shake.squeeze(&mut buf);
2293 *c = u32::from_le_bytes(buf) % Q;
2294 }
2295 p
2296 }
2297
2298 #[test]
2301 fn ntt_matches_scalar() {
2302 let mut shake = Shake128::new();
2303 shake.absorb(b"mldsa-ntt-equivalence");
2304 for _ in 0..500 {
2305 let p = random_poly(&mut shake);
2306 let simd = ntt(&p);
2307 let scalar = ntt_scalar(&p);
2308 assert_eq!(simd.coeffs, scalar.coeffs, "NEON NTT differs from scalar");
2309 }
2310 }
2311
2312 #[test]
2313 fn invntt_matches_scalar() {
2314 let mut shake = Shake128::new();
2315 shake.absorb(b"mldsa-invntt-equivalence");
2316 for _ in 0..500 {
2317 let p = random_poly(&mut shake);
2318 let n = NttPoly {
2319 coeffs: p.coeffs,
2320 };
2321 let simd = invntt(&n);
2322 let scalar = invntt_scalar(&n);
2323 assert_eq!(simd.coeffs, scalar.coeffs, "NEON inverse NTT differs from scalar");
2324 }
2325 }
2326
2327 #[test]
2328 fn ntt_mul_matches_scalar() {
2329 let mut shake = Shake128::new();
2330 shake.absorb(b"mldsa-nttmul-equivalence");
2331 for _ in 0..500 {
2332 let a = NttPoly {
2333 coeffs: random_poly(&mut shake).coeffs,
2334 };
2335 let b = NttPoly {
2336 coeffs: random_poly(&mut shake).coeffs,
2337 };
2338 let simd = ntt_mul(&a, &b);
2339 let scalar = ntt_mul_scalar(&a, &b);
2340 assert_eq!(simd.coeffs, scalar.coeffs, "NEON ntt_mul differs from scalar");
2341 }
2342 }
2343}