use crate::kem::ntru_prime::constants::{P, Q};
const MODULUS: i32 = Q as i32;
#[inline(always)]
pub fn poly_mul_scalar(f: &[i16], g: &[i16]) -> Vec<i16> {
let n = f.len();
let mut result = vec![0i16; n];
for i in 0..n {
let mut acc = 0i32;
for j in 0..=i {
acc += (f[j] as i32) * (g[i - j] as i32);
}
result[i] = freeze(acc);
}
result
}
#[inline(always)]
pub fn poly_mul_full_convolution(f: &[i16], g: &[i16]) -> Vec<i32> {
let n = f.len();
let mut result = vec![0i32; 2 * n - 1];
for i in 0..n {
for j in 0..n {
result[i + j] += (f[i] as i32) * (g[j] as i32);
}
}
result
}
#[inline(always)]
pub fn reduce_scalar(a: &[i32]) -> Vec<i16> {
a.iter().map(|&x| freeze(x)).collect()
}
#[inline(always)]
pub fn poly_mul_batch_scalar(results: &mut [i16], f: &[i16], g: &[i16]) {
for (i, res) in results.iter_mut().enumerate() {
let mut acc = 0i32;
for j in 0..=i {
acc += (f[j] as i32) * (g[i - j] as i32);
}
*res = freeze(acc);
}
}
#[cfg(target_arch = "x86_64")]
use std::arch::x86_64::*;
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2")]
pub unsafe fn poly_mul_avx2(f: &[i16], g: &[i16]) -> Vec<i16> {
let n = f.len();
if n != g.len() {
return poly_mul_scalar(f, g);
}
let mut fg = vec![0i32; 2 * n - 1];
let modulus = _mm256_set1_epi32(MODULUS);
let zero = _mm256_setzero_si256();
for k_block in 0..((2 * n - 1 + 7) / 8) {
let k_start = k_block * 8;
let k_end = (k_start + 8).min(2 * n - 1);
let mut acc0 = _mm256_setzero_si256();
let mut acc1 = _mm256_setzero_si256();
let mut acc2 = _mm256_setzero_si256();
let mut acc3 = _mm256_setzero_si256();
let mut acc4 = _mm256_setzero_si256();
let mut acc5 = _mm256_setzero_si256();
let mut acc6 = _mm256_setzero_si256();
let mut acc7 = _mm256_setzero_si256();
for i in 0..n {
let f_i = f[i] as i32;
let f_vec = _mm256_set1_epi32(f_i);
let g_end = (i + 8).min(n);
let g_start = i;
for (k_idx, &g_val) in g[g_start..g_end].iter().enumerate() {
let k = i + k_idx;
if k >= k_start && k < k_end {
let prod = f_i * (g_val as i32);
match k - k_start {
0 => acc0 = _mm256_add_epi32(acc0, _mm256_set1_epi32(prod)),
1 => acc1 = _mm256_add_epi32(acc1, _mm256_set1_epi32(prod)),
2 => acc2 = _mm256_add_epi32(acc2, _mm256_set1_epi32(prod)),
3 => acc3 = _mm256_add_epi32(acc3, _mm256_set1_epi32(prod)),
4 => acc4 = _mm256_add_epi32(acc4, _mm256_set1_epi32(prod)),
5 => acc5 = _mm256_add_epi32(acc5, _mm256_set1_epi32(prod)),
6 => acc6 = _mm256_add_epi32(acc6, _mm256_set1_epi32(prod)),
7 => acc7 = _mm256_add_epi32(acc7, _mm256_set1_epi32(prod)),
_ => {}
}
}
}
}
for k in k_start..k_end {
let idx = k - k_start;
let acc = match idx {
0 => acc0,
1 => acc1,
2 => acc2,
3 => acc3,
4 => acc4,
5 => acc5,
6 => acc6,
_ => acc7,
};
fg[k] = _mm256_extract_epi32(acc, 0);
}
}
apply_ntru_reduction(&fg)
}
#[cfg(target_arch = "x86_64")]
#[inline(always)]
unsafe fn apply_ntru_reduction(fg: &[i32]) -> Vec<i16> {
let p_len = (fg.len() + 1) / 2;
let mut result = vec![0i32; p_len];
for i in 0..p_len {
result[i] = fg[i];
}
for i in p_len..fg.len() {
let coeff = fg[i];
let target1 = i - p_len;
let target2 = target1 + 1;
result[target1] += coeff;
if target2 < p_len {
result[target2] += coeff;
}
}
result.iter().map(|&v| freeze(v)).collect()
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2")]
pub unsafe fn reduce_avx2(a: &[i32]) -> Vec<i16> {
if !is_x86_feature_detected!("avx2") {
return reduce_scalar(a);
}
let n = a.len();
let mut result = vec![0i16; n];
let chunks = n / 8;
let rem = n % 8;
let modulus = _mm256_set1_epi32(MODULUS);
let q_inv_256 = _mm256_set1_epi32(934_409);
for i in 0..chunks {
let offset = i * 8;
let data = _mm256_loadu_si256(a.as_ptr().add(offset) as *const __m256i);
let t = _mm256_mullo_epi32(data, q_inv_256);
let t_shifted = _mm256_srli_epi32(t, 32);
let q_mul = _mm256_mullo_epi32(t_shifted, modulus);
let b = _mm256_sub_epi32(data, q_mul);
let c = _mm256_sub_epi32(b, modulus);
_mm256_storeu_si256(result.as_mut_ptr().add(offset) as *mut __m256i, c);
}
for i in (chunks * 8)..n {
result[i] = freeze(a[i]);
}
result
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2")]
pub unsafe fn freeze_batch_avx2(a: &mut [i32]) {
let n = a.len();
let chunks = n / 8;
let modulus = _mm256_set1_epi32(MODULUS);
let q_inv = _mm256_set1_epi32(934_409);
for i in 0..chunks {
let offset = i * 8;
let data = _mm256_loadu_si256(a.as_ptr().add(offset) as *const __m256i);
let t = _mm256_mullo_epi32(data, q_inv);
let t_hi = _mm256_srli_epi32(t, 32);
let q_mul = _mm256_mullo_epi32(t_hi, modulus);
let b = _mm256_sub_epi32(data, q_mul);
let b_arr: [i32; 8] = std::mem::transmute(b);
for j in 0..8 {
a[offset + j] = b_arr[j];
}
}
for i in (chunks * 8)..n {
a[i] = freeze(a[i]) as i32;
}
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2")]
pub unsafe fn poly_mul_avx2_optimized(f: &[i16], g: &[i16]) -> Vec<i16> {
let n = f.len();
if n <= 64 {
return poly_mul_scalar(f, g);
}
let mut result = vec![0i32; n];
const BLOCK_SIZE: usize = 8;
for i in 0..n {
let mut acc = _mm256_setzero_si256();
let j_max = (i + 1).min(n);
let j_limit = (j_max / BLOCK_SIZE) * BLOCK_SIZE;
for j in (0..j_limit).step_by(BLOCK_SIZE) {
let f_vec = load_i16_block(&f[j..]);
let mut g_tmp = [0i16; 8];
let g_start = i.saturating_sub(j + 7);
let g_end = i + 1 - j;
let g_len = (g_end - g_start).clamp(0, 8);
for k in 0..g_len {
if g_start + k < n && i - j - k >= 0 && i - j - k < n {
g_tmp[7 - k] = g[i - j - k];
}
}
let g_lo = _mm_loadu_si128(g_tmp.as_ptr() as *const __m128i);
let g_vec = _mm256_cvtepi16_epi32(g_lo);
let prod = _mm256_mullo_epi32(f_vec, g_vec);
acc = _mm256_add_epi32(acc, prod);
}
let mut sum = horizontal_sum_epi32(acc);
for j in j_limit..j_max {
sum += (f[j] as i32) * (g[i - j] as i32);
}
result[i] = sum;
}
let mut reduced = vec![0i16; n];
for i in 0..n {
reduced[i] = freeze(result[i]);
}
reduced
}
#[cfg(target_arch = "x86_64")]
#[inline(always)]
unsafe fn load_i16_block(arr: &[i16]) -> __m256i {
let mut tmp = [0i16; 8];
let len = arr.len().min(8);
tmp[..len].copy_from_slice(&arr[..len]);
let lo = _mm_loadu_si128(tmp.as_ptr() as *const __m128i);
_mm256_cvtepi16_epi32(lo)
}
#[cfg(target_arch = "x86_64")]
#[inline(always)]
unsafe fn load_i16_block_reverse(arr: &[i16], _base_idx: usize) -> __m256i {
let mut tmp = [0i16; 8];
let len = arr.len().min(8);
for (i, &val) in arr.iter().take(len).enumerate() {
tmp[7 - i] = val;
}
let lo = _mm_loadu_si128(tmp.as_ptr() as *const __m128i);
_mm256_cvtepi16_epi32(lo)
}
#[cfg(target_arch = "x86_64")]
#[inline(always)]
unsafe fn horizontal_sum_epi32(v: __m256i) -> i32 {
_mm256_extract_epi32(v, 0)
+ _mm256_extract_epi32(v, 1)
+ _mm256_extract_epi32(v, 2)
+ _mm256_extract_epi32(v, 3)
+ _mm256_extract_epi32(v, 4)
+ _mm256_extract_epi32(v, 5)
+ _mm256_extract_epi32(v, 6)
+ _mm256_extract_epi32(v, 7)
}
#[cfg(target_arch = "x86_64")]
#[inline(always)]
pub unsafe fn freeze_avx2(a: i32) -> i16 {
freeze(a)
}
#[cfg(target_arch = "aarch64")]
use std::arch::aarch64::*;
#[cfg(target_arch = "aarch64")]
#[target_feature(enable = "neon")]
pub unsafe fn poly_mul_neon(f: &[i16], g: &[i16]) -> Vec<i16> {
let n = f.len();
let mut result = vec![0i16; n];
for i in 0..n {
let mut scalar_acc = 0i32;
let j_max = i + 1;
for j in 0..j_max {
scalar_acc += (f[j] as i32) * (g[i - j] as i32);
}
result[i] = freeze(scalar_acc);
}
result
}
#[cfg(target_arch = "aarch64")]
#[target_feature(enable = "neon")]
pub unsafe fn reduce_neon(a: &[i32]) -> Vec<i16> {
a.iter().map(|&x| freeze(x)).collect()
}
#[inline(always)]
pub fn freeze(a: i32) -> i16 {
const MAGIC: i32 = 934_409;
const Q_I32: i32 = 4_591;
let t = ((a as i64) * (MAGIC as i64)) >> 32;
let b = a - (t as i32) * Q_I32;
let mut c = b - Q_I32;
let mask = (b >> 31) & Q_I32;
c += mask;
c as i16
}
#[inline(always)]
pub fn mod_mul(a: i16, b: i16) -> i16 {
freeze(a as i32 * b as i32)
}
#[inline(always)]
pub fn mod_sub(a: i16, b: i16) -> i16 {
freeze(a as i32 - b as i32)
}
#[inline(always)]
pub fn mod_mul_add(a: i16, b: i16, c: i16) -> i16 {
freeze(a as i32 + (b as i32 * c as i32))
}
#[inline(always)]
pub fn mod_mul_sub(a: i16, b: i16, c: i16) -> i16 {
freeze(a as i32 - (b as i32 * c as i32))
}
#[cfg(target_arch = "x86_64")]
pub fn poly_mul(f: &[i16], g: &[i16]) -> Vec<i16> {
#[cfg(target_feature = "avx2")]
{
if is_x86_feature_detected!("avx2") {
return unsafe { poly_mul_avx2_optimized(f, g) };
}
}
poly_mul_scalar(f, g)
}
#[cfg(not(target_arch = "x86_64"))]
pub fn poly_mul(f: &[i16], g: &[i16]) -> Vec<i16> {
#[cfg(target_arch = "aarch64")]
{
#[cfg(target_feature = "neon")]
{
return unsafe { poly_mul_neon(f, g) };
}
}
poly_mul_scalar(f, g)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_freeze() {
assert_eq!(freeze(0), 0);
assert_eq!(freeze(4591), 0);
assert_eq!(freeze(-4591), 0);
assert_eq!(freeze(2295), 2295); assert_eq!(freeze(-2296), -2296); }
#[test]
fn test_poly_mul_scalar() {
let f = [1i16, 2, 3, 4, 5];
let g = [2i16, 3, 4, 5, 6];
let result = poly_mul_scalar(&f, &g);
assert_eq!(result[0], 2);
assert_eq!(result[1], 7);
}
#[test]
fn test_poly_mul_dispatch() {
let f = [1i16, 2, 3, 4, 5];
let g = [2i16, 3, 4, 5, 6];
let result = poly_mul(&f, &g);
let expected = poly_mul_scalar(&f, &g);
assert_eq!(result, expected);
}
#[test]
fn test_mod_operations() {
assert_eq!(mod_mul(100, 50), freeze(5000));
assert_eq!(freeze(1000 + 2000), freeze(3000));
assert_eq!(mod_sub(100, 200), freeze(-100));
}
}