use crate::params::Q;
pub type Fe = u16;
pub const MONT_R: u16 = 2285;
pub const QINV: u32 = 62209;
#[inline(always)]
pub fn barrett_reduce(a: i32) -> u16 {
const V: i64 = (1i64 << 26) / (Q as i64);
let t = ((V * a as i64) >> 26) as i32 * (Q as i32);
let r = a - t;
let r = r + ((r >> 31) & Q as i32);
let r = r - ((((Q as i32 - 1 - r) >> 31) & 1) * Q as i32);
r as u16
}
#[inline(always)]
pub fn fqadd(a: Fe, b: Fe) -> Fe {
let s = a as i32 + b as i32 - Q as i32;
(s + (((s >> 15) & 1) * Q as i32)) as u16
}
#[inline(always)]
pub fn fqsub(a: Fe, b: Fe) -> Fe {
let s = a as i32 - b as i32;
(s + (((s >> 15) & 1) * Q as i32)) as u16
}
#[inline(always)]
pub fn fqmul(a: Fe, b: Fe) -> Fe {
barrett_reduce(a as i32 * b as i32)
}
#[inline(always)]
pub fn montgomery_reduce(a: i32) -> u16 {
let m = a.wrapping_mul(QINV as i32) as i16;
let r = (a - (m as i32) * (Q as i32)) >> 16;
let r = r + ((r >> 31) & (Q as i32));
r as u16
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn barrett_matches_naive() {
for a in 0..(Q as i32 * Q as i32) {
assert_eq!(barrett_reduce(a), (a % Q as i32) as u16);
if a > 100000 {
break;
} }
for a in [0, 1, Q as i32 - 1, Q as i32, Q as i32 * (Q as i32 - 1)] {
assert_eq!(barrett_reduce(a), (a % Q as i32) as u16);
}
}
#[test]
fn ops() {
assert_eq!(fqadd(3000, 500), 3500 - Q);
assert_eq!(fqsub(10, 20), (Q as i32 - 10) as u16);
assert_eq!(fqmul(17, 17), 289);
}
}
#[cfg(kani)]
mod kani_proofs {
use super::*;
#[kani::proof]
fn barrett_reduce_matches_naive_nonneg() {
let a: i32 = kani::any();
kani::assume(a >= 0 && a < (Q as i32) * (Q as i32));
let r = barrett_reduce(a);
assert!(r < Q);
assert_eq!(r as i32, a % Q as i32);
}
#[kani::proof]
fn fqadd_in_range() {
let a: u16 = kani::any();
let b: u16 = kani::any();
kani::assume(a < Q && b < Q);
let r = fqadd(a, b);
assert!(r < Q);
assert_eq!(r as u32, (a as u32 + b as u32) % Q as u32);
}
#[kani::proof]
fn fqsub_in_range() {
let a: u16 = kani::any();
let b: u16 = kani::any();
kani::assume(a < Q && b < Q);
let r = fqsub(a, b);
assert!(r < Q);
let expected = ((a as i32) - (b as i32)).rem_euclid(Q as i32) as u16;
assert_eq!(r, expected);
}
#[kani::proof]
fn montgomery_reduce_in_range() {
let a: i32 = kani::any();
kani::assume(a >= 0 && a < (Q as i32) * (Q as i32));
let r = montgomery_reduce(a);
assert!(r < Q);
}
}