use super::params::{N, N_INV, Q};
const Q64: i64 = Q as i64;
const QINV: i32 = 58728449;
const R_SQ_MOD_Q: i64 = 2365951;
#[inline(always)]
pub fn mod_q(a: i32) -> i32 {
let mut r = a % Q;
r += Q & (r >> 31);
r
}
#[inline(always)]
pub fn mul_mod_q(a: i32, b: i32) -> i32 {
mod_q(((a as i64 * b as i64) % Q64) as i32)
}
#[inline(always)]
fn montgomery_reduce(a: i64) -> i32 {
let t = (a as i32).wrapping_mul(QINV) as i64;
((a - t * Q64) >> 32) as i32
}
#[inline(always)]
fn mont_mul(a: i32, b: i32) -> i32 {
montgomery_reduce(a as i64 * b as i64)
}
#[inline(always)]
fn to_mont(a: i32) -> i32 {
montgomery_reduce(a as i64 * R_SQ_MOD_Q)
}
const fn compute_zetas_mont() -> [i32; N] {
let mut table = [0i32; N];
let mut k = 0;
while k < N {
let rev = bitrev8(k as u8) as u64;
let z = pow_mod(1753, rev, Q64);
let a = z as i64 * R_SQ_MOD_Q;
let t = (a as i32).wrapping_mul(QINV) as i64;
table[k] = ((a - t * Q64) >> 32) as i32;
k += 1;
}
table
}
const ZETAS_MONT: [i32; N] = compute_zetas_mont();
const fn bitrev8(k: u8) -> u8 {
let mut r = 0u8;
let mut v = k;
let mut i = 0;
while i < 8 {
r = (r << 1) | (v & 1);
v >>= 1;
i += 1;
}
r
}
const fn pow_mod(mut base: i64, mut exp: u64, m: i64) -> i64 {
let mut result = 1i64;
base %= m;
while exp > 0 {
if exp & 1 == 1 {
result = (result * base) % m;
}
exp >>= 1;
base = (base * base) % m;
}
result
}
const fn compute_zetas() -> [i32; N] {
let mut table = [0i32; N];
let mut k = 0usize;
while k < N {
let rev = bitrev8(k as u8) as u64;
let val = pow_mod(1753, rev, Q as i64);
table[k] = val as i32;
k += 1;
}
table
}
pub const ZETAS: [i32; N] = compute_zetas();
pub fn ntt(f: &mut [i32; N]) {
let mut m = 0usize;
let mut len = 128;
while len >= 1 {
let mut start = 0;
while start < N {
m += 1;
let zeta = ZETAS_MONT[m];
let mut j = start;
while j < start + len {
let t = mont_mul(zeta, f[j + len]);
f[j + len] = f[j] - t;
f[j] = f[j] + t;
j += 1;
}
start += 2 * len;
}
len /= 2;
}
for c in f.iter_mut() {
*c = mod_q(*c);
}
}
const F_SCALE_DSA: i32 = 41978;
pub fn ntt_inv(f: &mut [i32; N]) {
let mut m = N; let mut len = 1;
while len <= 128 {
let mut start = 0;
while start < N {
m -= 1;
let neg_zeta = mod_q(-ZETAS_MONT[m]);
let mut j = start;
while j < start + len {
let t = f[j];
f[j] = t + f[j + len];
f[j + len] = montgomery_reduce(neg_zeta as i64 * (t - f[j + len]) as i64);
j += 1;
}
start += 2 * len;
}
len *= 2;
}
for coeff in f.iter_mut() {
*coeff = montgomery_reduce(F_SCALE_DSA as i64 * *coeff as i64);
}
}
pub fn pointwise_mul(a: &[i32; N], b: &[i32; N]) -> [i32; N] {
let mut c = [0i32; N];
for i in 0..N {
c[i] = mont_mul(a[i], b[i]);
}
c
}
pub fn to_mont_poly(f: &mut [i32; N]) {
for c in f.iter_mut() {
*c = montgomery_reduce(*c as i64 * R_SQ_MOD_Q);
}
}
pub fn poly_add(a: &[i32; N], b: &[i32; N]) -> [i32; N] {
let mut c = [0i32; N];
for i in 0..N {
c[i] = mod_q(a[i] + b[i]);
}
c
}
pub fn poly_sub(a: &[i32; N], b: &[i32; N]) -> [i32; N] {
let mut c = [0i32; N];
for i in 0..N {
c[i] = mod_q(a[i] - b[i]);
}
c
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_zetas_0() {
assert_eq!(ZETAS[0], 1);
}
#[test]
fn test_ntt_roundtrip() {
let mut f = [0i32; N];
for i in 0..N {
f[i] = (i as i32 * 17 + 3) % Q;
}
let orig = f;
ntt(&mut f);
let mut one = [0i32; N];
one[0] = 1;
ntt(&mut one);
let h = pointwise_mul(&one, &f);
let mut result = h;
ntt_inv(&mut result);
for i in 0..N {
let r = mod_q(result[i]);
assert_eq!(r, orig[i], "mismatch at index {}: got {} expected {}", i, r, orig[i]);
}
}
#[test]
fn test_mod_q_negative() {
assert_eq!(mod_q(-1), Q - 1);
assert_eq!(mod_q(0), 0);
assert_eq!(mod_q(Q), 0);
}
}