pub mod reed_muller;
pub mod reed_solomon;
use crate::params::HqcParams;
use crate::poly::Poly;
#[inline]
fn codeword_bytes<P: HqcParams>() -> usize {
P::N1 * P::N2 / 8
}
#[inline]
fn block_bytes<P: HqcParams>() -> usize {
P::N2 / 8
}
pub fn encode<P: HqcParams>(msg: &[u8]) -> Poly<P> {
debug_assert_eq!(msg.len(), P::K, "message must be exactly K bytes");
let mut rs_cw = vec![0u8; P::N1];
reed_solomon::rs_encode(msg, &mut rs_cw, P::DELTA);
let mut buf = vec![0u8; codeword_bytes::<P>()];
for (j, &symbol) in rs_cw.iter().enumerate() {
reed_muller::rm_encode(symbol, P::MULTIPLICITY, &mut buf, j * P::N2);
}
bytes_to_poly::<P>(&buf)
}
pub fn decode<P: HqcParams>(poly: &Poly<P>) -> Option<Vec<u8>> {
let buf = poly_to_bytes::<P>(codeword_bytes::<P>(), poly);
let bb = block_bytes::<P>();
let mut rs_cw = vec![0u8; P::N1];
for j in 0..P::N1 {
let block = &buf[j * bb..(j + 1) * bb];
rs_cw[j] = reed_muller::rm_decode(block, P::MULTIPLICITY);
}
reed_solomon::rs_decode(&rs_cw, P::K, P::DELTA)
}
fn bytes_to_poly<P: HqcParams>(buf: &[u8]) -> Poly<P> {
debug_assert!(buf.len() <= P::N_WORDS * 8);
let mut p = Poly::<P>::zero();
for (k, &b) in buf.iter().enumerate() {
p.words[k / 8] |= (b as u64) << (8 * (k % 8));
}
p
}
fn poly_to_bytes<P: HqcParams>(len: usize, p: &Poly<P>) -> Vec<u8> {
debug_assert!(len <= P::N_WORDS * 8);
let mut buf = vec![0u8; len];
for (k, b) in buf.iter_mut().enumerate() {
*b = (p.words[k / 8] >> (8 * (k % 8))) as u8;
}
buf
}
#[cfg(test)]
mod tests {
use super::*;
use crate::params::{Hqc128, Hqc192, Hqc256};
fn test_msg<P: HqcParams>() -> Vec<u8> {
(0..P::K).map(|i| (i.wrapping_mul(37).wrapping_add(11)) as u8).collect()
}
fn roundtrip_no_errors<P: HqcParams>() {
let msg = test_msg::<P>();
let cw = encode::<P>(&msg);
let recovered = decode::<P>(&cw).expect("decode failed on clean codeword");
assert_eq!(recovered, msg);
}
#[test]
fn roundtrip_no_errors_128() { roundtrip_no_errors::<Hqc128>(); }
#[test]
fn roundtrip_no_errors_192() { roundtrip_no_errors::<Hqc192>(); }
#[test]
fn roundtrip_no_errors_256() { roundtrip_no_errors::<Hqc256>(); }
#[test]
fn codeword_fits_in_ring_with_zero_tail() {
let msg = test_msg::<Hqc128>();
let cw = encode::<Hqc128>(&msg);
for i in (Hqc128::N1 * Hqc128::N2)..Hqc128::N {
assert_eq!(cw.get_bit(i), 0, "trailing bit {i} must be zero");
}
}
fn flip_bit<P: HqcParams>(p: &mut Poly<P>, pos: usize) {
if p.get_bit(pos) == 1 { p.clear_bit(pos); } else { p.set_bit(pos); }
}
fn roundtrip_small_bit_errors<P: HqcParams>() {
let msg = test_msg::<P>();
let mut cw = encode::<P>(&msg);
for j in 0..P::N1.min(4) {
for b in 0..5 {
flip_bit::<P>(&mut cw, j * P::N2 + b * 7);
}
}
let recovered = decode::<P>(&cw).expect("decode failed within RM capacity");
assert_eq!(recovered, msg);
}
#[test]
fn roundtrip_small_bit_errors_128() { roundtrip_small_bit_errors::<Hqc128>(); }
#[test]
fn roundtrip_small_bit_errors_192() { roundtrip_small_bit_errors::<Hqc192>(); }
#[test]
fn roundtrip_small_bit_errors_256() { roundtrip_small_bit_errors::<Hqc256>(); }
fn roundtrip_delta_symbol_errors<P: HqcParams>() {
let msg = test_msg::<P>();
let mut cw = encode::<P>(&msg);
for j in 0..P::DELTA {
for b in 0..P::N2 {
flip_bit::<P>(&mut cw, j * P::N2 + b);
}
}
let recovered = decode::<P>(&cw).expect("RS should correct δ symbol errors");
assert_eq!(recovered, msg);
}
#[test]
fn roundtrip_delta_symbol_errors_128() { roundtrip_delta_symbol_errors::<Hqc128>(); }
#[test]
fn roundtrip_delta_symbol_errors_192() { roundtrip_delta_symbol_errors::<Hqc192>(); }
#[test]
fn roundtrip_delta_symbol_errors_256() { roundtrip_delta_symbol_errors::<Hqc256>(); }
}