//! Encrypted message history: backup and restore. //! //! History key derived from seed via HKDF (info="warzone-history"). //! Format: MAGIC(4) + nonce(12) + ciphertext (ChaCha20-Poly1305). use crate::crypto::{aead_decrypt, aead_encrypt, hkdf_derive}; use crate::errors::ProtocolError; const HISTORY_MAGIC: &[u8; 4] = b"WZH1"; /// Derive history encryption key from seed. pub fn derive_history_key(seed: &[u8; 32]) -> [u8; 32] { let derived = hkdf_derive(seed, b"", b"warzone-history", 32); let mut key = [0u8; 32]; key.copy_from_slice(&derived); key } /// Encrypt a history blob (JSON messages serialized to bytes). pub fn encrypt_history(seed: &[u8; 32], plaintext: &[u8]) -> Vec { let key = derive_history_key(seed); let encrypted = aead_encrypt(&key, plaintext, HISTORY_MAGIC); let mut result = Vec::with_capacity(4 + encrypted.len()); result.extend_from_slice(HISTORY_MAGIC); result.extend_from_slice(&encrypted); result } /// Decrypt a history blob. pub fn decrypt_history(seed: &[u8; 32], data: &[u8]) -> Result, ProtocolError> { if data.len() < 4 || &data[..4] != HISTORY_MAGIC { return Err(ProtocolError::DecryptionFailed); } let key = derive_history_key(seed); aead_decrypt(&key, &data[4..], HISTORY_MAGIC) } #[cfg(test)] mod tests { use super::*; #[test] fn roundtrip() { let seed = [42u8; 32]; let messages = b"[{\"from\":\"alice\",\"text\":\"hello\"}]"; let encrypted = encrypt_history(&seed, messages); let decrypted = decrypt_history(&seed, &encrypted).unwrap(); assert_eq!(decrypted, messages); } #[test] fn wrong_seed_fails() { let seed = [42u8; 32]; let wrong = [99u8; 32]; let encrypted = encrypt_history(&seed, b"secret"); assert!(decrypt_history(&wrong, &encrypted).is_err()); } }