use super::ntt::mod_q;
use super::params::{N, Q};
use alloc::vec::Vec;
pub const COMPRESSED_POLY_BYTES: usize = N * 3;
#[inline]
pub fn poly_pack_24(out: &mut [u8], poly: &[i32; N]) {
debug_assert!(out.len() >= COMPRESSED_POLY_BYTES);
for i in 0..N {
let c = mod_q(poly[i]) as u32;
out[i * 3] = c as u8;
out[i * 3 + 1] = (c >> 8) as u8;
out[i * 3 + 2] = (c >> 16) as u8;
}
}
#[inline]
pub fn poly_unpack_24(poly: &mut [i32; N], data: &[u8]) {
debug_assert!(data.len() >= COMPRESSED_POLY_BYTES);
for i in 0..N {
let c = data[i * 3] as u32 | ((data[i * 3 + 1] as u32) << 8) | ((data[i * 3 + 2] as u32) << 16);
poly[i] = c as i32;
}
}
pub struct CompressedVecK {
data: Vec<u8>,
len: usize,
}
impl CompressedVecK {
pub fn new(len: usize) -> Self {
Self {
data: vec![0u8; len * COMPRESSED_POLY_BYTES],
len,
}
}
pub fn pack(&mut self, idx: usize, poly: &[i32; N]) {
let off = idx * COMPRESSED_POLY_BYTES;
poly_pack_24(&mut self.data[off..off + COMPRESSED_POLY_BYTES], poly);
}
pub fn unpack(&self, idx: usize, poly: &mut [i32; N]) {
let off = idx * COMPRESSED_POLY_BYTES;
poly_unpack_24(poly, &self.data[off..off + COMPRESSED_POLY_BYTES]);
}
pub fn slot(&self, idx: usize) -> &[u8] {
let off = idx * COMPRESSED_POLY_BYTES;
&self.data[off..off + COMPRESSED_POLY_BYTES]
}
pub fn len(&self) -> usize {
self.len
}
pub fn sub_into(&self, idx: usize, b: &[i32; N], out: &mut [i32; N]) {
let off = idx * COMPRESSED_POLY_BYTES;
let data = &self.data[off..];
for i in 0..N {
let c = data[i * 3] as u32 | ((data[i * 3 + 1] as u32) << 8) | ((data[i * 3 + 2] as u32) << 16);
out[i] = c as i32 - b[i];
}
}
pub fn add_into(&self, idx: usize, b: &[i32; N], out: &mut [i32; N]) {
let off = idx * COMPRESSED_POLY_BYTES;
let data = &self.data[off..];
for i in 0..N {
let c = data[i * 3] as u32 | ((data[i * 3 + 1] as u32) << 8) | ((data[i * 3 + 2] as u32) << 16);
out[i] = c as i32 + b[i];
}
}
}
const MAX_TAU: usize = 60;
pub const COMPRESSED_CHALLENGE_BYTES: usize = MAX_TAU + 8;
pub fn challenge_compress(out: &mut [u8; COMPRESSED_CHALLENGE_BYTES], c: &[i32; N], tau: usize) {
for b in out.iter_mut() {
*b = 0;
}
let mut signs: u64 = 0;
let mut mask: u64 = 1;
let mut pos = 0;
for i in 0..N {
if c[i] != 0 {
out[pos] = i as u8;
pos += 1;
if c[i] == -1 {
signs |= mask;
}
mask <<= 1;
}
}
debug_assert_eq!(pos, tau);
for i in 0..8 {
out[MAX_TAU + i] = (signs >> (8 * i)) as u8;
}
}
pub fn challenge_decompress(c: &mut [i32; N], comp: &[u8; COMPRESSED_CHALLENGE_BYTES], tau: usize) {
for coeff in c.iter_mut() {
*coeff = 0;
}
let mut signs: u64 = 0;
for i in 0..8 {
signs |= (comp[MAX_TAU + i] as u64) << (8 * i);
}
for idx in 0..tau {
let pos = comp[idx] as usize;
if signs & 1 == 1 {
c[pos] = -1;
} else {
c[pos] = 1;
}
signs >>= 1;
}
}
pub fn schoolbook_mul_add(out: &mut [i32; N], c_comp: &[u8; COMPRESSED_CHALLENGE_BYTES], b: &[i32; N], tau: usize) {
let mut signs: u64 = 0;
for i in 0..8 {
signs |= (c_comp[MAX_TAU + i] as u64) << (8 * i);
}
for idx in 0..tau {
let ci = c_comp[idx] as usize; if signs & 1 == 0 {
for j in 0..N {
if ci + j < N {
out[ci + j] += b[j];
} else {
out[ci + j - N] -= b[j];
}
}
} else {
for j in 0..N {
if ci + j < N {
out[ci + j] -= b[j];
} else {
out[ci + j - N] += b[j];
}
}
}
signs >>= 1;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn pack_unpack_roundtrip() {
let mut poly = [0i32; N];
for i in 0..N {
poly[i] = ((i as i32 * 32771 + 13) % Q) as i32;
}
let mut buf = [0u8; COMPRESSED_POLY_BYTES];
poly_pack_24(&mut buf, &poly);
let mut recovered = [0i32; N];
poly_unpack_24(&mut recovered, &buf);
for i in 0..N {
assert_eq!(recovered[i], mod_q(poly[i]), "mismatch at i={}", i);
}
}
#[test]
fn challenge_compress_decompress_roundtrip() {
let tau = 39;
let mut c = [0i32; N];
for i in (N - tau)..N {
c[i] = if i % 3 == 0 { -1 } else { 1 };
}
let mut comp = [0u8; COMPRESSED_CHALLENGE_BYTES];
challenge_compress(&mut comp, &c, tau);
let mut recovered = [0i32; N];
challenge_decompress(&mut recovered, &comp, tau);
assert_eq!(c, recovered);
}
#[test]
fn schoolbook_mul_matches_ntt_pointwise() {
use super::super::ntt;
use super::super::sample;
use super::super::sha3;
let c_tilde = [0x42u8; 32];
let tau = 39; let c = sample::sample_in_ball::<super::super::params::MlDsa44>(&c_tilde);
let mut b = [0i32; N];
for i in 0..N {
b[i] = ((i as i32 * 32771 + 17) % Q) as i32;
}
let mut c_ntt = c;
for coeff in c_ntt.iter_mut() {
*coeff = mod_q(*coeff);
}
ntt::ntt(&mut c_ntt);
let mut b_ntt = b;
ntt::ntt(&mut b_ntt);
let mut prod_ntt = ntt::pointwise_mul(&c_ntt, &b_ntt);
ntt::ntt_inv(&mut prod_ntt);
for coeff in prod_ntt.iter_mut() {
*coeff = mod_q(*coeff);
}
let mut comp = [0u8; COMPRESSED_CHALLENGE_BYTES];
challenge_compress(&mut comp, &c, tau);
let mut prod_school = [0i32; N];
schoolbook_mul_add(&mut prod_school, &comp, &b, tau);
for coeff in prod_school.iter_mut() {
*coeff = mod_q(*coeff);
}
assert_eq!(prod_ntt, prod_school, "schoolbook mul must match NTT pointwise mul");
}
#[test]
fn compressed_vec_sub() {
let mut v = CompressedVecK::new(2);
let mut a = [0i32; N];
let mut b = [0i32; N];
for i in 0..N {
a[i] = ((i as i32 * 999 + 7) % Q) as i32;
b[i] = ((i as i32 * 333 + 3) % Q) as i32;
}
v.pack(0, &a);
let mut out = [0i32; N];
v.sub_into(0, &b, &mut out);
for i in 0..N {
assert_eq!(out[i], mod_q(a[i]) - b[i], "sub mismatch at i={}", i);
}
}
}