1use cp_core::{CPError, Result};
6use ed25519_dalek::{SigningKey, VerifyingKey, Signer, Signature};
7use hkdf::Hkdf;
8use rand::RngCore;
9use sha2::Sha256;
10use x25519_dalek::{PublicKey as X25519PublicKey, StaticSecret};
11use serde::{Deserialize, Serialize};
12use serde_big_array::BigArray;
13
14#[derive(Clone)]
16pub struct DeviceIdentity {
17 pub device_id: [u8; 16],
19
20 pub public_key: [u8; 32],
22
23 signing_key: SigningKey,
25
26 x25519_secret: StaticSecret,
28
29 x25519_public: X25519PublicKey,
31}
32
33impl DeviceIdentity {
34 pub fn generate() -> Self {
36 let mut rng = rand::thread_rng();
37 let mut seed = [0u8; 32];
38 rng.fill_bytes(&mut seed);
39 Self::from_seed(seed)
40 }
41
42 pub fn from_seed(seed: [u8; 32]) -> Self {
44 let signing_key = SigningKey::from_bytes(&seed);
46 let verifying_key = signing_key.verifying_key();
47 let public_key = verifying_key.to_bytes();
48
49 let mut device_id = [0u8; 16];
51 device_id.copy_from_slice(&blake3::hash(&public_key).as_bytes()[0..16]);
52
53 let hk = Hkdf::<Sha256>::new(None, &seed);
56 let mut x25519_seed = [0u8; 32];
57 hk.expand(b"cp-x25519-key", &mut x25519_seed)
58 .expect("HKDF expand failed");
59
60 let x25519_secret = StaticSecret::from(x25519_seed);
61 let x25519_public = X25519PublicKey::from(&x25519_secret);
62
63 Self {
64 device_id,
65 public_key,
66 signing_key,
67 x25519_secret,
68 x25519_public,
69 }
70 }
71
72 pub fn sign(&self, data: &[u8]) -> [u8; 64] {
74 let signature = self.signing_key.sign(data);
75 signature.to_bytes()
76 }
77
78 pub fn x25519_public_key(&self) -> [u8; 32] {
80 self.x25519_public.to_bytes()
81 }
82
83 pub fn agree(&self, remote_x25519_public: &[u8; 32]) -> [u8; 32] {
85 let remote_key = X25519PublicKey::from(*remote_x25519_public);
86 let shared_secret = self.x25519_secret.diffie_hellman(&remote_key);
87 *shared_secret.as_bytes()
88 }
89
90 pub fn pair_with(&self, remote_public_key: &[u8; 32], remote_x25519_public: &[u8; 32]) -> Result<PairedDevice> {
92 let mut remote_device_id = [0u8; 16];
94 remote_device_id.copy_from_slice(&blake3::hash(remote_public_key).as_bytes()[0..16]);
95
96 let shared_secret = self.agree(remote_x25519_public);
98
99 let (id_a, id_b) = if self.device_id < remote_device_id {
101 (self.device_id, remote_device_id)
102 } else {
103 (remote_device_id, self.device_id)
104 };
105
106 let mut info = Vec::with_capacity(32);
107 info.extend_from_slice(&id_a);
108 info.extend_from_slice(&id_b);
109
110 let hk = Hkdf::<Sha256>::new(None, &shared_secret);
111 let mut encryption_key = [0u8; 32];
112 hk.expand(&info, &mut encryption_key)
113 .map_err(|_| CPError::Crypto("HKDF expand failed".into()))?;
114
115 Ok(PairedDevice {
116 device_id: remote_device_id,
117 public_key: *remote_public_key,
118 x25519_public_key: *remote_x25519_public,
119 encryption_key,
120 last_synced_seq: 0,
121 })
122 }
123
124 pub fn export_seed(&self) -> [u8; 32] {
126 self.signing_key.to_bytes()
127 }
128}
129
130impl std::fmt::Debug for DeviceIdentity {
131 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
132 f.debug_struct("DeviceIdentity")
133 .field("device_id", &hex::encode(self.device_id))
134 .field("public_key", &hex::encode(self.public_key))
135 .finish()
136 }
137}
138
139#[derive(Debug, Clone, Serialize, Deserialize)]
141pub struct PairedDevice {
142 pub device_id: [u8; 16],
144
145 pub public_key: [u8; 32],
147
148 pub x25519_public_key: [u8; 32],
150
151 pub encryption_key: [u8; 32],
153
154 pub last_synced_seq: u64,
156}
157
158impl PairedDevice {
159 pub fn update_last_synced(&mut self, seq: u64) {
161 self.last_synced_seq = seq;
162 }
163
164 pub fn device_id_hex(&self) -> String {
166 hex::encode(self.device_id)
167 }
168}
169
170#[derive(Debug, Clone, Serialize, Deserialize)]
172pub struct PairingRequest {
173 pub device_id: [u8; 16],
175
176 pub public_key: [u8; 32],
178
179 pub x25519_public_key: [u8; 32],
181
182 pub device_name: Option<String>,
184}
185
186impl PairingRequest {
187 pub fn from_identity(identity: &DeviceIdentity, device_name: Option<String>) -> Self {
189 Self {
190 device_id: identity.device_id,
191 public_key: identity.public_key,
192 x25519_public_key: identity.x25519_public_key(),
193 device_name,
194 }
195 }
196}
197
198#[derive(Debug, Clone, Serialize, Deserialize)]
200pub struct PairingConfirmation {
201 pub request: PairingRequest,
203
204 #[serde(with = "BigArray")]
206 pub signature: [u8; 64],
207
208 pub confirmer_public_key: [u8; 32],
210}
211
212impl PairingConfirmation {
213 pub fn create(identity: &DeviceIdentity, request: &PairingRequest) -> Self {
215 let mut data = Vec::new();
217 data.extend_from_slice(&request.device_id);
218 data.extend_from_slice(&request.public_key);
219 data.extend_from_slice(&request.x25519_public_key);
220
221 let signature = identity.sign(&data);
222
223 Self {
224 request: request.clone(),
225 signature,
226 confirmer_public_key: identity.public_key,
227 }
228 }
229
230 pub fn verify(&self) -> Result<()> {
232 let verifying_key = VerifyingKey::from_bytes(&self.confirmer_public_key)
233 .map_err(|e| CPError::Crypto(format!("Invalid public key: {}", e)))?;
234
235 let mut data = Vec::new();
236 data.extend_from_slice(&self.request.device_id);
237 data.extend_from_slice(&self.request.public_key);
238 data.extend_from_slice(&self.request.x25519_public_key);
239
240 let signature = Signature::from_bytes(&self.signature);
241
242 verifying_key
243 .verify_strict(&data, &signature)
244 .map_err(|_| CPError::Verification("Invalid pairing confirmation signature".into()))
245 }
246}
247
248#[cfg(test)]
249mod tests {
250 use super::*;
251 use ed25519_dalek::{Signature, Verifier, VerifyingKey};
252
253 #[test]
254 fn test_device_identity_generation() {
255 let id1 = DeviceIdentity::generate();
256 let id2 = DeviceIdentity::generate();
257
258 assert_ne!(id1.device_id, id2.device_id);
260 assert_ne!(id1.public_key, id2.public_key);
261 }
262
263 #[test]
264 fn test_device_identity_from_seed() {
265 let seed = [42u8; 32];
266 let id1 = DeviceIdentity::from_seed(seed);
267 let id2 = DeviceIdentity::from_seed(seed);
268
269 assert_eq!(id1.device_id, id2.device_id);
271 assert_eq!(id1.public_key, id2.public_key);
272 }
273
274 #[test]
275 fn test_device_pairing_symmetric() {
276 let alice = DeviceIdentity::generate();
277 let bob = DeviceIdentity::generate();
278
279 let alice_view_of_bob = alice
281 .pair_with(&bob.public_key, &bob.x25519_public_key())
282 .unwrap();
283
284 let bob_view_of_alice = bob
286 .pair_with(&alice.public_key, &alice.x25519_public_key())
287 .unwrap();
288
289 assert_eq!(alice_view_of_bob.encryption_key, bob_view_of_alice.encryption_key);
291 }
292
293 #[test]
294 fn test_signing_and_verification() {
295 let identity = DeviceIdentity::generate();
296 let data = b"test message";
297
298 let signature = identity.sign(data);
299
300 let verifying_key = VerifyingKey::from_bytes(&identity.public_key).unwrap();
302 let sig = Signature::from_bytes(&signature);
303 assert!(verifying_key.verify_strict(data, &sig).is_ok());
304 }
305
306 #[test]
307 fn test_pairing_request_and_confirmation() {
308 let alice = DeviceIdentity::generate();
309 let bob = DeviceIdentity::generate();
310
311 let request = PairingRequest::from_identity(&alice, Some("Alice's Phone".into()));
313
314 let confirmation = PairingConfirmation::create(&bob, &request);
316
317 assert!(confirmation.verify().is_ok());
319 }
320
321 #[test]
324 fn test_identity_generate() {
325 let identity = DeviceIdentity::generate();
326
327 assert_ne!(identity.device_id, [0u8; 16]);
329 assert_ne!(identity.public_key, [0u8; 32]);
330
331 let x25519_pub = identity.x25519_public_key();
333 assert_ne!(x25519_pub, [0u8; 32]);
334 }
335
336 #[test]
337 fn test_identity_public_key_derivation() {
338 let seed = [1u8; 32];
339 let identity = DeviceIdentity::from_seed(seed);
340
341 let verifying_key = VerifyingKey::from_bytes(&identity.public_key);
343 assert!(verifying_key.is_ok());
344
345 let derived_pubkey = verifying_key.unwrap().to_bytes();
347 assert_eq!(identity.public_key, derived_pubkey);
348 }
349
350 #[test]
351 fn test_identity_device_id_derivation() {
352 let identity = DeviceIdentity::generate();
353
354 let expected_device_id: [u8; 16] = blake3::hash(&identity.public_key).as_bytes()[0..16].try_into().unwrap();
356 assert_eq!(identity.device_id, expected_device_id);
357 }
358
359 #[test]
360 fn test_identity_serialization() {
361 use ciborium::{ser, de};
362
363 let identity = DeviceIdentity::generate();
364
365 let paired = identity.pair_with(&[2u8; 32], &[3u8; 32]).unwrap();
367
368 let mut serialized = Vec::new();
370 ciborium::ser::into_writer(&paired, &mut serialized).unwrap();
371 assert!(!serialized.is_empty());
372
373 let deserialized: PairedDevice = ciborium::de::from_reader(serialized.as_slice()).unwrap();
375 assert_eq!(paired.device_id, deserialized.device_id);
376 assert_eq!(paired.public_key, deserialized.public_key);
377 }
378
379 #[test]
380 fn test_identity_persistence() {
381 let seed = [7u8; 32];
383 let original = DeviceIdentity::from_seed(seed);
384
385 let restored = DeviceIdentity::from_seed(seed);
387
388 assert_eq!(original.device_id, restored.device_id);
389 assert_eq!(original.public_key, restored.public_key);
390 assert_eq!(original.export_seed(), restored.export_seed());
391 }
392
393 #[test]
394 fn test_identity_pairing_x25519() {
395 let alice = DeviceIdentity::generate();
396 let bob = DeviceIdentity::generate();
397
398 let alice_x25519 = alice.x25519_public_key();
400 let bob_x25519 = bob.x25519_public_key();
401
402 assert_eq!(alice_x25519.len(), 32);
404 assert_eq!(bob_x25519.len(), 32);
405
406 let shared_alice = alice.agree(&bob_x25519);
408 let shared_bob = bob.agree(&alice_x25519);
409
410 assert_eq!(shared_alice, shared_bob);
412 }
413
414 #[test]
415 fn test_identity_shared_key_derivation() {
416 let alice = DeviceIdentity::generate();
417 let bob = DeviceIdentity::generate();
418
419 let alice_paired = alice.pair_with(&bob.public_key, &bob.x25519_public_key()).unwrap();
421 let bob_paired = bob.pair_with(&alice.public_key, &alice.x25519_public_key()).unwrap();
422
423 assert_eq!(alice_paired.encryption_key, bob_paired.encryption_key);
425
426 let direct_shared = alice.agree(&bob.x25519_public_key());
428 assert_ne!(alice_paired.encryption_key, direct_shared);
429 }
430
431 #[test]
432 fn test_identity_unpairing() {
433 let alice = DeviceIdentity::generate();
434 let bob = DeviceIdentity::generate();
435
436 let paired = alice.pair_with(&bob.public_key, &bob.x25519_public_key()).unwrap();
438 assert_eq!(paired.device_id, bob.device_id);
439
440 let charlie = DeviceIdentity::generate();
443 let paired_charlie = alice.pair_with(&charlie.public_key, &charlie.x25519_public_key()).unwrap();
444
445 assert_ne!(paired.encryption_key, paired_charlie.encryption_key);
447 }
448
449 #[test]
450 fn test_identity_pairing_device_id_computation() {
451 let alice = DeviceIdentity::generate();
452 let bob = DeviceIdentity::generate();
453
454 let paired = alice.pair_with(&bob.public_key, &bob.x25519_public_key()).unwrap();
456
457 assert_eq!(paired.device_id, bob.device_id);
459
460 assert_eq!(paired.public_key, bob.public_key);
462 assert_eq!(paired.x25519_public_key, bob.x25519_public_key());
463 }
464
465 #[test]
466 fn test_identity_pairing_order_independence() {
467 let alice = DeviceIdentity::generate();
468 let bob = DeviceIdentity::generate();
469
470 let paired_ab = alice.pair_with(&bob.public_key, &bob.x25519_public_key()).unwrap();
472 let paired_ba = bob.pair_with(&alice.public_key, &alice.x25519_public_key()).unwrap();
473
474 assert_eq!(paired_ab.encryption_key, paired_ba.encryption_key);
475 }
476
477 #[test]
478 fn test_identity_sign_deterministic() {
479 let identity = DeviceIdentity::from_seed([5u8; 32]);
480 let data = b"test data";
481
482 let sig1 = identity.sign(data);
483 let sig2 = identity.sign(data);
484
485 assert_eq!(sig1, sig2);
487 }
488
489 #[test]
490 fn test_identity_sign_different_data() {
491 let identity = DeviceIdentity::generate();
492 let data1 = b"data one";
493 let data2 = b"data two";
494
495 let sig1 = identity.sign(data1);
496 let sig2 = identity.sign(data2);
497
498 assert_ne!(sig1, sig2);
500 }
501
502 #[test]
503 fn test_identity_x25519_public_key_format() {
504 let identity = DeviceIdentity::generate();
505 let pubkey = identity.x25519_public_key();
506
507 assert_eq!(pubkey.len(), 32);
509
510 assert!(pubkey[31] != 0 || pubkey.iter().all(|&b| b == 0)); }
514
515 #[test]
516 fn test_identity_agree_invalid_key() {
517 let identity = DeviceIdentity::generate();
518
519 let invalid_key = [0u8; 32];
521 let result = identity.agree(&invalid_key);
522
523 assert_eq!(result.len(), 32);
525 }
526
527 #[test]
528 fn test_pairing_request_from_identity() {
529 let identity = DeviceIdentity::generate();
530
531 let request_no_name = PairingRequest::from_identity(&identity, None);
533 assert_eq!(request_no_name.device_id, identity.device_id);
534 assert_eq!(request_no_name.public_key, identity.public_key);
535 assert_eq!(request_no_name.x25519_public_key, identity.x25519_public_key());
536 assert!(request_no_name.device_name.is_none());
537
538 let request_with_name = PairingRequest::from_identity(&identity, Some("Test Device".to_string()));
540 assert_eq!(request_with_name.device_name, Some("Test Device".to_string()));
541 }
542
543 #[test]
544 fn test_pairing_confirmation_verify_fails_wrong_key() {
545 let alice = DeviceIdentity::generate();
546 let bob = DeviceIdentity::generate();
547 let charlie = DeviceIdentity::generate();
548
549 let request = PairingRequest::from_identity(&alice, None);
551
552 let confirmation = PairingConfirmation::create(&bob, &request);
554
555 let mut modified_request = request.clone();
559 modified_request.device_name = Some("Modified".to_string());
560
561 let mut confirmation_wrong = PairingConfirmation::create(&bob, &modified_request);
562
563 assert!(confirmation.verify().is_ok());
565 }
566
567 #[test]
568 fn test_paired_device_update_last_synced() {
569 let alice = DeviceIdentity::generate();
570 let bob = DeviceIdentity::generate();
571
572 let mut paired = alice.pair_with(&bob.public_key, &bob.x25519_public_key()).unwrap();
573
574 assert_eq!(paired.last_synced_seq, 0);
576
577 paired.update_last_synced(10);
579 assert_eq!(paired.last_synced_seq, 10);
580
581 paired.update_last_synced(20);
583 assert_eq!(paired.last_synced_seq, 20);
584 }
585
586 #[test]
587 fn test_paired_device_device_id_hex() {
588 let alice = DeviceIdentity::generate();
589 let bob = DeviceIdentity::generate();
590
591 let paired = alice.pair_with(&bob.public_key, &bob.x25519_public_key()).unwrap();
592
593 let hex = paired.device_id_hex();
594 assert_eq!(hex.len(), 32);
596
597 assert_eq!(hex, hex::encode(paired.device_id));
599 }
600
601 #[test]
602 fn test_identity_export_seed() {
603 let identity = DeviceIdentity::generate();
604 let seed = identity.export_seed();
605
606 assert_eq!(seed.len(), 32);
608
609 let recreated = DeviceIdentity::from_seed(seed);
611 assert_eq!(identity.device_id, recreated.device_id);
612 assert_eq!(identity.public_key, recreated.public_key);
613 }
614
615 #[test]
616 fn test_identity_debug_format() {
617 let identity = DeviceIdentity::generate();
618 let debug_str = format!("{:?}", identity);
619
620 assert!(debug_str.contains("DeviceIdentity"));
622 }
623
624 #[test]
625 fn test_identity_pairing_with_different_seeds() {
626 let seed_a = [1u8; 32];
628 let seed_b = [2u8; 32];
629
630 let alice = DeviceIdentity::from_seed(seed_a);
631 let bob = DeviceIdentity::from_seed(seed_b);
632
633 let paired = alice.pair_with(&bob.public_key, &bob.x25519_public_key()).unwrap();
634
635 assert_ne!(paired.encryption_key, [0u8; 32]);
637 }
638
639 #[test]
640 fn test_confirmation_serialization() {
641 let alice = DeviceIdentity::generate();
642 let bob = DeviceIdentity::generate();
643
644 let request = PairingRequest::from_identity(&alice, None);
645 let confirmation = PairingConfirmation::create(&bob, &request);
646
647 let serialized = serde_json::to_vec(&confirmation).unwrap();
649 assert!(!serialized.is_empty());
650
651 let deserialized: PairingConfirmation = serde_json::from_slice(&serialized).unwrap();
653
654 assert_eq!(confirmation.request.device_id, deserialized.request.device_id);
655 assert_eq!(confirmation.signature, deserialized.signature);
656 assert_eq!(confirmation.confirmer_public_key, deserialized.confirmer_public_key);
657 }
658}