1use cp_core::{CPError, CognitiveDiff, Result};
4use chacha20poly1305::{
5 aead::{Aead, KeyInit},
6 XChaCha20Poly1305, XNonce,
7};
8use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey};
9use rand::RngCore;
10
11use crate::{serialize_diff, deserialize_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 uuid::Uuid;
145 use ed25519_dalek::{Signature, Verifier, VerifyingKey};
146
147 const TEST_SEED: [u8; 32] = [
150 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
151 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
152 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
153 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
154 ];
155
156 const TEST_MESSAGE: &[u8] = b"Canon Protocol v1.0 - Test Message";
157
158 #[test]
159 fn test_crypto_generate_keypair() {
160 let engine = CryptoEngine::new();
161
162 let public_key = engine.public_key();
164 assert_eq!(public_key.len(), 32);
165
166 let engine2 = CryptoEngine::new();
168 assert_ne!(engine.public_key(), engine2.public_key());
169 }
170
171 #[test]
172 fn test_crypto_sign() {
173 let engine = CryptoEngine::new();
174 let message = b"Test message for signing";
175
176 let signature = engine.sign(message);
177
178 assert_eq!(signature.to_bytes().len(), 64);
180 }
181
182 #[test]
183 fn test_crypto_verify_valid() {
184 let engine = CryptoEngine::new();
185 let message = b"Test message for verification";
186
187 let signature = engine.sign(message);
188 let public_key = engine.public_key();
189
190 let verifying_key = VerifyingKey::from_bytes(&public_key).unwrap();
192 let sig = Signature::from_bytes(&signature.to_bytes());
193
194 assert!(verifying_key.verify(message, &sig).is_ok());
195 }
196
197 #[test]
198 fn test_crypto_verify_invalid() {
199 let engine = CryptoEngine::new();
200 let message = b"Original message";
201
202 let signature = engine.sign(message);
203
204 let public_key = engine.public_key();
206 let verifying_key = VerifyingKey::from_bytes(&public_key).unwrap();
207 let sig = Signature::from_bytes(&signature.to_bytes());
208
209 let wrong_message = b"Different message";
210 assert!(verifying_key.verify(wrong_message, &sig).is_err());
211 }
212
213 #[test]
214 fn test_crypto_encrypt_xchacha20() {
215 let engine = CryptoEngine::new();
216 let diff = CognitiveDiff::empty(
217 [0u8; 32],
218 Uuid::new_v4(),
219 0,
220 Hlc::new(1000, [0u8; 16]),
221 );
222
223 let encrypted = engine.encrypt_diff(&diff).unwrap();
224
225 assert_eq!(encrypted.nonce.len(), 24);
227 assert_eq!(encrypted.signature.len(), 64);
228 assert_eq!(encrypted.public_key.len(), 32);
229 assert!(!encrypted.ciphertext.is_empty());
231 }
232
233 #[test]
234 fn test_crypto_decrypt_xchacha20() {
235 let engine = CryptoEngine::new();
236 let diff = CognitiveDiff::empty(
237 [0u8; 32],
238 Uuid::new_v4(),
239 0,
240 Hlc::new(1000, [0u8; 16]),
241 );
242
243 let encrypted = engine.encrypt_diff(&diff).unwrap();
244 let decrypted = engine.decrypt_diff(&encrypted).unwrap();
245
246 assert_eq!(diff.metadata.device_id, decrypted.metadata.device_id);
248 }
249
250 #[test]
251 fn test_crypto_encrypt_decrypt_roundtrip() {
252 let engine = CryptoEngine::new();
253 let diff = CognitiveDiff::empty([0u8; 32], Uuid::new_v4(), 0, Hlc::new(1000, [0u8; 16]));
254
255 let encrypted = engine.encrypt_diff(&diff).unwrap();
256 let decrypted = engine.decrypt_diff(&encrypted).unwrap();
257
258 assert_eq!(diff.metadata.device_id, decrypted.metadata.device_id);
259 }
260
261 #[test]
262 fn test_crypto_nonce_uniqueness() {
263 let engine = CryptoEngine::new();
264 let diff = CognitiveDiff::empty(
265 [0u8; 32],
266 Uuid::new_v4(),
267 0,
268 Hlc::new(1000, [0u8; 16]),
269 );
270
271 let encrypted1 = engine.encrypt_diff(&diff).unwrap();
273 let encrypted2 = engine.encrypt_diff(&diff).unwrap();
274 let encrypted3 = engine.encrypt_diff(&diff).unwrap();
275
276 assert_ne!(encrypted1.nonce, encrypted2.nonce);
278 assert_ne!(encrypted2.nonce, encrypted3.nonce);
279 assert_ne!(encrypted1.nonce, encrypted3.nonce);
280 }
281
282 #[test]
283 fn test_crypto_key_derivation_hkdf() {
284 let engine1 = CryptoEngine::new_with_seed(TEST_SEED);
286 let engine2 = CryptoEngine::new_with_seed(TEST_SEED);
287
288 assert_eq!(engine1.public_key(), engine2.public_key());
290
291 let mut different_seed = TEST_SEED;
293 different_seed[0] ^= 0xFF;
294 let engine3 = CryptoEngine::new_with_seed(different_seed);
295
296 assert_ne!(engine1.public_key(), engine3.public_key());
297 }
298
299 #[test]
300 fn test_crypto_test_vector_encryption() {
301 let symmetric_key = [0x42u8; 32];
303 let signing_key_bytes = [0x24u8; 32];
304 let engine = CryptoEngine::from_keys(symmetric_key, signing_key_bytes);
305
306 let diff = CognitiveDiff::empty(
307 [0u8; 32],
308 Uuid::nil(),
309 0,
310 Hlc::new(0, [0u8; 16]),
311 );
312
313 let encrypted = engine.encrypt_diff(&diff).unwrap();
315 let decrypted = engine.decrypt_diff(&encrypted).unwrap();
316
317 assert_eq!(decrypted.metadata.device_id, Uuid::nil());
318 }
319
320 #[test]
321 fn test_crypto_test_vector_signature() {
322 let engine = CryptoEngine::from_keys([0x42u8; 32], [0x24u8; 32]);
324
325 let signature = engine.sign(TEST_MESSAGE);
326
327 let public_key = engine.public_key();
329 let verifying_key = VerifyingKey::from_bytes(&public_key).unwrap();
330 let sig = Signature::from_bytes(&signature.to_bytes());
331
332 assert!(verifying_key.verify(TEST_MESSAGE, &sig).is_ok());
333 }
334
335 #[test]
336 fn test_crypto_tampering_detection() {
337 let engine = CryptoEngine::new();
338 let diff = CognitiveDiff::empty(
339 [0u8; 32],
340 Uuid::new_v4(),
341 0,
342 Hlc::new(1000, [0u8; 16]),
343 );
344
345 let mut encrypted = engine.encrypt_diff(&diff).unwrap();
346
347 encrypted.ciphertext[0] ^= 0xFF;
349
350 assert!(engine.decrypt_diff(&encrypted).is_err());
352 }
353
354 #[test]
355 fn test_crypto_wrong_key_rejected() {
356 let engine1 = CryptoEngine::new();
357 let engine2 = CryptoEngine::new();
358
359 let diff = CognitiveDiff::empty(
360 [0u8; 32],
361 Uuid::new_v4(),
362 0,
363 Hlc::new(1000, [0u8; 16]),
364 );
365
366 let encrypted = engine1.encrypt_diff(&diff).unwrap();
368
369 assert!(engine2.decrypt_diff(&encrypted).is_err());
371 }
372
373 #[test]
374 fn test_crypto_signature_verification() {
375 let engine = CryptoEngine::new();
376 let diff = CognitiveDiff::empty([0u8; 32], Uuid::new_v4(), 0, Hlc::new(1000, [0u8; 16]));
377
378 let mut encrypted = engine.encrypt_diff(&diff).unwrap();
379
380 encrypted.signature[0] ^= 0xFF;
382
383 assert!(engine.decrypt_diff(&encrypted).is_err());
384 }
385
386 #[test]
387 fn test_crypto_from_keys_deterministic() {
388 let symmetric_key = [0xAAu8; 32];
390 let signing_key_bytes = [0x55u8; 32];
391
392 let engine1 = CryptoEngine::from_keys(symmetric_key, signing_key_bytes);
393 let engine2 = CryptoEngine::from_keys(symmetric_key, signing_key_bytes);
394
395 assert_eq!(engine1.public_key(), engine2.public_key());
396 }
397
398 #[test]
399 fn test_crypto_large_diff_encryption() {
400 let engine = CryptoEngine::new();
401
402 let mut diff = CognitiveDiff::empty(
404 [0u8; 32],
405 Uuid::new_v4(),
406 0,
407 Hlc::new(1000, [0u8; 16]),
408 );
409
410 for i in 0..10 {
412 let mut doc = Document::new(
413 std::path::PathBuf::from(format!("test{}.md", i)),
414 b"Test content for encryption",
415 0,
416 );
417 diff.added_docs.push(doc);
418 }
419
420 let encrypted = engine.encrypt_diff(&diff).unwrap();
421 let decrypted = engine.decrypt_diff(&encrypted).unwrap();
422
423 assert_eq!(diff.added_docs.len(), decrypted.added_docs.len());
424 }
425}