#![cfg(feature = "kat")]
use std::fs;
use sha3::digest::XofReader;
use hqcr::codes::{self, reed_muller, reed_solomon};
use hqcr::poly::mul::{mul_dense_ct, mul_sparse_dense};
use hqcr::poly::sampling::{sample_fixed_weight, sample_fixed_weight_mod, sample_uniform};
use hqcr::poly::Poly;
use hqcr::{hash, kem, parsing, pke};
use hqcr::{Hqc128, Hqc192, Hqc256, HqcParams};
fn concat_seeds(path: &str) -> Vec<u8> {
let content = fs::read_to_string(path).unwrap_or_else(|e| panic!("read {path}: {e}"));
let mut buf = Vec::new();
for line in content.lines() {
if let Some(rest) = line.trim().strip_prefix("seed = ") {
buf.extend_from_slice(&hex::decode(rest.trim()).expect("seed must be valid hex"));
}
}
assert!(buf.len() >= 96, "need at least two seeds' worth of bytes in {path}");
buf
}
fn ring_hex<P: HqcParams>(p: &Poly<P>) -> String {
hex::encode(parsing::ring_to_bytes::<P>(p))
}
fn v_hex<P: HqcParams>(p: &Poly<P>) -> String {
hex::encode(parsing::v_to_bytes::<P>(p))
}
fn kv(out: &mut String, label: &str, value: &str) {
out.push_str(label);
out.push_str(": ");
out.push_str(value);
out.push_str("\n\n");
}
fn generate<P: HqcParams>(req: &str, rsp: &str, level: u8, sec: u32) {
let buf = concat_seeds(req);
let mut seed_kem = [0u8; 32];
seed_kem.copy_from_slice(&buf[0..32]);
let m = &buf[32..32 + P::K];
let mut salt = [0u8; 16];
salt.copy_from_slice(&buf[32 + P::K..32 + P::K + 16]);
let (mut seed_pke, mut sigma) = ([0u8; 32], [0u8; 32]);
{
let mut xk = hash::xof(&seed_kem[..]);
xk.read(&mut seed_pke);
xk.read(&mut sigma);
}
let (seed_dk, seed_ek) = hash::i_pke_seed(&seed_pke);
let (ek, dk_pke) = pke::keygen::<P>(&seed_pke);
let (_ek_kem, dk_kem) = kem::keygen_from_seed::<P>(&seed_kem);
let (y, x) = {
let mut xdk = hash::xof(&seed_dk[..]);
let y = sample_fixed_weight::<P>(&mut xdk, P::OMEGA);
let x = sample_fixed_weight::<P>(&mut xdk, P::OMEGA);
(y, x)
};
let h = {
let mut xek = hash::xof(&seed_ek[..]);
sample_uniform::<P>(&mut xek)
};
let pk = ek.to_bytes();
let ek_hash = hash::h_ek(&pk);
let (k_shared, theta) = hash::g(&ek_hash, m, &salt);
let enc = encrypt_trace::<P>(&ek, m, &theta[..]);
let c = parsing::pack_ciphertext::<P>(&enc.u, &enc.v, &salt);
let (u_in, v_in, _salt_in) = parsing::unpack_ciphertext::<P>(&c).expect("well-formed c");
let y_dec = {
let mut xdk = hash::xof(&seed_dk[..]);
sample_fixed_weight::<P>(&mut xdk, P::OMEGA)
};
let uy = mul_dense_ct::<P>(&u_in, &y_dec);
let decode_input = v_in.add(&uy);
let cw_bytes = parsing::v_to_bytes::<P>(&decode_input);
let bb = P::N2 / 8;
let mut rm_result = vec![0u8; P::N1];
for j in 0..P::N1 {
rm_result[j] = reed_muller::rm_decode(&cw_bytes[j * bb..(j + 1) * bb], P::MULTIPLICITY);
}
let m_prime = pke::decrypt::<P>(&dk_pke, &u_in, &v_in).expect("valid ct decodes");
let (k_prime, theta_prime) = hash::g(&ek_hash, &m_prime, &salt);
let reenc = encrypt_trace::<P>(&ek, &m_prime, &theta_prime[..]);
let final_k = kem::decaps::<P>(&dk_kem, &c);
let mut out = String::new();
out.push_str(&format!("\n*********\n HQC-{level}\n*********\n\n"));
out.push_str(&format!(
"N: {} N1: {} N2: {} OMEGA: {} OMEGA_R: {} Failure rate: 2^-{} Sec: {} bits\n",
P::N, P::N1, P::N2, P::OMEGA, P::OMEGA_R, sec, sec
));
out.push_str("\n\n\n### KEYGEN ###\n\n");
kv(&mut out, "seed_dk", &hex::encode(&seed_dk[..]));
kv(&mut out, "seed_ek", &hex::encode(seed_ek));
kv(&mut out, "y", &ring_hex::<P>(&y));
kv(&mut out, "x", &ring_hex::<P>(&x));
kv(&mut out, "h", &ring_hex::<P>(&h));
kv(&mut out, "s", &ring_hex::<P>(&ek.s));
kv(&mut out, "seed_kem", &hex::encode(seed_kem));
kv(&mut out, "seed_pke", &hex::encode(seed_pke));
kv(&mut out, "sigma", &hex::encode(&sigma[..P::K]));
out.push_str("\n\n### ENCAPS ###\n\n");
kv(&mut out, "Reed-Solomon code word", &hex::encode(&enc.rs_codeword));
kv(&mut out, "Concatenated code word", &v_hex::<P>(&enc.concatenated));
kv(&mut out, "h", &ring_hex::<P>(&h));
kv(&mut out, "s", &ring_hex::<P>(&ek.s));
kv(&mut out, "r1", &ring_hex::<P>(&enc.r1));
kv(&mut out, "r2", &ring_hex::<P>(&enc.r2));
kv(&mut out, "e", &ring_hex::<P>(&enc.e));
kv(&mut out, "Truncate(s.r2 + e)", &v_hex::<P>(&enc.sr2_plus_e));
kv(&mut out, "c_pke->u", &ring_hex::<P>(&enc.u));
kv(&mut out, "c_pke->v", &v_hex::<P>(&enc.v));
kv(&mut out, "ek_kem", &hex::encode(&pk));
kv(&mut out, "m", &hex::encode(m));
kv(&mut out, "salt", &hex::encode(salt));
kv(&mut out, "H(ek_kem)", &hex::encode(ek_hash));
kv(&mut out, "theta", &hex::encode(&theta[..]));
kv(&mut out, "c_kem", &hex::encode(&c));
kv(&mut out, "K", &hex::encode(k_shared));
out.push_str("\n\n### DECAPS ###\n\n");
kv(&mut out, "c_pke.u", &ring_hex::<P>(&u_in));
kv(&mut out, "c_pke.v", &v_hex::<P>(&v_in));
kv(&mut out, "y", &ring_hex::<P>(&y_dec));
kv(&mut out, "Truncate(u.y)", &v_hex::<P>(&uy));
kv(&mut out, "v - Truncate(u.y)", &v_hex::<P>(&decode_input));
let mut synd = String::from("The syndromes: ");
for _ in 0..(2 * P::DELTA) {
synd.push_str("0 ");
}
out.push_str(&synd);
out.push_str("\n\n");
out.push_str("The error locator polynomial: sigma(x) = 1\n\n");
out.push_str("The polynomial: z(x) = 1\n\n");
out.push_str("The pairs of (error locator numbers, error values): \n\n\n");
kv(
&mut out,
"Reed-Muller decoding result (the input for the Reed-Solomon decoding algorithm)",
&hex::encode(&rm_result),
);
kv(&mut out, "Reed-Solomon code word", &hex::encode(&rm_result));
kv(&mut out, "Concatenated code word", &v_hex::<P>(&reenc.concatenated));
kv(&mut out, "h", &ring_hex::<P>(&h));
kv(&mut out, "s", &ring_hex::<P>(&ek.s));
kv(&mut out, "r1", &ring_hex::<P>(&reenc.r1));
kv(&mut out, "r2", &ring_hex::<P>(&reenc.r2));
kv(&mut out, "e", &ring_hex::<P>(&reenc.e));
kv(&mut out, "Truncate(s.r2 + e)", &v_hex::<P>(&reenc.sr2_plus_e));
kv(&mut out, "c_pke->u", &ring_hex::<P>(&reenc.u));
kv(&mut out, "c_pke->v", &v_hex::<P>(&reenc.v));
kv(&mut out, "ek_pke", &hex::encode(&pk));
kv(&mut out, "dk_pke", &hex::encode(&seed_dk[..]));
kv(&mut out, "c_kem", &hex::encode(&c));
kv(&mut out, "m_prime", &hex::encode(&m_prime));
kv(&mut out, "H(ek_kem)", &hex::encode(ek_hash));
kv(&mut out, "theta_prime", &hex::encode(&theta_prime[..]));
out.push_str("\n# Checking Ciphertext - Begin #\n\n");
kv(&mut out, "c_kem_prime_t.c_pke.u", &ring_hex::<P>(&reenc.u));
kv(&mut out, "c_kem_prime_t.c_pke.v", &v_hex::<P>(&reenc.v));
kv(&mut out, "salt", &hex::encode(salt));
out.push_str("# Checking Ciphertext - End #\n\n\n");
kv(&mut out, "K_prime", &hex::encode(k_prime));
out.push_str(&format!("secret1: {}\n", hex::encode(k_prime)));
out.push_str(&format!("secret2: {}\n\n", hex::encode(final_k)));
fs::write(rsp, out).unwrap_or_else(|e| panic!("write {rsp}: {e}"));
eprintln!("intermediate: wrote {rsp}");
}
struct EncryptTrace<P: HqcParams> {
rs_codeword: Vec<u8>, concatenated: Poly<P>, r1: Poly<P>,
r2: Poly<P>,
e: Poly<P>,
sr2_plus_e: Poly<P>, u: Poly<P>, v: Poly<P>,
}
fn encrypt_trace<P: HqcParams>(ek: &pke::EncryptionKey<P>, m: &[u8], theta: &[u8]) -> EncryptTrace<P> {
let (u, v) = pke::encrypt::<P>(ek, m, theta);
let mut rs_codeword = vec![0u8; P::N1];
reed_solomon::rs_encode(m, &mut rs_codeword, P::DELTA);
let concatenated = codes::encode::<P>(m);
let mut xth = hash::xof(theta);
let r2 = sample_fixed_weight_mod::<P>(&mut xth, P::OMEGA_R);
let e = sample_fixed_weight_mod::<P>(&mut xth, P::OMEGA_R);
let r1 = sample_fixed_weight_mod::<P>(&mut xth, P::OMEGA_R);
let sr2 = mul_sparse_dense::<P>(&r2, &ek.s);
let sr2_plus_e = sr2.add(&e);
EncryptTrace { rs_codeword, concatenated, r1, r2, e, sr2_plus_e, u, v }
}
#[test]
fn intermediate_hqc128() {
generate::<Hqc128>(
"tests/hqc-1/PQCkemKAT_2321.req",
"tests/hqc-1/intermediates_values.our",
1,
128,
);
}
#[test]
fn intermediate_hqc192() {
generate::<Hqc192>(
"tests/hqc-3/PQCkemKAT_4602.req",
"tests/hqc-3/intermediates_values.our",
3,
192,
);
}
#[test]
fn intermediate_hqc256() {
generate::<Hqc256>(
"tests/hqc-5/PQCkemKAT_7333.req",
"tests/hqc-5/intermediates_values.our",
5,
256,
);
}