use rand_core::{CryptoRng, RngCore};
use subtle::{Choice, ConditionallySelectable, ConstantTimeEq};
use zeroize::Zeroizing;
use crate::hash::{self, SharedKey};
use crate::params::{HqcParams, SALT_BYTES, SEED_BYTES, SHARED_KEY_BYTES};
use crate::parsing;
use crate::pke::{self, DecryptionKey, EncryptionKey};
pub type PublicKey<P> = EncryptionKey<P>;
pub struct DecapsulationKey<P: HqcParams> {
seed_kem: Zeroizing<[u8; SEED_BYTES]>,
sigma: Zeroizing<[u8; SEED_BYTES]>,
dk_pke: DecryptionKey,
ek: EncryptionKey<P>,
}
impl<P: HqcParams> DecapsulationKey<P> {
pub fn public_key(&self) -> &EncryptionKey<P> {
&self.ek
}
pub fn to_bytes(&self) -> Zeroizing<[u8; SEED_BYTES]> {
self.seed_kem.clone()
}
pub fn from_bytes(bytes: &[u8]) -> Option<Self> {
if bytes.len() != SEED_BYTES {
return None;
}
let mut seed = [0u8; SEED_BYTES];
seed.copy_from_slice(bytes);
let (_, dk) = keygen_from_seed::<P>(&seed);
Some(dk)
}
#[cfg(feature = "kat")]
pub fn expanded_secret_key_bytes(&self) -> Vec<u8> {
let mut out = Vec::with_capacity(P::PK_BYTES + 2 * SEED_BYTES + P::K);
out.extend_from_slice(&self.ek.to_bytes()); out.extend_from_slice(&self.dk_pke.seed_dk); out.extend_from_slice(&self.sigma[..P::K]); out.extend_from_slice(&self.seed_kem[..]); debug_assert_eq!(out.len(), P::PK_BYTES + 2 * SEED_BYTES + P::K);
out
}
}
pub fn keygen<P: HqcParams, R: RngCore + CryptoRng>(
rng: &mut R,
) -> (PublicKey<P>, DecapsulationKey<P>) {
let mut seed_kem = Zeroizing::new([0u8; SEED_BYTES]);
rng.fill_bytes(&mut seed_kem[..]);
keygen_from_seed::<P>(&seed_kem)
}
pub fn keygen_from_seed<P: HqcParams>(
seed_kem: &[u8; SEED_BYTES],
) -> (PublicKey<P>, DecapsulationKey<P>) {
let mut xof = hash::xof(&seed_kem[..]);
let mut seed_pke = Zeroizing::new([0u8; SEED_BYTES]);
read_xof(&mut xof, &mut seed_pke[..]);
let mut sigma = Zeroizing::new([0u8; SEED_BYTES]);
read_xof(&mut xof, &mut sigma[..]);
let (ek, dk_pke) = pke::keygen::<P>(&seed_pke);
let dk = DecapsulationKey {
seed_kem: Zeroizing::new(*seed_kem),
sigma,
dk_pke,
ek: ek.clone(),
};
(ek, dk)
}
pub fn encaps<P: HqcParams, R: RngCore + CryptoRng>(
rng: &mut R,
ek: &PublicKey<P>,
) -> (SharedKey, Vec<u8>) {
let mut m = Zeroizing::new(vec![0u8; P::K]);
rng.fill_bytes(&mut m[..]);
let mut salt = [0u8; SALT_BYTES];
rng.fill_bytes(&mut salt);
encaps_deterministic::<P>(ek, &m, &salt)
}
pub fn encaps_deterministic<P: HqcParams>(
ek: &PublicKey<P>,
m: &[u8],
salt: &[u8; SALT_BYTES],
) -> (SharedKey, Vec<u8>) {
debug_assert_eq!(m.len(), P::K, "message must be exactly K bytes");
let ek_bytes = ek.to_bytes();
let ek_hash = hash::h_ek(&ek_bytes);
let (k, theta) = hash::g(&ek_hash, m, salt);
let (u, v) = pke::encrypt::<P>(ek, m, &theta[..]);
let c = parsing::pack_ciphertext::<P>(&u, &v, salt);
(k, c)
}
pub fn decaps<P: HqcParams>(dk: &DecapsulationKey<P>, c: &[u8]) -> SharedKey {
let ek_bytes = dk.ek.to_bytes();
let ek_hash = hash::h_ek(&ek_bytes);
let k_bar = hash::j(&ek_hash, &dk.sigma, c);
let (u, v, salt) = match parsing::unpack_ciphertext::<P>(c) {
Some(parts) => parts,
None => return k_bar,
};
let m_prime = pke::decrypt::<P>(&dk.dk_pke, &u, &v);
let decode_ok = Choice::from(m_prime.is_some() as u8);
let m_bytes: Zeroizing<Vec<u8>> =
Zeroizing::new(m_prime.unwrap_or_else(|| vec![0u8; P::K]));
let (k_prime, theta) = hash::g(&ek_hash, &m_bytes, &salt);
let (u2, v2) = pke::encrypt::<P>(&dk.ek, &m_bytes, &theta[..]);
let c_prime = parsing::pack_ciphertext::<P>(&u2, &v2, &salt);
let reencrypt_ok = c_prime.as_slice().ct_eq(c);
let valid = decode_ok & reencrypt_ok;
ct_select_key(&k_prime, &k_bar, valid)
}
fn read_xof(xof: &mut impl sha3::digest::XofReader, out: &mut [u8]) {
xof.read(out);
}
fn ct_select_key(a: &SharedKey, b: &SharedKey, choice: Choice) -> SharedKey {
let mut out = [0u8; SHARED_KEY_BYTES];
for i in 0..SHARED_KEY_BYTES {
out[i] = u8::conditional_select(&b[i], &a[i], choice);
}
out
}
#[cfg(test)]
mod tests {
use super::*;
use crate::params::{Hqc128, Hqc192, Hqc256};
struct TestRng(u64);
impl TestRng {
fn new(seed: u64) -> Self {
TestRng(seed.wrapping_add(0x9E37_79B9_7F4A_7C15))
}
}
impl RngCore for TestRng {
fn next_u32(&mut self) -> u32 {
(self.next_u64() >> 32) as u32
}
fn next_u64(&mut self) -> u64 {
self.0 = self.0.wrapping_add(0x9E37_79B9_7F4A_7C15);
let mut z = self.0;
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
z ^ (z >> 31)
}
fn fill_bytes(&mut self, dest: &mut [u8]) {
for chunk in dest.chunks_mut(8) {
let r = self.next_u64().to_le_bytes();
chunk.copy_from_slice(&r[..chunk.len()]);
}
}
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core::Error> {
self.fill_bytes(dest);
Ok(())
}
}
impl CryptoRng for TestRng {}
fn kem_roundtrip<P: HqcParams>() {
let seed_kem = [0x42u8; SEED_BYTES];
let (ek, dk) = keygen_from_seed::<P>(&seed_kem);
let m: Vec<u8> = (0..P::K).map(|i| (i as u8).wrapping_mul(7).wrapping_add(1)).collect();
let salt = [0x17u8; SALT_BYTES];
let (k_enc, c) = encaps_deterministic::<P>(&ek, &m, &salt);
assert_eq!(c.len(), P::CT_BYTES);
let k_dec = decaps::<P>(&dk, &c);
assert_eq!(k_enc, k_dec, "shared keys must agree on the valid path");
}
#[test]
fn kem_roundtrip_128() { kem_roundtrip::<Hqc128>(); }
#[test]
fn kem_roundtrip_192() { kem_roundtrip::<Hqc192>(); }
#[test]
fn kem_roundtrip_256() { kem_roundtrip::<Hqc256>(); }
#[test]
fn kem_roundtrip_with_rng() {
let mut rng = TestRng::new(1);
let (ek, dk) = keygen::<Hqc128, _>(&mut rng);
let (k_enc, c) = encaps::<Hqc128, _>(&mut rng, &ek);
let k_dec = decaps::<Hqc128>(&dk, &c);
assert_eq!(k_enc, k_dec);
}
#[test]
fn keygen_from_seed_is_deterministic() {
let seed = [0x5Au8; SEED_BYTES];
let (ek1, dk1) = keygen_from_seed::<Hqc128>(&seed);
let (ek2, dk2) = keygen_from_seed::<Hqc128>(&seed);
assert_eq!(ek1.seed_ek, ek2.seed_ek);
assert_eq!(ek1.s, ek2.s);
assert_eq!(*dk1.sigma, *dk2.sigma);
assert_eq!(*dk1.seed_kem, *dk2.seed_kem);
}
#[test]
fn encaps_is_deterministic() {
let (ek, _dk) = keygen_from_seed::<Hqc128>(&[1u8; SEED_BYTES]);
let m = vec![3u8; Hqc128::K];
let salt = [9u8; SALT_BYTES];
let (k1, c1) = encaps_deterministic::<Hqc128>(&ek, &m, &salt);
let (k2, c2) = encaps_deterministic::<Hqc128>(&ek, &m, &salt);
assert_eq!(k1, k2);
assert_eq!(c1, c2);
}
fn implicit_rejection<P: HqcParams>() {
let (ek, dk) = keygen_from_seed::<P>(&[0x33u8; SEED_BYTES]);
let m: Vec<u8> = (0..P::K).map(|i| i as u8 ^ 0x5A).collect();
let salt = [0x21u8; SALT_BYTES];
let (k_valid, mut c) = encaps_deterministic::<P>(&ek, &m, &salt);
c[0] ^= 0x01;
let k_rej = decaps::<P>(&dk, &c);
assert_ne!(k_valid, k_rej, "rejection key must differ from the valid K");
let k_rej2 = decaps::<P>(&dk, &c);
assert_eq!(k_rej, k_rej2, "implicit-rejection key must be deterministic");
let ek_hash = hash::h_ek(&ek.to_bytes());
let expected = hash::j(&ek_hash, &dk.sigma, &c);
assert_eq!(k_rej, expected, "rejection key must be J(H(ek), σ, c)");
}
#[test]
fn implicit_rejection_128() { implicit_rejection::<Hqc128>(); }
#[test]
fn implicit_rejection_192() { implicit_rejection::<Hqc192>(); }
#[test]
fn implicit_rejection_256() { implicit_rejection::<Hqc256>(); }
#[test]
fn decaps_handles_bad_length() {
let (_ek, dk) = keygen_from_seed::<Hqc128>(&[7u8; SEED_BYTES]);
let k0 = decaps::<Hqc128>(&dk, &[]);
let k1 = decaps::<Hqc128>(&dk, &vec![0u8; Hqc128::CT_BYTES - 1]);
let k2 = decaps::<Hqc128>(&dk, &vec![0u8; Hqc128::CT_BYTES + 1]);
assert_ne!(k0, k1);
assert_ne!(k1, k2);
}
#[test]
fn compressed_secret_key_roundtrip() {
let seed = [0xC7u8; SEED_BYTES];
let (ek, dk) = keygen_from_seed::<Hqc192>(&seed);
let compressed = dk.to_bytes();
assert_eq!(compressed.len(), SEED_BYTES);
let dk2 = DecapsulationKey::<Hqc192>::from_bytes(&compressed[..]).expect("valid length");
assert_eq!(*dk2.seed_kem, *dk.seed_kem);
assert_eq!(*dk2.sigma, *dk.sigma);
let m = vec![0x11u8; Hqc192::K];
let salt = [0x22u8; SALT_BYTES];
let (k_enc, c) = encaps_deterministic::<Hqc192>(&ek, &m, &salt);
assert_eq!(decaps::<Hqc192>(&dk, &c), k_enc);
assert_eq!(decaps::<Hqc192>(&dk2, &c), k_enc);
}
#[test]
fn from_bytes_rejects_bad_length() {
assert!(DecapsulationKey::<Hqc128>::from_bytes(&[]).is_none());
assert!(DecapsulationKey::<Hqc128>::from_bytes(&[0u8; SEED_BYTES - 1]).is_none());
}
}