use crate::params::{HqcParams, SALT_BYTES, SEED_BYTES};
use crate::poly::Poly;
#[inline]
pub fn ring_bytes<P: HqcParams>() -> usize {
(P::N + 7) / 8
}
#[inline]
pub fn v_bytes<P: HqcParams>() -> usize {
(P::N1 * P::N2 + 7) / 8
}
fn pack<P: HqcParams>(poly: &Poly<P>, n_bits: usize) -> Vec<u8> {
let n_bytes = (n_bits + 7) / 8;
let mut out = vec![0u8; n_bytes];
for (k, b) in out.iter_mut().enumerate() {
*b = (poly.words[k / 8] >> (8 * (k % 8))) as u8;
}
let rem = n_bits % 8;
if rem != 0 {
out[n_bytes - 1] &= (1u8 << rem) - 1;
}
out
}
fn unpack<P: HqcParams>(bytes: &[u8], n_bits: usize) -> Poly<P> {
let n_bytes = (n_bits + 7) / 8;
debug_assert_eq!(bytes.len(), n_bytes);
let rem = n_bits % 8;
let mut p = Poly::<P>::zero();
for (k, &raw) in bytes.iter().enumerate() {
let byte = if rem != 0 && k == n_bytes - 1 {
raw & ((1u8 << rem) - 1)
} else {
raw
};
p.words[k / 8] |= (byte as u64) << (8 * (k % 8));
}
p
}
pub fn ring_to_bytes<P: HqcParams>(poly: &Poly<P>) -> Vec<u8> {
pack(poly, P::N)
}
pub fn ring_from_bytes<P: HqcParams>(bytes: &[u8]) -> Poly<P> {
unpack(bytes, P::N)
}
pub fn v_to_bytes<P: HqcParams>(poly: &Poly<P>) -> Vec<u8> {
pack(poly, P::N1 * P::N2)
}
pub fn v_from_bytes<P: HqcParams>(bytes: &[u8]) -> Poly<P> {
unpack(bytes, P::N1 * P::N2)
}
pub fn pack_public_key<P: HqcParams>(seed_ek: &[u8; SEED_BYTES], s: &Poly<P>) -> Vec<u8> {
let mut out = Vec::with_capacity(P::PK_BYTES);
out.extend_from_slice(seed_ek);
out.extend_from_slice(&ring_to_bytes(s));
debug_assert_eq!(out.len(), P::PK_BYTES);
out
}
pub fn unpack_public_key<P: HqcParams>(bytes: &[u8]) -> Option<([u8; SEED_BYTES], Poly<P>)> {
if bytes.len() != P::PK_BYTES {
return None;
}
let mut seed = [0u8; SEED_BYTES];
seed.copy_from_slice(&bytes[..SEED_BYTES]);
let s = ring_from_bytes::<P>(&bytes[SEED_BYTES..]);
Some((seed, s))
}
pub fn pack_ciphertext<P: HqcParams>(
u: &Poly<P>,
v: &Poly<P>,
salt: &[u8; SALT_BYTES],
) -> Vec<u8> {
let mut out = Vec::with_capacity(P::CT_BYTES);
out.extend_from_slice(&ring_to_bytes(u));
out.extend_from_slice(&v_to_bytes(v));
out.extend_from_slice(salt);
debug_assert_eq!(out.len(), P::CT_BYTES);
out
}
pub fn unpack_ciphertext<P: HqcParams>(
bytes: &[u8],
) -> Option<(Poly<P>, Poly<P>, [u8; SALT_BYTES])> {
if bytes.len() != P::CT_BYTES {
return None;
}
let rb = ring_bytes::<P>();
let vb = v_bytes::<P>();
let u = ring_from_bytes::<P>(&bytes[..rb]);
let v = v_from_bytes::<P>(&bytes[rb..rb + vb]);
let mut salt = [0u8; SALT_BYTES];
salt.copy_from_slice(&bytes[rb + vb..]);
Some((u, v, salt))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::params::{Hqc128, Hqc192, Hqc256};
fn patterned_poly<P: HqcParams>(stride: usize, bound: usize) -> Poly<P> {
let mut p = Poly::<P>::zero();
let mut i = 1;
while i < bound {
p.set_bit(i);
i += stride;
}
p
}
#[test]
fn bit_order_is_little_endian() {
let mut p = Poly::<Hqc128>::zero();
p.set_bit(0);
p.set_bit(7);
p.set_bit(8);
p.set_bit(63);
let bytes = ring_to_bytes(&p);
assert_eq!(bytes[0], 0b1000_0001); assert_eq!(bytes[1], 0b0000_0001); assert_eq!(bytes[7], 0b1000_0000); }
#[test]
fn lengths_match_params() {
assert_eq!(ring_bytes::<Hqc128>(), (Hqc128::N + 7) / 8);
assert_eq!(ring_to_bytes(&Poly::<Hqc128>::zero()).len(), (Hqc128::N + 7) / 8);
assert_eq!(v_to_bytes(&Poly::<Hqc128>::zero()).len(), Hqc128::N1 * Hqc128::N2 / 8);
let pk = pack_public_key::<Hqc128>(&[0u8; SEED_BYTES], &Poly::zero());
assert_eq!(pk.len(), Hqc128::PK_BYTES);
let ct = pack_ciphertext::<Hqc128>(&Poly::zero(), &Poly::zero(), &[0u8; SALT_BYTES]);
assert_eq!(ct.len(), Hqc128::CT_BYTES);
}
fn ring_roundtrip<P: HqcParams>() {
let p = patterned_poly::<P>(137, P::N);
let bytes = ring_to_bytes(&p);
assert_eq!(bytes.len(), ring_bytes::<P>());
let back = ring_from_bytes::<P>(&bytes);
assert_eq!(back, p);
}
#[test]
fn ring_roundtrip_128() { ring_roundtrip::<Hqc128>(); }
#[test]
fn ring_roundtrip_192() { ring_roundtrip::<Hqc192>(); }
#[test]
fn ring_roundtrip_256() { ring_roundtrip::<Hqc256>(); }
fn v_roundtrip<P: HqcParams>() {
let p = patterned_poly::<P>(91, P::N1 * P::N2);
let bytes = v_to_bytes(&p);
assert_eq!(bytes.len(), v_bytes::<P>());
let back = v_from_bytes::<P>(&bytes);
assert_eq!(back, p);
}
#[test]
fn v_roundtrip_128() { v_roundtrip::<Hqc128>(); }
#[test]
fn v_roundtrip_192() { v_roundtrip::<Hqc192>(); }
#[test]
fn v_roundtrip_256() { v_roundtrip::<Hqc256>(); }
#[test]
fn final_byte_high_bits_are_zero_on_pack() {
let rem = Hqc128::N % 8;
assert_ne!(rem, 0, "this test assumes N is not byte-aligned");
let p = patterned_poly::<Hqc128>(3, Hqc128::N); let bytes = ring_to_bytes(&p);
let last = *bytes.last().unwrap();
assert_eq!(last & !((1u8 << rem) - 1), 0, "high bits of final byte must be zero");
}
#[test]
fn unpack_masks_stray_high_bits() {
let mut bytes = ring_to_bytes(&patterned_poly::<Hqc128>(53, Hqc128::N));
let rem = Hqc128::N % 8;
*bytes.last_mut().unwrap() |= !((1u8 << rem) - 1); let p = ring_from_bytes::<Hqc128>(&bytes);
let repacked = ring_to_bytes(&p);
assert_eq!(repacked.last().unwrap() & !((1u8 << rem) - 1), 0);
}
fn public_key_roundtrip<P: HqcParams>() {
let seed: [u8; SEED_BYTES] =
core::array::from_fn(|i| (i as u8).wrapping_mul(3).wrapping_add(1));
let s = patterned_poly::<P>(101, P::N);
let packed = pack_public_key::<P>(&seed, &s);
let (seed_back, s_back) = unpack_public_key::<P>(&packed).expect("valid length");
assert_eq!(seed_back, seed);
assert_eq!(s_back, s);
}
#[test]
fn public_key_roundtrip_128() { public_key_roundtrip::<Hqc128>(); }
#[test]
fn public_key_roundtrip_192() { public_key_roundtrip::<Hqc192>(); }
#[test]
fn public_key_roundtrip_256() { public_key_roundtrip::<Hqc256>(); }
fn ciphertext_roundtrip<P: HqcParams>() {
let u = patterned_poly::<P>(71, P::N);
let v = patterned_poly::<P>(83, P::N1 * P::N2);
let salt: [u8; SALT_BYTES] = core::array::from_fn(|i| (i as u8) ^ 0x5A);
let packed = pack_ciphertext::<P>(&u, &v, &salt);
let (u_back, v_back, salt_back) = unpack_ciphertext::<P>(&packed).expect("valid length");
assert_eq!(u_back, u);
assert_eq!(v_back, v);
assert_eq!(salt_back, salt);
}
#[test]
fn ciphertext_roundtrip_128() { ciphertext_roundtrip::<Hqc128>(); }
#[test]
fn ciphertext_roundtrip_192() { ciphertext_roundtrip::<Hqc192>(); }
#[test]
fn ciphertext_roundtrip_256() { ciphertext_roundtrip::<Hqc256>(); }
#[test]
fn unpack_rejects_bad_lengths() {
assert!(unpack_public_key::<Hqc128>(&[]).is_none());
assert!(unpack_public_key::<Hqc128>(&vec![0u8; Hqc128::PK_BYTES - 1]).is_none());
assert!(unpack_ciphertext::<Hqc128>(&[]).is_none());
assert!(unpack_ciphertext::<Hqc128>(&vec![0u8; Hqc128::CT_BYTES + 1]).is_none());
}
}