1use std::sync::OnceLock;
8
9pub(crate) const Q: i32 = 8_380_417;
10const QINV: i32 = 58_728_449; const ZETA: i64 = 1753; const MONT: i64 = 4_193_792;
14const N: usize = 256;
15
16#[inline]
18pub(crate) fn montgomery_reduce(a: i64) -> i32 {
19 let t = (a as i32).wrapping_mul(QINV) as i64;
20 ((a - t * Q as i64) >> 32) as i32
21}
22
23fn zetas() -> &'static [i32; 256] {
25 static ZETAS: OnceLock<[i32; 256]> = OnceLock::new();
26 ZETAS.get_or_init(|| {
27 let mut pow = [1i64; 256];
28 for i in 1..256 {
29 pow[i] = pow[i - 1] * ZETA % Q as i64;
30 }
31 let mut z = [0i32; 256];
32 for (i, slot) in z.iter_mut().enumerate() {
33 let br = (i as u8).reverse_bits() as usize;
34 let mut v = pow[br] * MONT % Q as i64;
35 if v > Q as i64 / 2 {
36 v -= Q as i64;
37 }
38 *slot = v as i32;
39 }
40 z
41 })
42}
43
44pub(crate) fn ntt(a: &mut [i32; N]) {
49 let zetas = zetas();
50 let mut k = 0usize;
51 let mut len = 128usize;
52 while len > 0 {
53 let mut start = 0usize;
54 while start < N {
55 k += 1;
56 let zeta = zetas[k] as i64;
57 for j in start..start + len {
58 let t = montgomery_reduce(zeta * a[j + len] as i64);
59 a[j + len] = a[j] - t;
60 a[j] = a[j] + t;
61 }
62 start += 2 * len;
63 }
64 len >>= 1;
65 }
66}
67
68const INVNTT_F: i64 = 41_978; pub(crate) fn invntt_tomont(a: &mut [i32; N]) {
74 let zetas = zetas();
75 let mut k = 256usize;
76 let mut len = 1usize;
77 while len < N {
78 let mut start = 0usize;
79 while start < N {
80 k -= 1;
81 let zeta = -(zetas[k] as i64);
82 for j in start..start + len {
83 let t = a[j];
84 a[j] = t + a[j + len];
85 a[j + len] = t - a[j + len];
86 a[j + len] = montgomery_reduce(zeta * a[j + len] as i64);
87 }
88 start += 2 * len;
89 }
90 len <<= 1;
91 }
92 for x in a.iter_mut() {
93 *x = montgomery_reduce(INVNTT_F * *x as i64);
94 }
95}
96
97pub(crate) fn pointwise_montgomery(a: &[i32; N], b: &[i32; N]) -> [i32; N] {
99 std::array::from_fn(|i| montgomery_reduce(a[i] as i64 * b[i] as i64))
103}
104
105#[inline]
107pub(crate) fn freeze(a: i32) -> i32 {
108 ((a % Q) + Q) % Q
109}
110
111pub(crate) const D: i32 = 13; pub(crate) const GAMMA1: i32 = 1 << 19; pub(crate) const GAMMA2: i32 = (Q - 1) / 32; pub(crate) fn power2round(r: i32) -> (i32, i32) {
120 let r = freeze(r);
121 let r1 = (r + (1 << (D - 1)) - 1) >> D;
122 let r0 = r - (r1 << D);
123 (r1, r0)
124}
125
126pub(crate) fn decompose(r: i32) -> (i32, i32) {
129 let r = freeze(r);
130 let mut a1 = (r + 127) >> 7;
131 a1 = (a1 * 1025 + (1 << 21)) >> 22;
132 a1 &= 15;
133 let mut a0 = r - a1 * 2 * GAMMA2;
134 a0 -= (((Q - 1) / 2 - a0) >> 31) & Q;
135 (a1, a0)
136}
137
138pub(crate) fn highbits(r: i32) -> i32 {
140 decompose(r).0
141}
142pub(crate) fn lowbits(r: i32) -> i32 {
143 decompose(r).1
144}
145
146pub(crate) fn make_hint(z: i32, r: i32) -> u8 {
148 (highbits(r) != highbits(r + z)) as u8
149}
150
151pub(crate) fn use_hint(h: u8, r: i32) -> i32 {
153 const M: i32 = 16; let (r1, r0) = decompose(r);
155 if h == 0 {
156 r1
157 } else if r0 > 0 {
158 (r1 + 1) % M
159 } else {
160 (r1 - 1 + M) % M
161 }
162}
163
164pub(crate) const MK: usize = 6; pub(crate) const ML: usize = 5; pub(crate) const ETA: i32 = 4;
168pub(crate) const TAU: usize = 49;
169
170type Poly = [i32; N];
171
172fn rej_ntt_poly(seed: &[u8]) -> Poly {
177 let mut st = crate::keccak::shake_absorb(seed, 168);
178 let mut buf = [0u8; 168];
179 crate::keccak::shake_squeeze_block(&st, &mut buf, 168);
180 let mut pos = 0;
181 let mut a = [0i32; N];
182 let mut ctr = 0;
183 while ctr < N {
184 if pos + 3 > 168 {
185 crate::keccak::keccak_permute(&mut st);
186 crate::keccak::shake_squeeze_block(&st, &mut buf, 168);
187 pos = 0;
188 }
189 let d = (buf[pos] as u32) | ((buf[pos + 1] as u32) << 8) | (((buf[pos + 2] as u32) & 0x7f) << 16);
190 pos += 3;
191 if d < Q as u32 {
192 a[ctr] = d as i32;
193 ctr += 1;
194 }
195 }
196 a
197}
198
199fn rej_bounded_poly(seed: &[u8]) -> Poly {
202 let mut st = crate::keccak::shake_absorb(seed, 136);
203 let mut buf = [0u8; 136];
204 crate::keccak::shake_squeeze_block(&st, &mut buf, 136);
205 let mut pos = 0;
206 let mut a = [0i32; N];
207 let mut ctr = 0;
208 while ctr < N {
209 if pos >= 136 {
210 crate::keccak::keccak_permute(&mut st);
211 crate::keccak::shake_squeeze_block(&st, &mut buf, 136);
212 pos = 0;
213 }
214 let b = buf[pos];
215 pos += 1;
216 let t0 = (b & 15) as i32;
217 let t1 = (b >> 4) as i32;
218 if t0 < 9 {
219 a[ctr] = ETA - t0;
220 ctr += 1;
221 }
222 if t1 < 9 && ctr < N {
223 a[ctr] = ETA - t1;
224 ctr += 1;
225 }
226 }
227 a
228}
229
230pub(crate) fn expand_a(rho: &[u8]) -> Vec<Vec<Poly>> {
236 #[cfg(target_arch = "x86_64")]
237 {
238 if std::is_x86_feature_detected!("avx2") {
239 const ENTRIES: usize = MK * ML;
240 let coords: [(usize, usize); ENTRIES] = std::array::from_fn(|i| (i / ML, i % ML));
241 let mut flat = vec![[0i32; N]; ENTRIES];
242 let mut e = 0;
243 while e + 4 <= ENTRIES {
244 let blocks: [[u8; 168]; 4] = std::array::from_fn(|l| {
245 let (r, c) = coords[e + l];
246 mldsa_expand_xof_block(rho, c as u8, r as u8)
247 });
248 let mut accs: [Vec<i32>; 4] = std::array::from_fn(|_| Vec::with_capacity(256));
249 let mut st = unsafe { crate::keccak::shake128_x4_absorb_once(&blocks) };
250 loop {
251 let outb = unsafe { crate::keccak::shake128_x4_squeeze_block(&st) };
252 let mut done = true;
253 for (l, acc) in accs.iter_mut().enumerate() {
254 if acc.len() < N {
255 mldsa_reject_ntt_block(&outb[l], acc);
256 }
257 if acc.len() < N {
258 done = false;
259 }
260 }
261 if done {
262 break;
263 }
264 unsafe { crate::keccak::keccak_f1600_x4(&mut st) };
265 }
266 for (l, acc) in accs.iter().enumerate() {
267 let (r, c) = coords[e + l];
268 flat[r * ML + c].copy_from_slice(&acc[..N]);
269 }
270 e += 4;
271 }
272 while e < ENTRIES {
273 let (r, c) = coords[e];
274 let mut seed = rho[..32].to_vec();
275 seed.push(c as u8);
276 seed.push(r as u8);
277 flat[r * ML + c] = rej_ntt_poly(&seed);
278 e += 1;
279 }
280 return (0..MK).map(|r| (0..ML).map(|c| flat[r * ML + c]).collect()).collect();
281 }
282 }
283 expand_a_scalar(rho)
284}
285
286fn expand_a_scalar(rho: &[u8]) -> Vec<Vec<Poly>> {
287 (0..MK)
288 .map(|r| {
289 (0..ML)
290 .map(|s| {
291 let mut seed = rho.to_vec();
292 seed.push(s as u8);
293 seed.push(r as u8);
294 rej_ntt_poly(&seed)
295 })
296 .collect()
297 })
298 .collect()
299}
300
301#[cfg(target_arch = "x86_64")]
304#[inline]
305fn mldsa_expand_xof_block(rho: &[u8], s: u8, r: u8) -> [u8; 168] {
306 let mut blk = [0u8; 168];
307 blk[..32].copy_from_slice(&rho[..32]);
308 blk[32] = s;
309 blk[33] = r;
310 blk[34] = 0x1f;
311 blk[167] |= 0x80;
312 blk
313}
314
315#[cfg(target_arch = "x86_64")]
318#[inline]
319fn mldsa_reject_ntt_block(buf: &[u8; 168], out: &mut Vec<i32>) {
320 let q = Q as u32;
321 let mut k = 0;
322 while k + 3 <= 168 && out.len() < N {
323 let d = (buf[k] as u32) | ((buf[k + 1] as u32) << 8) | (((buf[k + 2] as u32) & 0x7f) << 16);
324 k += 3;
325 if d < q {
326 out.push(d as i32);
327 }
328 }
329}
330
331fn pack_t1(t1: &Poly) -> Vec<u8> {
335 let mut r = vec![0u8; N / 4 * 5];
336 for i in 0..N / 4 {
337 let a = [t1[4 * i], t1[4 * i + 1], t1[4 * i + 2], t1[4 * i + 3]];
338 r[5 * i] = a[0] as u8;
339 r[5 * i + 1] = ((a[0] >> 8) | (a[1] << 2)) as u8;
340 r[5 * i + 2] = ((a[1] >> 6) | (a[2] << 4)) as u8;
341 r[5 * i + 3] = ((a[2] >> 4) | (a[3] << 6)) as u8;
342 r[5 * i + 4] = (a[3] >> 2) as u8;
343 }
344 r
345}
346
347fn pack_eta(p: &Poly) -> Vec<u8> {
349 let mut r = vec![0u8; N / 2];
350 for i in 0..N / 2 {
351 let t0 = (ETA - p[2 * i]) as u8;
352 let t1 = (ETA - p[2 * i + 1]) as u8;
353 r[i] = t0 | (t1 << 4);
354 }
355 r
356}
357
358fn pack_t0(p: &Poly) -> Vec<u8> {
360 let mut r = vec![0u8; N / 8 * 13];
361 for i in 0..N / 8 {
362 let mut t = [0u32; 8];
363 for j in 0..8 {
364 t[j] = ((1 << (D - 1)) - p[8 * i + j]) as u32;
365 }
366 let o = 13 * i;
367 r[o] = t[0] as u8;
368 r[o + 1] = ((t[0] >> 8) | (t[1] << 5)) as u8;
369 r[o + 2] = (t[1] >> 3) as u8;
370 r[o + 3] = ((t[1] >> 11) | (t[2] << 2)) as u8;
371 r[o + 4] = ((t[2] >> 6) | (t[3] << 7)) as u8;
372 r[o + 5] = (t[3] >> 1) as u8;
373 r[o + 6] = ((t[3] >> 9) | (t[4] << 4)) as u8;
374 r[o + 7] = (t[4] >> 4) as u8;
375 r[o + 8] = ((t[4] >> 12) | (t[5] << 1)) as u8;
376 r[o + 9] = ((t[5] >> 7) | (t[6] << 6)) as u8;
377 r[o + 10] = (t[6] >> 2) as u8;
378 r[o + 11] = ((t[6] >> 10) | (t[7] << 3)) as u8;
379 r[o + 12] = (t[7] >> 5) as u8;
380 }
381 r
382}
383
384pub const PK_BYTES: usize = 32 + MK * 320;
388pub const SK_BYTES: usize = 32 + 32 + 64 + ML * 128 + MK * 128 + MK * 416;
390
391pub fn keygen(seed: &[u8; 32]) -> (Vec<u8>, Vec<u8>) {
393 let mut h_in = seed.to_vec();
394 h_in.push(MK as u8);
395 h_in.push(ML as u8);
396 let hash = crate::keccak::shake256_bytes(&h_in, 128);
397 let rho = &hash[0..32];
398 let rho_prime = &hash[32..96];
399 let k_key = &hash[96..128];
400
401 let a_hat = expand_a(rho);
402 let s1: Vec<Poly> = (0..ML)
403 .map(|i| {
404 let mut seed = rho_prime.to_vec();
405 seed.extend_from_slice(&(i as u16).to_le_bytes());
406 rej_bounded_poly(&seed)
407 })
408 .collect();
409 let s2: Vec<Poly> = (0..MK)
410 .map(|i| {
411 let mut seed = rho_prime.to_vec();
412 seed.extend_from_slice(&((ML + i) as u16).to_le_bytes());
413 rej_bounded_poly(&seed)
414 })
415 .collect();
416
417 let s1_hat: Vec<Poly> = s1.iter().map(|p| { let mut q = *p; ntt(&mut q); q }).collect();
418 let mut t1 = vec![[0i32; N]; MK];
419 let mut t0 = vec![[0i32; N]; MK];
420 for i in 0..MK {
421 let mut acc = [0i32; N];
422 for j in 0..ML {
423 let prod = pointwise_montgomery(&a_hat[i][j], &s1_hat[j]);
424 for c in 0..N {
425 acc[c] += prod[c];
426 }
427 }
428 invntt_tomont(&mut acc);
429 for c in 0..N {
430 let t = freeze(acc[c] + s2[i][c]);
431 let (r1, r0) = power2round(t);
432 t1[i][c] = r1;
433 t0[i][c] = r0;
434 }
435 }
436
437 let mut pk = rho.to_vec();
438 for poly in &t1 {
439 pk.extend(pack_t1(poly));
440 }
441 let tr = crate::keccak::shake256_bytes(&pk, 64);
442
443 let mut sk = rho.to_vec();
444 sk.extend_from_slice(k_key);
445 sk.extend_from_slice(&tr);
446 for poly in &s1 {
447 sk.extend(pack_eta(poly));
448 }
449 for poly in &s2 {
450 sk.extend(pack_eta(poly));
451 }
452 for poly in &t0 {
453 sk.extend(pack_t0(poly));
454 }
455 (pk, sk)
456}
457
458pub(crate) const BETA: i32 = (TAU as i32) * ETA; pub(crate) const OMEGA: usize = 55;
461const CTILDE: usize = 48; pub const SIG_BYTES: usize = CTILDE + ML * 640 + OMEGA + MK;
464
465#[inline]
467fn to_signed(a: i32) -> i32 {
468 let r = freeze(a);
469 if r > Q / 2 {
470 r - Q
471 } else {
472 r
473 }
474}
475fn inf_norm(p: &Poly) -> i32 {
476 p.iter().map(|&c| to_signed(c).abs()).max().unwrap()
477}
478
479#[inline]
480fn nttp(p: &Poly) -> Poly {
481 let mut q = *p;
482 ntt(&mut q);
483 q
484}
485
486fn unpack_eta(b: &[u8]) -> Poly {
489 std::array::from_fn(|i| {
490 let byte = b[i / 2];
491 let nib = if i % 2 == 0 { byte & 15 } else { byte >> 4 };
492 ETA - nib as i32
493 })
494}
495
496fn unpack_t0(b: &[u8]) -> Poly {
497 let mut r = [0i32; N];
498 for i in 0..N / 8 {
499 let a: Vec<u32> = b[13 * i..13 * i + 13].iter().map(|&x| x as u32).collect();
500 let mut t = [0u32; 8];
501 t[0] = a[0] | (a[1] << 8);
502 t[1] = (a[1] >> 5) | (a[2] << 3) | (a[3] << 11);
503 t[2] = (a[3] >> 2) | (a[4] << 6);
504 t[3] = (a[4] >> 7) | (a[5] << 1) | (a[6] << 9);
505 t[4] = (a[6] >> 4) | (a[7] << 4) | (a[8] << 12);
506 t[5] = (a[8] >> 1) | (a[9] << 7);
507 t[6] = (a[9] >> 6) | (a[10] << 2) | (a[11] << 10);
508 t[7] = (a[11] >> 3) | (a[12] << 5);
509 for j in 0..8 {
510 r[8 * i + j] = (1 << (D - 1)) - (t[j] & 0x1fff) as i32;
511 }
512 }
513 r
514}
515
516fn unpack_t1(b: &[u8]) -> Poly {
517 let mut r = [0i32; N];
518 for i in 0..N / 4 {
519 let a: Vec<u32> = b[5 * i..5 * i + 5].iter().map(|&x| x as u32).collect();
520 r[4 * i] = ((a[0] | (a[1] << 8)) & 0x3ff) as i32;
521 r[4 * i + 1] = (((a[1] >> 2) | (a[2] << 6)) & 0x3ff) as i32;
522 r[4 * i + 2] = (((a[2] >> 4) | (a[3] << 4)) & 0x3ff) as i32;
523 r[4 * i + 3] = (((a[3] >> 6) | (a[4] << 2)) & 0x3ff) as i32;
524 }
525 r
526}
527
528fn unpack_z(b: &[u8]) -> Poly {
529 let mut r = [0i32; N];
530 for i in 0..N / 2 {
531 let a: Vec<u32> = b[5 * i..5 * i + 5].iter().map(|&x| x as u32).collect();
532 let z0 = (a[0] | (a[1] << 8) | (a[2] << 16)) & 0xfffff;
533 let z1 = ((a[2] >> 4) | (a[3] << 4) | (a[4] << 12)) & 0xfffff;
534 r[2 * i] = GAMMA1 - z0 as i32;
535 r[2 * i + 1] = GAMMA1 - z1 as i32;
536 }
537 r
538}
539
540fn unpack_hint(b: &[u8]) -> Option<Vec<Poly>> {
541 let mut h = vec![[0i32; N]; MK];
542 let mut k = 0usize;
543 for i in 0..MK {
544 let cnt = b[OMEGA + i] as usize;
545 if cnt < k || cnt > OMEGA {
546 return None;
547 }
548 for j in k..cnt {
549 if j > k && b[j] <= b[j - 1] {
550 return None; }
552 h[i][b[j] as usize] = 1;
553 }
554 k = cnt;
555 }
556 if b[k..OMEGA].iter().any(|&x| x != 0) {
557 return None; }
559 Some(h)
560}
561
562fn pack_z(p: &Poly) -> Vec<u8> {
565 let mut r = vec![0u8; N / 2 * 5];
566 for i in 0..N / 2 {
567 let t0 = (GAMMA1 - to_signed(p[2 * i])) as u32;
568 let t1 = (GAMMA1 - to_signed(p[2 * i + 1])) as u32;
569 r[5 * i] = t0 as u8;
570 r[5 * i + 1] = (t0 >> 8) as u8;
571 r[5 * i + 2] = ((t0 >> 16) | (t1 << 4)) as u8;
572 r[5 * i + 3] = (t1 >> 4) as u8;
573 r[5 * i + 4] = (t1 >> 12) as u8;
574 }
575 r
576}
577
578fn pack_w1(p: &Poly) -> Vec<u8> {
579 (0..N / 2).map(|i| (p[2 * i] | (p[2 * i + 1] << 4)) as u8).collect()
580}
581
582fn pack_hint(h: &[Poly]) -> Vec<u8> {
583 let mut r = vec![0u8; OMEGA + MK];
584 let mut k = 0usize;
585 for (i, poly) in h.iter().enumerate() {
586 for (j, &v) in poly.iter().enumerate() {
587 if v != 0 {
588 r[k] = j as u8;
589 k += 1;
590 }
591 }
592 r[OMEGA + i] = k as u8;
593 }
594 r
595}
596
597fn expand_mask(rho: &[u8], kappa: u16) -> Poly {
600 let mut seed = rho.to_vec();
601 seed.extend_from_slice(&kappa.to_le_bytes());
602 let buf = crate::keccak::shake256_bytes(&seed, N / 2 * 5);
603 let mut a = [0i32; N];
604 for i in 0..N / 2 {
605 let o = 5 * i;
606 let z0 = (buf[o] as u32) | ((buf[o + 1] as u32) << 8) | (((buf[o + 2] as u32) & 0xf) << 16);
607 let z1 = ((buf[o + 2] as u32) >> 4) | ((buf[o + 3] as u32) << 4) | ((buf[o + 4] as u32) << 12);
608 a[2 * i] = GAMMA1 - z0 as i32;
609 a[2 * i + 1] = GAMMA1 - z1 as i32;
610 }
611 a
612}
613
614fn sample_in_ball(seed: &[u8]) -> Poly {
615 let mut st = crate::keccak::shake_absorb(seed, 136);
616 let mut buf = [0u8; 136];
617 crate::keccak::shake_squeeze_block(&st, &mut buf, 136);
618 let signs = u64::from_le_bytes(buf[0..8].try_into().unwrap());
619 let mut pos = 8;
620 let mut c = [0i32; N];
621 let mut sign_idx = 0;
622 for i in (N - TAU)..N {
623 let j = loop {
624 if pos >= 136 {
625 crate::keccak::keccak_permute(&mut st);
626 crate::keccak::shake_squeeze_block(&st, &mut buf, 136);
627 pos = 0;
628 }
629 let candidate = buf[pos] as usize;
630 pos += 1;
631 if candidate <= i {
632 break candidate;
633 }
634 };
635 c[i] = c[j];
636 c[j] = 1 - 2 * (((signs >> sign_idx) & 1) as i32);
637 sign_idx += 1;
638 }
639 c
640}
641
642fn compute_mu(tr: &[u8], m: &[u8], ctx: &[u8]) -> Vec<u8> {
644 let mut mp = vec![0u8, ctx.len() as u8];
645 mp.extend_from_slice(ctx);
646 mp.extend_from_slice(m);
647 let mut mu_in = tr.to_vec();
648 mu_in.extend_from_slice(&mp);
649 crate::keccak::shake256_bytes(&mu_in, 64)
650}
651
652pub fn sign(sk: &[u8], m: &[u8], ctx: &[u8]) -> Vec<u8> {
656 let rho = &sk[0..32];
657 let k_key = &sk[32..64];
658 let tr = &sk[64..128];
659 let mut off = 128;
660 let mut take = |n: usize, sk: &[u8], off: &mut usize| {
661 let s = sk[*off..*off + n].to_vec();
662 *off += n;
663 s
664 };
665 let s1: Vec<Poly> = (0..ML).map(|_| unpack_eta(&take(128, sk, &mut off))).collect();
666 let s2: Vec<Poly> = (0..MK).map(|_| unpack_eta(&take(128, sk, &mut off))).collect();
667 let t0: Vec<Poly> = (0..MK).map(|_| unpack_t0(&take(416, sk, &mut off))).collect();
668
669 let a_hat = expand_a(rho);
670 let s1_hat: Vec<Poly> = s1.iter().map(nttp).collect();
671 let s2_hat: Vec<Poly> = s2.iter().map(nttp).collect();
672 let t0_hat: Vec<Poly> = t0.iter().map(nttp).collect();
673
674 let mu = compute_mu(tr, m, ctx);
675 let mut rp_in = k_key.to_vec();
676 rp_in.extend_from_slice(&[0u8; 32]); rp_in.extend_from_slice(&mu);
678 let rho_pp = crate::keccak::shake256_bytes(&rp_in, 64);
679
680 let mut kappa = 0u16;
681 loop {
682 let y: Vec<Poly> = (0..ML).map(|i| expand_mask(&rho_pp, kappa + i as u16)).collect();
683 let y_hat: Vec<Poly> = y.iter().map(nttp).collect();
684 let mut w = vec![[0i32; N]; MK];
685 for i in 0..MK {
686 let mut acc = [0i32; N];
687 for j in 0..ML {
688 let prod = pointwise_montgomery(&a_hat[i][j], &y_hat[j]);
689 for c in 0..N {
690 acc[c] += prod[c];
691 }
692 }
693 invntt_tomont(&mut acc);
694 w[i] = acc;
695 }
696 let w1: Vec<Poly> =
697 w.iter().map(|p| std::array::from_fn(|c| highbits(p[c]))).collect();
698
699 let mut ct_in = mu.clone();
700 for p in &w1 {
701 ct_in.extend(pack_w1(p));
702 }
703 let c_tilde = crate::keccak::shake256_bytes(&ct_in, CTILDE);
704 let c_hat = nttp(&sample_in_ball(&c_tilde));
705
706 let cs1: Vec<Poly> = s1_hat
707 .iter()
708 .map(|sh| {
709 let mut a = pointwise_montgomery(&c_hat, sh);
710 invntt_tomont(&mut a);
711 a
712 })
713 .collect();
714 let cs2: Vec<Poly> = s2_hat
715 .iter()
716 .map(|sh| {
717 let mut a = pointwise_montgomery(&c_hat, sh);
718 invntt_tomont(&mut a);
719 a
720 })
721 .collect();
722
723 let z: Vec<Poly> = (0..ML).map(|j| std::array::from_fn(|c| y[j][c] + cs1[j][c])).collect();
724 let r0: Vec<Poly> =
725 (0..MK).map(|i| std::array::from_fn(|c| lowbits(freeze(w[i][c] - cs2[i][c])))).collect();
726
727 if z.iter().any(|p| inf_norm(p) >= GAMMA1 - BETA)
728 || r0.iter().any(|p| inf_norm(p) >= GAMMA2 - BETA)
729 {
730 kappa += ML as u16;
731 continue;
732 }
733
734 let ct0: Vec<Poly> = t0_hat
735 .iter()
736 .map(|th| {
737 let mut a = pointwise_montgomery(&c_hat, th);
738 invntt_tomont(&mut a);
739 a
740 })
741 .collect();
742 let mut h = vec![[0i32; N]; MK];
743 let mut weight = 0i32;
744 for i in 0..MK {
745 for c in 0..N {
746 let r = w[i][c] - cs2[i][c] + ct0[i][c];
747 let bit = make_hint(-ct0[i][c], r) as i32;
748 h[i][c] = bit;
749 weight += bit;
750 }
751 }
752 if ct0.iter().any(|p| inf_norm(p) >= GAMMA2) || weight > OMEGA as i32 {
753 kappa += ML as u16;
754 continue;
755 }
756
757 let mut sig = c_tilde.clone();
758 for p in &z {
759 sig.extend(pack_z(p));
760 }
761 sig.extend(pack_hint(&h));
762 return sig;
763 }
764}
765
766pub fn verify(pk: &[u8], m: &[u8], ctx: &[u8], sig: &[u8]) -> bool {
768 if sig.len() != SIG_BYTES || pk.len() != PK_BYTES {
769 return false;
770 }
771 let rho = &pk[0..32];
772 let t1: Vec<Poly> = (0..MK).map(|i| unpack_t1(&pk[32 + i * 320..32 + (i + 1) * 320])).collect();
773
774 let c_tilde = &sig[0..CTILDE];
775 let mut off = CTILDE;
776 let z: Vec<Poly> = (0..ML)
777 .map(|_| {
778 let p = unpack_z(&sig[off..off + 640]);
779 off += 640;
780 p
781 })
782 .collect();
783 let h = match unpack_hint(&sig[off..off + OMEGA + MK]) {
784 Some(h) => h,
785 None => return false,
786 };
787 if z.iter().any(|p| inf_norm(p) >= GAMMA1 - BETA) {
788 return false;
789 }
790
791 let a_hat = expand_a(rho);
792 let tr = crate::keccak::shake256_bytes(pk, 64);
793 let mu = compute_mu(&tr, m, ctx);
794 let c_hat = nttp(&sample_in_ball(c_tilde));
795 let z_hat: Vec<Poly> = z.iter().map(nttp).collect();
796
797 let mut w1 = vec![[0i32; N]; MK];
798 for i in 0..MK {
799 let mut acc = [0i32; N];
800 for j in 0..ML {
801 let prod = pointwise_montgomery(&a_hat[i][j], &z_hat[j]);
802 for c in 0..N {
803 acc[c] += prod[c];
804 }
805 }
806 let t1d_hat = nttp(&std::array::from_fn(|c| t1[i][c] << D));
807 let ct1 = pointwise_montgomery(&c_hat, &t1d_hat);
808 for c in 0..N {
809 acc[c] -= ct1[c];
810 }
811 invntt_tomont(&mut acc);
812 for c in 0..N {
813 w1[i][c] = use_hint(h[i][c] as u8, acc[c]);
814 }
815 }
816
817 let mut ct_in = mu;
818 for p in &w1 {
819 ct_in.extend(pack_w1(p));
820 }
821 c_tilde == crate::keccak::shake256_bytes(&ct_in, CTILDE).as_slice()
822}
823
824pub fn bench_verify_breakdown(pk: &[u8], sig: &[u8]) {
826 use std::time::Instant;
827 let rho = &pk[0..32];
828 let c_tilde = &sig[0..CTILDE];
829 let iters = 2000u32;
830 macro_rules! t {
831 ($l:expr, $b:expr) => {{
832 for _ in 0..50 { std::hint::black_box($b); }
833 let s = Instant::now();
834 for _ in 0..iters { std::hint::black_box($b); }
835 eprintln!(" {:<22} {:>9.1} ns/op", $l, s.elapsed().as_nanos() as f64 / iters as f64);
836 }};
837 }
838 eprintln!("--- ML-DSA verify component breakdown ---");
839 t!("expand_a (30 poly)", expand_a(rho));
840 t!("sample_in_ball", sample_in_ball(c_tilde));
841 let c = sample_in_ball(c_tilde);
842 t!("nttp (1 poly)", nttp(&c));
843 t!("shake256(pk,64)", crate::keccak::shake256_bytes(pk, 64));
844}
845
846fn bytes(s: &[i64]) -> Vec<u8> {
849 s.iter().map(|&x| x.rem_euclid(256) as u8).collect()
850}
851fn seq(v: &[u8]) -> logicaffeine_data::LogosSeq<i64> {
852 logicaffeine_data::LogosSeq::from_vec(v.iter().map(|&b| b as i64).collect())
853}
854
855pub fn mldsa_keypair_seq(seed: &[i64]) -> logicaffeine_data::LogosSeq<i64> {
857 let mut s = [0u8; 32];
858 for (b, &x) in s.iter_mut().zip(seed) {
859 *b = x.rem_euclid(256) as u8;
860 }
861 let (pk, sk) = keygen(&s);
862 let mut out = pk;
863 out.extend(sk);
864 seq(&out)
865}
866pub fn mldsa_sign_seq(sk: &[i64], msg: &[i64], ctx: &[i64]) -> logicaffeine_data::LogosSeq<i64> {
868 seq(&sign(&bytes(sk), &bytes(msg), &bytes(ctx)))
869}
870pub fn mldsa_verify_seq(pk: &[i64], msg: &[i64], ctx: &[i64], sig: &[i64]) -> i64 {
872 verify(&bytes(pk), &bytes(msg), &bytes(ctx), &bytes(sig)) as i64
873}
874
875#[cfg(test)]
876mod tests {
877 use super::*;
878
879 fn schoolbook(a: &[i32; N], b: &[i32; N]) -> [i32; N] {
881 let mut c = [0i64; N];
882 for i in 0..N {
883 for j in 0..N {
884 let prod = a[i] as i64 * b[j] as i64;
885 if i + j < N {
886 c[i + j] += prod;
887 } else {
888 c[i + j - N] -= prod;
889 }
890 }
891 }
892 std::array::from_fn(|i| ((c[i] % Q as i64 + Q as i64) % Q as i64) as i32)
893 }
894
895 fn rand_coeff(s: &mut u64) -> i32 {
896 *s = s.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
897 ((*s >> 33) % Q as u64) as i32
898 }
899 fn rand_poly(s: &mut u64) -> [i32; N] {
900 std::array::from_fn(|_| rand_coeff(s))
901 }
902
903 #[test]
904 fn ntt_multiply_matches_schoolbook_convolution() {
905 let mut s = 0xD15A_C0DEu64;
906 for _ in 0..50 {
907 let a = rand_poly(&mut s);
908 let b = rand_poly(&mut s);
909 let mut na = a;
910 let mut nb = b;
911 ntt(&mut na);
912 ntt(&mut nb);
913 let mut nc = pointwise_montgomery(&na, &nb);
914 invntt_tomont(&mut nc);
915 let got: [i32; N] = std::array::from_fn(|i| freeze(nc[i]));
916 assert_eq!(got, schoolbook(&a, &b), "NTT product must equal the schoolbook convolution");
917 }
918 }
919
920 #[test]
921 fn power2round_reconstructs() {
922 let mut s = 0xABCD_1234u64;
923 for _ in 0..100000 {
924 let r = rand_coeff(&mut s);
925 let (r1, r0) = power2round(r);
926 assert!(r0 > -(1 << (D - 1)) && r0 <= (1 << (D - 1)), "r0 ∈ (−2¹², 2¹²]");
927 assert_eq!(r1 * (1 << D) + r0, r, "r = r1·2ᵈ + r0");
928 }
929 }
930
931 #[test]
932 fn decompose_reconstructs_and_bounds() {
933 let mut s = 0x5678_9ABCu64;
934 for _ in 0..100000 {
935 let r = rand_coeff(&mut s);
936 let (r1, r0) = decompose(r);
937 assert!(r0 > -GAMMA2 && r0 <= GAMMA2, "r0 ∈ (−γ2, γ2]: got {r0}");
938 assert!((0..16).contains(&r1), "r1 ∈ [0,16): got {r1}");
939 assert_eq!(freeze(r1 * 2 * GAMMA2 + r0), r, "r1·2γ2 + r0 = r (mod q)");
941 }
942 }
943
944 #[test]
945 fn use_hint_inverts_make_hint() {
946 let mut s = 0xFEED_FACEu64;
948 for _ in 0..100000 {
949 let r = rand_coeff(&mut s);
950 s = s.wrapping_mul(6364136223846793005).wrapping_add(1);
951 let z = ((s >> 40) as i32 % (2 * GAMMA2 + 1)) - GAMMA2; let h = make_hint(z, r);
953 assert_eq!(use_hint(h, r), highbits(freeze(r + z)), "UseHint∘MakeHint = HighBits(r+z)");
954 }
955 }
956
957 #[test]
958 fn sign_verify_round_trips_and_rejects_tamper() {
959 let (pk, sk) = keygen(&[0x33; 32]);
960 let m = b"the quick brown fox jumps over the lazy dog";
961 let ctx = b"ctx";
962 let sig = sign(&sk, m, ctx);
963 assert_eq!(sig.len(), SIG_BYTES, "ML-DSA-65 sig = 3309 bytes");
964 assert!(verify(&pk, m, ctx, &sig), "a fresh signature verifies");
965 assert!(!verify(&pk, b"different message", ctx, &sig), "wrong message rejects");
967 assert!(!verify(&pk, m, b"other", &sig), "wrong context rejects");
968 let mut bad = sig.clone();
969 bad[100] ^= 1;
970 assert!(!verify(&pk, m, ctx, &bad), "tampered signature rejects");
971 let (pk2, _) = keygen(&[0x99; 32]);
973 assert!(!verify(&pk2, m, ctx, &sig), "wrong public key rejects");
974 }
975
976 #[test]
977 fn keygen_sizes_and_determinism() {
978 let (pk, sk) = keygen(&[0x11; 32]);
979 assert_eq!(pk.len(), PK_BYTES, "ML-DSA-65 pk = 1952 bytes");
980 assert_eq!(sk.len(), SK_BYTES, "ML-DSA-65 sk = 4032 bytes");
981 assert_eq!(keygen(&[0x11; 32]).0, pk, "keygen is deterministic in the seed");
982 assert_ne!(keygen(&[0x22; 32]).0, pk, "distinct seeds ⇒ distinct keys");
983 }
984
985 #[test]
986 fn ntt_round_trip_is_identity() {
987 let mut s = 0x1234_5678u64;
989 let a = rand_poly(&mut s);
990 let mut one = [0i32; N];
992 one[0] = 1;
993 let mut na = a;
994 let mut n1 = one;
995 ntt(&mut na);
996 ntt(&mut n1);
997 let mut prod = pointwise_montgomery(&na, &n1);
998 invntt_tomont(&mut prod);
999 let got: [i32; N] = std::array::from_fn(|i| freeze(prod[i]));
1000 let want: [i32; N] = std::array::from_fn(|i| freeze(a[i]));
1001 assert_eq!(got, want, "a · 1 through the NTT must recover a");
1002 }
1003}