use crate::error::PrivacyError;
use crate::types::EphemeralKey;
use hkdf::Hkdf;
use sha2::{Digest, Sha256};
use aes_gcm::{Aes256Gcm, Key, Nonce, AeadInPlace, KeyInit};
use zeroize::Zeroize;
use pqc_kem::fips203::MlKem768Keypair;
use pqc_kem::types::{KemAlgorithm, KemCiphertext, KemPublicKey};
extern crate alloc;
use alloc::vec::Vec;
pub const HOP_INTERVAL_MS: u64 = 1;
const TAG_LEN: usize = 16;
pub struct QfkhRatchet {
chain_key: [u8; 32],
interval_ms: u64,
created_ms: u64,
hop_count: u64,
}
impl QfkhRatchet {
pub fn new(
shared_secret: [u8; 32],
chaos_seed: &[u8; 32],
now_ms: u64,
) -> Self {
let mut chain_key = [0u8; 32];
hkdf_derive(&shared_secret, chaos_seed, b"qfkh-init-v1", &mut chain_key)
.expect("HKDF init failed");
Self {
chain_key,
interval_ms: HOP_INTERVAL_MS,
created_ms: now_ms,
hop_count: 0,
}
}
pub fn initiate(chaos_seed: &[u8; 32]) -> Result<(Vec<u8>, Vec<u8>), PrivacyError> {
let mut seed_64 = [0u8; 64];
seed_64[..32].copy_from_slice(chaos_seed);
let hash = Sha256::digest(chaos_seed);
seed_64[32..].copy_from_slice(&hash);
let keypair = MlKem768Keypair::from_secret_key_bytes(&seed_64)
.map_err(|e| PrivacyError::Internal(alloc::format!("ML-KEM-768 initiate failed: {:?}", e)))?;
let ek = keypair.public_key();
let dk = keypair.secret_key();
Ok((dk.bytes.clone(), ek.bytes))
}
pub fn respond(
encapsulation_key: &[u8],
chaos_seed: &[u8; 32],
now_ms: u64,
) -> Result<(Self, Vec<u8>), PrivacyError> {
let ek = KemPublicKey::new(KemAlgorithm::MlKem768, encapsulation_key.to_vec());
let mut rng_seed = [0u8; 32];
let mut h = Sha256::new();
h.update(encapsulation_key);
h.update(chaos_seed);
h.update(b"qfkh-respond-rng-v1");
rng_seed.copy_from_slice(&h.finalize());
let mut rng = DeterministicRng { state: rng_seed, counter: 0 };
let (ct, ss) = MlKem768Keypair::encapsulate(&mut rng, &ek)
.map_err(|e| PrivacyError::Internal(alloc::format!("ML-KEM-768 respond failed: {:?}", e)))?;
let mut ss_arr = [0u8; 32];
let ss_bytes = ss.bytes.clone();
let copy_len = ss_bytes.len().min(32);
ss_arr[..copy_len].copy_from_slice(&ss_bytes[..copy_len]);
let ratchet = Self::new(ss_arr, chaos_seed, now_ms);
Ok((ratchet, ct.bytes))
}
pub fn complete(
decapsulation_key: &[u8],
ciphertext: &[u8],
chaos_seed: &[u8; 32],
now_ms: u64,
) -> Result<Self, PrivacyError> {
let keypair = MlKem768Keypair::from_secret_key_bytes(decapsulation_key)
.map_err(|e| PrivacyError::Internal(alloc::format!("ML-KEM-768 complete failed: {:?}", e)))?;
let ct = KemCiphertext::new(KemAlgorithm::MlKem768, ciphertext.to_vec());
let ss = keypair.decapsulate(&ct)
.map_err(|e| PrivacyError::Internal(alloc::format!("ML-KEM-768 decapsulate failed: {:?}", e)))?;
let mut ss_arr = [0u8; 32];
let ss_bytes = ss.bytes.clone();
let copy_len = ss_bytes.len().min(32);
ss_arr[..copy_len].copy_from_slice(&ss_bytes[..copy_len]);
Ok(Self::new(ss_arr, chaos_seed, now_ms))
}
pub fn hop(
&mut self,
chaos_perturbation: &[u8; 32],
now_ms: u64,
) -> Result<EphemeralKey, PrivacyError> {
let elapsed = now_ms.saturating_sub(self.created_ms + self.hop_count * self.interval_ms);
if elapsed > self.interval_ms * 10 {
return Err(PrivacyError::KeyHopIntervalExceeded {
elapsed_ms: elapsed,
limit_ms: self.interval_ms * 10,
});
}
let mut message_key = [0u8; 32];
hkdf_derive(&self.chain_key, chaos_perturbation, b"qfkh-msg-v1", &mut message_key)?;
let mut ck_input = self.chain_key;
let mut new_ck = [0u8; 32];
hkdf_derive(&ck_input, chaos_perturbation, b"qfkh-hop-v1", &mut new_ck)?;
ck_input.zeroize();
self.chain_key.zeroize();
self.chain_key = new_ck;
self.hop_count += 1;
Ok(EphemeralKey {
shared_secret: message_key,
chain_key: self.chain_key,
created_ms: now_ms,
expiry_ms: self.interval_ms,
})
}
pub fn encrypt(
&mut self,
payload: &[u8],
chaos_perturbation: &[u8; 32],
now_ms: u64,
) -> Result<Vec<u8>, PrivacyError> {
let ek = self.hop(chaos_perturbation, now_ms)?;
aes_gcm_encrypt(&ek.shared_secret, payload)
}
pub fn decrypt(
&mut self,
ciphertext: &[u8],
chaos_perturbation: &[u8; 32],
now_ms: u64,
) -> Result<Vec<u8>, PrivacyError> {
let ek = self.hop(chaos_perturbation, now_ms)?;
aes_gcm_decrypt(&ek.shared_secret, ciphertext)
}
pub fn hop_count(&self) -> u64 {
self.hop_count
}
}
impl Drop for QfkhRatchet {
fn drop(&mut self) {
self.chain_key.zeroize();
}
}
struct DeterministicRng {
state: [u8; 32],
counter: u64,
}
impl rand_core::RngCore for DeterministicRng {
fn next_u32(&mut self) -> u32 {
let mut buf = [0u8; 4];
self.fill_bytes(&mut buf);
u32::from_le_bytes(buf)
}
fn next_u64(&mut self) -> u64 {
let mut buf = [0u8; 8];
self.fill_bytes(&mut buf);
u64::from_le_bytes(buf)
}
fn fill_bytes(&mut self, dest: &mut [u8]) {
let mut pos = 0;
while pos < dest.len() {
let mut hasher = Sha256::new();
hasher.update(self.state);
hasher.update(self.counter.to_le_bytes());
let hash = hasher.finalize();
let copy_len = (dest.len() - pos).min(32);
dest[pos..pos + copy_len].copy_from_slice(&hash[..copy_len]);
pos += copy_len;
self.counter += 1;
}
}
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core::Error> {
self.fill_bytes(dest);
Ok(())
}
}
impl rand_core::CryptoRng for DeterministicRng {}
pub(crate) fn hkdf_derive(
ikm: &[u8],
salt: &[u8],
info: &[u8],
output: &mut [u8],
) -> Result<(), PrivacyError> {
let hk = Hkdf::<Sha256>::new(Some(salt), ikm);
hk.expand(info, output)
.map_err(|_| PrivacyError::Internal("HKDF expand failed".into()))
}
pub(crate) fn aes_gcm_encrypt(
message_key: &[u8; 32],
plaintext: &[u8],
) -> Result<Vec<u8>, PrivacyError> {
let mut aes_key_bytes = [0u8; 32];
hkdf_derive(message_key, b"aes-gcm-key", b"aes-key-v1", &mut aes_key_bytes)?;
let nonce_hash = Sha256::digest(message_key);
let nonce_bytes: [u8; 12] = nonce_hash[..12].try_into()
.map_err(|_| PrivacyError::Internal("Nonce slice error".into()))?;
let key = Key::<Aes256Gcm>::from_slice(&aes_key_bytes);
let nonce = Nonce::from_slice(&nonce_bytes);
let cipher = Aes256Gcm::new(key);
let mut buf: Vec<u8> = plaintext.to_vec();
buf.reserve(TAG_LEN);
cipher.encrypt_in_place(nonce, b"", &mut buf)
.map_err(|_| PrivacyError::Internal("AES-GCM encrypt failed".into()))?;
let mut out = Vec::with_capacity(12 + buf.len());
out.extend_from_slice(&nonce_bytes);
out.extend_from_slice(&buf);
Ok(out)
}
pub(crate) fn aes_gcm_decrypt(
message_key: &[u8; 32],
data: &[u8],
) -> Result<Vec<u8>, PrivacyError> {
if data.len() < 12 + TAG_LEN {
return Err(PrivacyError::Internal("Ciphertext too short".into()));
}
let mut aes_key_bytes = [0u8; 32];
hkdf_derive(message_key, b"aes-gcm-key", b"aes-key-v1", &mut aes_key_bytes)?;
let nonce_bytes = &data[..12];
let ct_and_tag = &data[12..];
let key = Key::<Aes256Gcm>::from_slice(&aes_key_bytes);
let nonce = Nonce::from_slice(nonce_bytes);
let cipher = Aes256Gcm::new(key);
let mut buf: Vec<u8> = ct_and_tag.to_vec();
cipher.decrypt_in_place(nonce, b"", &mut buf)
.map_err(|_| PrivacyError::Internal("AES-GCM decrypt failed".into()))?;
Ok(buf)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_hop_produces_key() {
let secret = [1u8; 32];
let chaos = [2u8; 32];
let mut ratchet = QfkhRatchet::new(secret, &chaos, 0);
let key = ratchet.hop(&chaos, 0).unwrap();
assert_eq!(key.shared_secret.len(), 32);
assert_eq!(key.expiry_ms, HOP_INTERVAL_MS);
}
#[test]
fn test_successive_hops_differ() {
let secret = [3u8; 32];
let chaos = [4u8; 32];
let mut ratchet = QfkhRatchet::new(secret, &chaos, 0);
let k1 = ratchet.hop(&chaos, 0).unwrap();
let k2 = ratchet.hop(&chaos, 0).unwrap();
assert_ne!(k1.shared_secret, k2.shared_secret);
}
#[test]
fn test_encrypt_decrypt() {
let secret = [5u8; 32];
let chaos = [6u8; 32];
let mut enc_ratchet = QfkhRatchet::new(secret, &chaos, 0);
let mut dec_ratchet = QfkhRatchet::new(secret, &chaos, 0);
let payload = b"secret message";
let encrypted = enc_ratchet.encrypt(payload, &chaos, 0).unwrap();
let decrypted = dec_ratchet.decrypt(&encrypted, &chaos, 0).unwrap();
assert_eq!(decrypted, payload);
}
#[test]
fn test_aes_gcm_roundtrip() {
let key = [0xabu8; 32];
let plaintext = b"hello quantum world";
let ct = aes_gcm_encrypt(&key, plaintext).unwrap();
let pt = aes_gcm_decrypt(&key, &ct).unwrap();
assert_eq!(pt, plaintext);
}
#[test]
fn test_initiate_respond_complete() {
let chaos = [7u8; 32];
let (dk_bytes, ek_bytes) = QfkhRatchet::initiate(&chaos).unwrap();
assert_eq!(ek_bytes.len(), 1184); assert_eq!(dk_bytes.len(), 64);
let (mut responder_ratchet, ct_bytes) = QfkhRatchet::respond(&ek_bytes, &chaos, 0).unwrap();
let mut initiator_ratchet = QfkhRatchet::complete(&dk_bytes, &ct_bytes, &chaos, 0).unwrap();
let k_init = initiator_ratchet.hop(&chaos, 0).unwrap();
let k_resp = responder_ratchet.hop(&chaos, 0).unwrap();
assert_eq!(k_init.shared_secret, k_resp.shared_secret);
}
}