pub mod sampling;
pub mod mul;
use core::marker::PhantomData;
use zeroize::{Zeroize, ZeroizeOnDrop};
use crate::params::HqcParams;
pub(crate) const MAX_N_WORDS: usize = 901;
#[derive(Zeroize, ZeroizeOnDrop)]
pub struct Poly<P: HqcParams> {
pub(crate) words: [u64; MAX_N_WORDS],
_p: PhantomData<P>,
}
impl<P: HqcParams> Clone for Poly<P> {
fn clone(&self) -> Self {
Self { words: self.words, _p: PhantomData }
}
}
impl<P: HqcParams> Poly<P> {
#[inline]
pub fn zero() -> Self {
Self { words: [0u64; MAX_N_WORDS], _p: PhantomData }
}
#[inline(always)]
pub(crate) fn n_words() -> usize {
P::N_WORDS
}
#[inline]
pub fn get_bit(&self, i: usize) -> u64 {
debug_assert!(i < P::N, "bit index {i} out of range (N={})", P::N);
(self.words[i >> 6] >> (i & 63)) & 1
}
#[inline]
pub fn set_bit(&mut self, i: usize) {
debug_assert!(i < P::N, "bit index {i} out of range (N={})", P::N);
self.words[i >> 6] |= 1u64 << (i & 63);
}
#[inline]
pub fn clear_bit(&mut self, i: usize) {
debug_assert!(i < P::N, "bit index {i} out of range (N={})", P::N);
self.words[i >> 6] &= !(1u64 << (i & 63));
}
#[inline]
pub fn clear(&mut self) {
self.words.fill(0);
}
#[inline]
pub fn add(&self, rhs: &Self) -> Self {
let mut out = Self::zero();
for i in 0..P::N_WORDS {
out.words[i] = self.words[i] ^ rhs.words[i];
}
out
}
#[inline]
pub fn add_assign(&mut self, rhs: &Self) {
for i in 0..P::N_WORDS {
self.words[i] ^= rhs.words[i];
}
}
#[inline]
pub fn reduce(&mut self) {
let last_bit = P::N & 63; if last_bit == 0 {
return;
}
let last_idx = P::N_WORDS - 1;
let mask = (1u64 << last_bit) - 1;
let overflow = self.words[last_idx] >> last_bit;
self.words[last_idx] &= mask; self.words[0] ^= overflow; }
pub fn hamming_weight(&self) -> usize {
self.words[..P::N_WORDS]
.iter()
.map(|w| w.count_ones() as usize)
.sum()
}
}
impl<P: HqcParams> PartialEq for Poly<P> {
fn eq(&self, other: &Self) -> bool {
self.words[..P::N_WORDS] == other.words[..P::N_WORDS]
}
}
impl<P: HqcParams> Eq for Poly<P> {}
impl<P: HqcParams> core::fmt::Debug for Poly<P> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "Poly(N={}, weight={})", P::N, self.hamming_weight())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::params::{Hqc128, Hqc192, Hqc256};
#[test]
fn zero_poly_has_no_set_bits() {
let p = Poly::<Hqc128>::zero();
assert_eq!(p.hamming_weight(), 0);
for i in 0..Hqc128::N {
assert_eq!(p.get_bit(i), 0);
}
}
#[test]
fn set_and_get_bit_roundtrip() {
let mut p = Poly::<Hqc128>::zero();
let positions = [0, 1, 63, 64, 65, Hqc128::N - 1];
for &i in &positions {
p.set_bit(i);
assert_eq!(p.get_bit(i), 1, "bit {i} should be 1 after set");
}
assert_eq!(p.hamming_weight(), positions.len());
for &i in &positions {
p.clear_bit(i);
assert_eq!(p.get_bit(i), 0, "bit {i} should be 0 after clear");
}
assert_eq!(p.hamming_weight(), 0);
}
#[test]
fn add_is_xor() {
let mut a = Poly::<Hqc128>::zero();
let mut b = Poly::<Hqc128>::zero();
a.set_bit(0);
a.set_bit(1);
b.set_bit(1);
b.set_bit(2);
let c = a.add(&b);
assert_eq!(c.get_bit(0), 1);
assert_eq!(c.get_bit(1), 0); assert_eq!(c.get_bit(2), 1);
}
#[test]
fn add_assign_matches_add() {
let mut a = Poly::<Hqc192>::zero();
let mut b = Poly::<Hqc192>::zero();
a.set_bit(100);
b.set_bit(200);
let expected = a.add(&b);
a.add_assign(&b);
assert_eq!(a, expected);
}
#[test]
fn reduce_folds_overflow() {
let mut p = Poly::<Hqc128>::zero();
p.words[276] |= 1u64 << 5; p.reduce();
assert_eq!(p.get_bit(0), 1, "overflow bit N should fold to bit 0");
assert_eq!(p.words[276] >> 5, 0, "overflow cleared after reduce");
}
#[test]
fn reduce_is_idempotent() {
let mut p = Poly::<Hqc256>::zero();
p.set_bit(1000);
p.set_bit(50000);
p.reduce(); let w_before = p.hamming_weight();
p.reduce();
assert_eq!(p.hamming_weight(), w_before);
}
#[test]
fn hamming_weight_counts_only_active_bits() {
let mut p = Poly::<Hqc128>::zero();
p.words[Hqc128::N_WORDS - 1] = u64::MAX; let raw_count = p.hamming_weight();
assert_eq!(raw_count, 64); p.reduce();
let _ = p.hamming_weight();
}
#[test]
fn all_three_param_sets_compile() {
let _a = Poly::<Hqc128>::zero();
let _b = Poly::<Hqc192>::zero();
let _c = Poly::<Hqc256>::zero();
}
#[test]
fn clear_zeros_all_words() {
let mut p = Poly::<Hqc128>::zero();
p.set_bit(0);
p.set_bit(Hqc128::N - 1);
p.clear();
assert_eq!(p.hamming_weight(), 0);
assert!(p.words.iter().all(|&w| w == 0));
}
}