1use chacha20poly1305::{
4 aead::{Aead, KeyInit},
5 XChaCha20Poly1305, XNonce,
6};
7use cp_core::{CPError, CognitiveDiff, Result};
8use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey};
9use rand::RngCore;
10
11use crate::{deserialize_diff, serialize_diff, EncryptedPayload};
12
13pub struct CryptoEngine {
15 symmetric_key: [u8; 32],
17 signing_key: SigningKey,
19}
20
21impl CryptoEngine {
22 pub fn new() -> Self {
24 let mut rng = rand::thread_rng();
25
26 let mut symmetric_key = [0u8; 32];
27 rng.fill_bytes(&mut symmetric_key);
28
29 let mut signing_seed = [0u8; 32];
30 rng.fill_bytes(&mut signing_seed);
31 let signing_key = SigningKey::from_bytes(&signing_seed);
32
33 Self {
34 symmetric_key,
35 signing_key,
36 }
37 }
38
39 pub fn from_keys(symmetric_key: [u8; 32], signing_key_bytes: [u8; 32]) -> Self {
41 Self {
42 symmetric_key,
43 signing_key: SigningKey::from_bytes(&signing_key_bytes),
44 }
45 }
46
47 pub fn new_with_seed(seed: [u8; 32]) -> Self {
49 let mut hasher = blake3::Hasher::new();
54 hasher.update(&seed);
55 hasher.update(b"enc");
56 let symmetric_key = *hasher.finalize().as_bytes();
57
58 let mut hasher = blake3::Hasher::new();
59 hasher.update(&seed);
60 hasher.update(b"sign");
61 let signing_seed = *hasher.finalize().as_bytes();
62
63 Self {
64 symmetric_key,
65 signing_key: SigningKey::from_bytes(&signing_seed),
66 }
67 }
68
69 pub fn public_key(&self) -> [u8; 32] {
71 self.signing_key.verifying_key().to_bytes()
72 }
73
74 pub fn encrypt_diff(&self, diff: &CognitiveDiff) -> Result<EncryptedPayload> {
76 let plaintext = serialize_diff(diff)?;
78
79 let mut nonce_bytes = [0u8; 24];
81 rand::thread_rng().fill_bytes(&mut nonce_bytes);
82 let nonce = XNonce::from_slice(&nonce_bytes);
83
84 let cipher = XChaCha20Poly1305::new_from_slice(&self.symmetric_key)
86 .map_err(|e| CPError::Crypto(e.to_string()))?;
87
88 let ciphertext = cipher
89 .encrypt(nonce, plaintext.as_ref())
90 .map_err(|e| CPError::Crypto(e.to_string()))?;
91
92 let signature = self.signing_key.sign(&ciphertext);
94
95 Ok(EncryptedPayload {
96 ciphertext,
97 nonce: nonce_bytes,
98 signature: signature.to_bytes(),
99 public_key: self.public_key(),
100 })
101 }
102
103 pub fn sign(&self, data: &[u8]) -> Signature {
105 self.signing_key.sign(data)
106 }
107
108 pub fn decrypt_diff(&self, payload: &EncryptedPayload) -> Result<CognitiveDiff> {
110 let verifying_key = VerifyingKey::from_bytes(&payload.public_key)
112 .map_err(|e| CPError::Crypto(e.to_string()))?;
113
114 let signature = Signature::from_bytes(&payload.signature);
115
116 verifying_key
117 .verify(&payload.ciphertext, &signature)
118 .map_err(|_| CPError::Verification("Invalid signature".into()))?;
119
120 let nonce = XNonce::from_slice(&payload.nonce);
122 let cipher = XChaCha20Poly1305::new_from_slice(&self.symmetric_key)
123 .map_err(|e| CPError::Crypto(e.to_string()))?;
124
125 let plaintext = cipher
126 .decrypt(nonce, payload.ciphertext.as_ref())
127 .map_err(|_| CPError::Crypto("Decryption failed".into()))?;
128
129 deserialize_diff(&plaintext)
131 }
132}
133
134impl Default for CryptoEngine {
135 fn default() -> Self {
136 Self::new()
137 }
138}
139
140#[cfg(test)]
141mod tests {
142 use super::*;
143 use cp_core::{CognitiveDiff, Document, Hlc};
144 use ed25519_dalek::{Signature, Verifier, VerifyingKey};
145 use uuid::Uuid;
146
147 const TEST_SEED: [u8; 32] = [
150 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
151 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d,
152 0x1e, 0x1f,
153 ];
154
155 const TEST_MESSAGE: &[u8] = b"Canon Protocol v1.0 - Test Message";
156
157 #[test]
158 fn test_crypto_generate_keypair() {
159 let engine = CryptoEngine::new();
160
161 let public_key = engine.public_key();
163 assert_eq!(public_key.len(), 32);
164
165 let engine2 = CryptoEngine::new();
167 assert_ne!(engine.public_key(), engine2.public_key());
168 }
169
170 #[test]
171 fn test_crypto_sign() {
172 let engine = CryptoEngine::new();
173 let message = b"Test message for signing";
174
175 let signature = engine.sign(message);
176
177 assert_eq!(signature.to_bytes().len(), 64);
179 }
180
181 #[test]
182 fn test_crypto_verify_valid() {
183 let engine = CryptoEngine::new();
184 let message = b"Test message for verification";
185
186 let signature = engine.sign(message);
187 let public_key = engine.public_key();
188
189 let verifying_key = VerifyingKey::from_bytes(&public_key).unwrap();
191 let sig = Signature::from_bytes(&signature.to_bytes());
192
193 assert!(verifying_key.verify(message, &sig).is_ok());
194 }
195
196 #[test]
197 fn test_crypto_verify_invalid() {
198 let engine = CryptoEngine::new();
199 let message = b"Original message";
200
201 let signature = engine.sign(message);
202
203 let public_key = engine.public_key();
205 let verifying_key = VerifyingKey::from_bytes(&public_key).unwrap();
206 let sig = Signature::from_bytes(&signature.to_bytes());
207
208 let wrong_message = b"Different message";
209 assert!(verifying_key.verify(wrong_message, &sig).is_err());
210 }
211
212 #[test]
213 fn test_crypto_encrypt_xchacha20() {
214 let engine = CryptoEngine::new();
215 let diff = CognitiveDiff::empty([0u8; 32], Uuid::new_v4(), 0, Hlc::new(1000, [0u8; 16]));
216
217 let encrypted = engine.encrypt_diff(&diff).unwrap();
218
219 assert_eq!(encrypted.nonce.len(), 24);
221 assert_eq!(encrypted.signature.len(), 64);
222 assert_eq!(encrypted.public_key.len(), 32);
223 assert!(!encrypted.ciphertext.is_empty());
225 }
226
227 #[test]
228 fn test_crypto_decrypt_xchacha20() {
229 let engine = CryptoEngine::new();
230 let diff = CognitiveDiff::empty([0u8; 32], Uuid::new_v4(), 0, Hlc::new(1000, [0u8; 16]));
231
232 let encrypted = engine.encrypt_diff(&diff).unwrap();
233 let decrypted = engine.decrypt_diff(&encrypted).unwrap();
234
235 assert_eq!(diff.metadata.device_id, decrypted.metadata.device_id);
237 }
238
239 #[test]
240 fn test_crypto_encrypt_decrypt_roundtrip() {
241 let engine = CryptoEngine::new();
242 let diff = CognitiveDiff::empty([0u8; 32], Uuid::new_v4(), 0, Hlc::new(1000, [0u8; 16]));
243
244 let encrypted = engine.encrypt_diff(&diff).unwrap();
245 let decrypted = engine.decrypt_diff(&encrypted).unwrap();
246
247 assert_eq!(diff.metadata.device_id, decrypted.metadata.device_id);
248 }
249
250 #[test]
251 fn test_crypto_nonce_uniqueness() {
252 let engine = CryptoEngine::new();
253 let diff = CognitiveDiff::empty([0u8; 32], Uuid::new_v4(), 0, Hlc::new(1000, [0u8; 16]));
254
255 let encrypted1 = engine.encrypt_diff(&diff).unwrap();
257 let encrypted2 = engine.encrypt_diff(&diff).unwrap();
258 let encrypted3 = engine.encrypt_diff(&diff).unwrap();
259
260 assert_ne!(encrypted1.nonce, encrypted2.nonce);
262 assert_ne!(encrypted2.nonce, encrypted3.nonce);
263 assert_ne!(encrypted1.nonce, encrypted3.nonce);
264 }
265
266 #[test]
267 fn test_crypto_key_derivation_hkdf() {
268 let engine1 = CryptoEngine::new_with_seed(TEST_SEED);
270 let engine2 = CryptoEngine::new_with_seed(TEST_SEED);
271
272 assert_eq!(engine1.public_key(), engine2.public_key());
274
275 let mut different_seed = TEST_SEED;
277 different_seed[0] ^= 0xFF;
278 let engine3 = CryptoEngine::new_with_seed(different_seed);
279
280 assert_ne!(engine1.public_key(), engine3.public_key());
281 }
282
283 #[test]
284 fn test_crypto_test_vector_encryption() {
285 let symmetric_key = [0x42u8; 32];
287 let signing_key_bytes = [0x24u8; 32];
288 let engine = CryptoEngine::from_keys(symmetric_key, signing_key_bytes);
289
290 let diff = CognitiveDiff::empty([0u8; 32], Uuid::nil(), 0, Hlc::new(0, [0u8; 16]));
291
292 let encrypted = engine.encrypt_diff(&diff).unwrap();
294 let decrypted = engine.decrypt_diff(&encrypted).unwrap();
295
296 assert_eq!(decrypted.metadata.device_id, Uuid::nil());
297 }
298
299 #[test]
300 fn test_crypto_test_vector_signature() {
301 let engine = CryptoEngine::from_keys([0x42u8; 32], [0x24u8; 32]);
303
304 let signature = engine.sign(TEST_MESSAGE);
305
306 let public_key = engine.public_key();
308 let verifying_key = VerifyingKey::from_bytes(&public_key).unwrap();
309 let sig = Signature::from_bytes(&signature.to_bytes());
310
311 assert!(verifying_key.verify(TEST_MESSAGE, &sig).is_ok());
312 }
313
314 #[test]
315 fn test_crypto_tampering_detection() {
316 let engine = CryptoEngine::new();
317 let diff = CognitiveDiff::empty([0u8; 32], Uuid::new_v4(), 0, Hlc::new(1000, [0u8; 16]));
318
319 let mut encrypted = engine.encrypt_diff(&diff).unwrap();
320
321 encrypted.ciphertext[0] ^= 0xFF;
323
324 assert!(engine.decrypt_diff(&encrypted).is_err());
326 }
327
328 #[test]
329 fn test_crypto_wrong_key_rejected() {
330 let engine1 = CryptoEngine::new();
331 let engine2 = CryptoEngine::new();
332
333 let diff = CognitiveDiff::empty([0u8; 32], Uuid::new_v4(), 0, Hlc::new(1000, [0u8; 16]));
334
335 let encrypted = engine1.encrypt_diff(&diff).unwrap();
337
338 assert!(engine2.decrypt_diff(&encrypted).is_err());
340 }
341
342 #[test]
343 fn test_crypto_signature_verification() {
344 let engine = CryptoEngine::new();
345 let diff = CognitiveDiff::empty([0u8; 32], Uuid::new_v4(), 0, Hlc::new(1000, [0u8; 16]));
346
347 let mut encrypted = engine.encrypt_diff(&diff).unwrap();
348
349 encrypted.signature[0] ^= 0xFF;
351
352 assert!(engine.decrypt_diff(&encrypted).is_err());
353 }
354
355 #[test]
356 fn test_crypto_from_keys_deterministic() {
357 let symmetric_key = [0xAAu8; 32];
359 let signing_key_bytes = [0x55u8; 32];
360
361 let engine1 = CryptoEngine::from_keys(symmetric_key, signing_key_bytes);
362 let engine2 = CryptoEngine::from_keys(symmetric_key, signing_key_bytes);
363
364 assert_eq!(engine1.public_key(), engine2.public_key());
365 }
366
367 #[test]
368 fn test_crypto_large_diff_encryption() {
369 let engine = CryptoEngine::new();
370
371 let mut diff =
373 CognitiveDiff::empty([0u8; 32], Uuid::new_v4(), 0, Hlc::new(1000, [0u8; 16]));
374
375 for i in 0..10 {
377 let doc = Document::new(
378 std::path::PathBuf::from(format!("test{i}.md")),
379 b"Test content for encryption",
380 0,
381 );
382 diff.added_docs.push(doc);
383 }
384
385 let encrypted = engine.encrypt_diff(&diff).unwrap();
386 let decrypted = engine.decrypt_diff(&encrypted).unwrap();
387
388 assert_eq!(diff.added_docs.len(), decrypted.added_docs.len());
389 }
390}