use chacha20poly1305::{
aead::{Aead, KeyInit},
XChaCha20Poly1305, XNonce,
};
use cp_core::{CPError, CognitiveDiff, Result};
use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey};
use rand::RngCore;
use crate::{deserialize_diff, serialize_diff, EncryptedPayload};
pub struct CryptoEngine {
symmetric_key: [u8; 32],
signing_key: SigningKey,
}
impl CryptoEngine {
pub fn new() -> Self {
let mut rng = rand::thread_rng();
let mut symmetric_key = [0u8; 32];
rng.fill_bytes(&mut symmetric_key);
let mut signing_seed = [0u8; 32];
rng.fill_bytes(&mut signing_seed);
let signing_key = SigningKey::from_bytes(&signing_seed);
Self {
symmetric_key,
signing_key,
}
}
pub fn from_keys(symmetric_key: [u8; 32], signing_key_bytes: [u8; 32]) -> Self {
Self {
symmetric_key,
signing_key: SigningKey::from_bytes(&signing_key_bytes),
}
}
pub fn new_with_seed(seed: [u8; 32]) -> Self {
let mut hasher = blake3::Hasher::new();
hasher.update(&seed);
hasher.update(b"enc");
let symmetric_key = *hasher.finalize().as_bytes();
let mut hasher = blake3::Hasher::new();
hasher.update(&seed);
hasher.update(b"sign");
let signing_seed = *hasher.finalize().as_bytes();
Self {
symmetric_key,
signing_key: SigningKey::from_bytes(&signing_seed),
}
}
pub fn public_key(&self) -> [u8; 32] {
self.signing_key.verifying_key().to_bytes()
}
pub fn encrypt_diff(&self, diff: &CognitiveDiff) -> Result<EncryptedPayload> {
let plaintext = serialize_diff(diff)?;
let mut nonce_bytes = [0u8; 24];
rand::thread_rng().fill_bytes(&mut nonce_bytes);
let nonce = XNonce::from_slice(&nonce_bytes);
let cipher = XChaCha20Poly1305::new_from_slice(&self.symmetric_key)
.map_err(|e| CPError::Crypto(e.to_string()))?;
let ciphertext = cipher
.encrypt(nonce, plaintext.as_ref())
.map_err(|e| CPError::Crypto(e.to_string()))?;
let signature = self.signing_key.sign(&ciphertext);
Ok(EncryptedPayload {
ciphertext,
nonce: nonce_bytes,
signature: signature.to_bytes(),
public_key: self.public_key(),
})
}
pub fn sign(&self, data: &[u8]) -> Signature {
self.signing_key.sign(data)
}
pub fn decrypt_diff(&self, payload: &EncryptedPayload) -> Result<CognitiveDiff> {
let verifying_key = VerifyingKey::from_bytes(&payload.public_key)
.map_err(|e| CPError::Crypto(e.to_string()))?;
let signature = Signature::from_bytes(&payload.signature);
verifying_key
.verify(&payload.ciphertext, &signature)
.map_err(|_| CPError::Verification("Invalid signature".into()))?;
let nonce = XNonce::from_slice(&payload.nonce);
let cipher = XChaCha20Poly1305::new_from_slice(&self.symmetric_key)
.map_err(|e| CPError::Crypto(e.to_string()))?;
let plaintext = cipher
.decrypt(nonce, payload.ciphertext.as_ref())
.map_err(|_| CPError::Crypto("Decryption failed".into()))?;
deserialize_diff(&plaintext)
}
}
impl Default for CryptoEngine {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use cp_core::{CognitiveDiff, Document, Hlc};
use ed25519_dalek::{Signature, Verifier, VerifyingKey};
use uuid::Uuid;
const TEST_SEED: [u8; 32] = [
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d,
0x1e, 0x1f,
];
const TEST_MESSAGE: &[u8] = b"Canon Protocol v1.0 - Test Message";
#[test]
fn test_crypto_generate_keypair() {
let engine = CryptoEngine::new();
let public_key = engine.public_key();
assert_eq!(public_key.len(), 32);
let engine2 = CryptoEngine::new();
assert_ne!(engine.public_key(), engine2.public_key());
}
#[test]
fn test_crypto_sign() {
let engine = CryptoEngine::new();
let message = b"Test message for signing";
let signature = engine.sign(message);
assert_eq!(signature.to_bytes().len(), 64);
}
#[test]
fn test_crypto_verify_valid() {
let engine = CryptoEngine::new();
let message = b"Test message for verification";
let signature = engine.sign(message);
let public_key = engine.public_key();
let verifying_key = VerifyingKey::from_bytes(&public_key).unwrap();
let sig = Signature::from_bytes(&signature.to_bytes());
assert!(verifying_key.verify(message, &sig).is_ok());
}
#[test]
fn test_crypto_verify_invalid() {
let engine = CryptoEngine::new();
let message = b"Original message";
let signature = engine.sign(message);
let public_key = engine.public_key();
let verifying_key = VerifyingKey::from_bytes(&public_key).unwrap();
let sig = Signature::from_bytes(&signature.to_bytes());
let wrong_message = b"Different message";
assert!(verifying_key.verify(wrong_message, &sig).is_err());
}
#[test]
fn test_crypto_encrypt_xchacha20() {
let engine = CryptoEngine::new();
let diff = CognitiveDiff::empty([0u8; 32], Uuid::new_v4(), 0, Hlc::new(1000, [0u8; 16]));
let encrypted = engine.encrypt_diff(&diff).unwrap();
assert_eq!(encrypted.nonce.len(), 24);
assert_eq!(encrypted.signature.len(), 64);
assert_eq!(encrypted.public_key.len(), 32);
assert!(!encrypted.ciphertext.is_empty());
}
#[test]
fn test_crypto_decrypt_xchacha20() {
let engine = CryptoEngine::new();
let diff = CognitiveDiff::empty([0u8; 32], Uuid::new_v4(), 0, Hlc::new(1000, [0u8; 16]));
let encrypted = engine.encrypt_diff(&diff).unwrap();
let decrypted = engine.decrypt_diff(&encrypted).unwrap();
assert_eq!(diff.metadata.device_id, decrypted.metadata.device_id);
}
#[test]
fn test_crypto_encrypt_decrypt_roundtrip() {
let engine = CryptoEngine::new();
let diff = CognitiveDiff::empty([0u8; 32], Uuid::new_v4(), 0, Hlc::new(1000, [0u8; 16]));
let encrypted = engine.encrypt_diff(&diff).unwrap();
let decrypted = engine.decrypt_diff(&encrypted).unwrap();
assert_eq!(diff.metadata.device_id, decrypted.metadata.device_id);
}
#[test]
fn test_crypto_nonce_uniqueness() {
let engine = CryptoEngine::new();
let diff = CognitiveDiff::empty([0u8; 32], Uuid::new_v4(), 0, Hlc::new(1000, [0u8; 16]));
let encrypted1 = engine.encrypt_diff(&diff).unwrap();
let encrypted2 = engine.encrypt_diff(&diff).unwrap();
let encrypted3 = engine.encrypt_diff(&diff).unwrap();
assert_ne!(encrypted1.nonce, encrypted2.nonce);
assert_ne!(encrypted2.nonce, encrypted3.nonce);
assert_ne!(encrypted1.nonce, encrypted3.nonce);
}
#[test]
fn test_crypto_key_derivation_hkdf() {
let engine1 = CryptoEngine::new_with_seed(TEST_SEED);
let engine2 = CryptoEngine::new_with_seed(TEST_SEED);
assert_eq!(engine1.public_key(), engine2.public_key());
let mut different_seed = TEST_SEED;
different_seed[0] ^= 0xFF;
let engine3 = CryptoEngine::new_with_seed(different_seed);
assert_ne!(engine1.public_key(), engine3.public_key());
}
#[test]
fn test_crypto_test_vector_encryption() {
let symmetric_key = [0x42u8; 32];
let signing_key_bytes = [0x24u8; 32];
let engine = CryptoEngine::from_keys(symmetric_key, signing_key_bytes);
let diff = CognitiveDiff::empty([0u8; 32], Uuid::nil(), 0, Hlc::new(0, [0u8; 16]));
let encrypted = engine.encrypt_diff(&diff).unwrap();
let decrypted = engine.decrypt_diff(&encrypted).unwrap();
assert_eq!(decrypted.metadata.device_id, Uuid::nil());
}
#[test]
fn test_crypto_test_vector_signature() {
let engine = CryptoEngine::from_keys([0x42u8; 32], [0x24u8; 32]);
let signature = engine.sign(TEST_MESSAGE);
let public_key = engine.public_key();
let verifying_key = VerifyingKey::from_bytes(&public_key).unwrap();
let sig = Signature::from_bytes(&signature.to_bytes());
assert!(verifying_key.verify(TEST_MESSAGE, &sig).is_ok());
}
#[test]
fn test_crypto_tampering_detection() {
let engine = CryptoEngine::new();
let diff = CognitiveDiff::empty([0u8; 32], Uuid::new_v4(), 0, Hlc::new(1000, [0u8; 16]));
let mut encrypted = engine.encrypt_diff(&diff).unwrap();
encrypted.ciphertext[0] ^= 0xFF;
assert!(engine.decrypt_diff(&encrypted).is_err());
}
#[test]
fn test_crypto_wrong_key_rejected() {
let engine1 = CryptoEngine::new();
let engine2 = CryptoEngine::new();
let diff = CognitiveDiff::empty([0u8; 32], Uuid::new_v4(), 0, Hlc::new(1000, [0u8; 16]));
let encrypted = engine1.encrypt_diff(&diff).unwrap();
assert!(engine2.decrypt_diff(&encrypted).is_err());
}
#[test]
fn test_crypto_signature_verification() {
let engine = CryptoEngine::new();
let diff = CognitiveDiff::empty([0u8; 32], Uuid::new_v4(), 0, Hlc::new(1000, [0u8; 16]));
let mut encrypted = engine.encrypt_diff(&diff).unwrap();
encrypted.signature[0] ^= 0xFF;
assert!(engine.decrypt_diff(&encrypted).is_err());
}
#[test]
fn test_crypto_from_keys_deterministic() {
let symmetric_key = [0xAAu8; 32];
let signing_key_bytes = [0x55u8; 32];
let engine1 = CryptoEngine::from_keys(symmetric_key, signing_key_bytes);
let engine2 = CryptoEngine::from_keys(symmetric_key, signing_key_bytes);
assert_eq!(engine1.public_key(), engine2.public_key());
}
#[test]
fn test_crypto_large_diff_encryption() {
let engine = CryptoEngine::new();
let mut diff =
CognitiveDiff::empty([0u8; 32], Uuid::new_v4(), 0, Hlc::new(1000, [0u8; 16]));
for i in 0..10 {
let doc = Document::new(
std::path::PathBuf::from(format!("test{i}.md")),
b"Test content for encryption",
0,
);
diff.added_docs.push(doc);
}
let encrypted = engine.encrypt_diff(&diff).unwrap();
let decrypted = engine.decrypt_diff(&encrypted).unwrap();
assert_eq!(diff.added_docs.len(), decrypted.added_docs.len());
}
}