1use cp_core::{CPError, Result};
6use ed25519_dalek::{Signature, Signer, SigningKey, VerifyingKey};
7use hkdf::Hkdf;
8use rand::RngCore;
9use serde::{Deserialize, Serialize};
10use serde_big_array::BigArray;
11use sha2::Sha256;
12use x25519_dalek::{PublicKey as X25519PublicKey, StaticSecret};
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]) -> Result<[u8; 32]> {
86 let remote_key = X25519PublicKey::from(*remote_x25519_public);
87 let shared_secret = self.x25519_secret.diffie_hellman(&remote_key);
88 let bytes = *shared_secret.as_bytes();
89 if bytes == [0u8; 32] {
90 return Err(CPError::Crypto(
91 "X25519 DH produced all-zero shared secret".into(),
92 ));
93 }
94 Ok(bytes)
95 }
96
97 pub fn pair_with(
99 &self,
100 remote_public_key: &[u8; 32],
101 remote_x25519_public: &[u8; 32],
102 ) -> Result<PairedDevice> {
103 let mut remote_device_id = [0u8; 16];
105 remote_device_id.copy_from_slice(&blake3::hash(remote_public_key).as_bytes()[0..16]);
106
107 let shared_secret = self.agree(remote_x25519_public)?;
109
110 let (id_a, id_b) = if self.device_id < remote_device_id {
112 (self.device_id, remote_device_id)
113 } else {
114 (remote_device_id, self.device_id)
115 };
116
117 let mut info = Vec::with_capacity(32);
118 info.extend_from_slice(&id_a);
119 info.extend_from_slice(&id_b);
120
121 let hk = Hkdf::<Sha256>::new(None, &shared_secret);
122 let mut encryption_key = [0u8; 32];
123 hk.expand(&info, &mut encryption_key)
124 .map_err(|_| CPError::Crypto("HKDF expand failed".into()))?;
125
126 Ok(PairedDevice {
127 device_id: remote_device_id,
128 public_key: *remote_public_key,
129 x25519_public_key: *remote_x25519_public,
130 encryption_key,
131 last_synced_seq: 0,
132 })
133 }
134
135 pub fn export_seed(&self) -> [u8; 32] {
137 self.signing_key.to_bytes()
138 }
139}
140
141impl std::fmt::Debug for DeviceIdentity {
142 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
143 f.debug_struct("DeviceIdentity")
144 .field("device_id", &hex::encode(self.device_id))
145 .field("public_key", &hex::encode(self.public_key))
146 .finish()
147 }
148}
149
150#[derive(Debug, Clone, Serialize, Deserialize)]
152pub struct PairedDevice {
153 pub device_id: [u8; 16],
155
156 pub public_key: [u8; 32],
158
159 pub x25519_public_key: [u8; 32],
161
162 pub encryption_key: [u8; 32],
164
165 pub last_synced_seq: u64,
167}
168
169impl PairedDevice {
170 pub fn update_last_synced(&mut self, seq: u64) {
172 self.last_synced_seq = seq;
173 }
174
175 pub fn device_id_hex(&self) -> String {
177 hex::encode(self.device_id)
178 }
179}
180
181#[derive(Debug, Clone, Serialize, Deserialize)]
183pub struct PairingRequest {
184 pub device_id: [u8; 16],
186
187 pub public_key: [u8; 32],
189
190 pub x25519_public_key: [u8; 32],
192
193 pub device_name: Option<String>,
195}
196
197impl PairingRequest {
198 pub fn from_identity(identity: &DeviceIdentity, device_name: Option<String>) -> Self {
200 Self {
201 device_id: identity.device_id,
202 public_key: identity.public_key,
203 x25519_public_key: identity.x25519_public_key(),
204 device_name,
205 }
206 }
207}
208
209#[derive(Debug, Clone, Serialize, Deserialize)]
211pub struct PairingConfirmation {
212 pub request: PairingRequest,
214
215 #[serde(with = "BigArray")]
217 pub signature: [u8; 64],
218
219 pub confirmer_public_key: [u8; 32],
221}
222
223impl PairingConfirmation {
224 pub fn create(identity: &DeviceIdentity, request: &PairingRequest) -> Self {
226 let mut data = Vec::new();
228 data.extend_from_slice(&request.device_id);
229 data.extend_from_slice(&request.public_key);
230 data.extend_from_slice(&request.x25519_public_key);
231
232 let signature = identity.sign(&data);
233
234 Self {
235 request: request.clone(),
236 signature,
237 confirmer_public_key: identity.public_key,
238 }
239 }
240
241 pub fn verify(&self) -> Result<()> {
243 let verifying_key = VerifyingKey::from_bytes(&self.confirmer_public_key)
244 .map_err(|e| CPError::Crypto(format!("Invalid public key: {e}")))?;
245
246 let mut data = Vec::new();
247 data.extend_from_slice(&self.request.device_id);
248 data.extend_from_slice(&self.request.public_key);
249 data.extend_from_slice(&self.request.x25519_public_key);
250
251 let signature = Signature::from_bytes(&self.signature);
252
253 verifying_key
254 .verify_strict(&data, &signature)
255 .map_err(|_| CPError::Verification("Invalid pairing confirmation signature".into()))
256 }
257}
258
259#[cfg(test)]
260mod tests {
261 use super::*;
262 use ed25519_dalek::{Signature, VerifyingKey};
263
264 #[test]
265 fn test_device_identity_generation() {
266 let id1 = DeviceIdentity::generate();
267 let id2 = DeviceIdentity::generate();
268
269 assert_ne!(id1.device_id, id2.device_id);
271 assert_ne!(id1.public_key, id2.public_key);
272 }
273
274 #[test]
275 fn test_device_identity_from_seed() {
276 let seed = [42u8; 32];
277 let id1 = DeviceIdentity::from_seed(seed);
278 let id2 = DeviceIdentity::from_seed(seed);
279
280 assert_eq!(id1.device_id, id2.device_id);
282 assert_eq!(id1.public_key, id2.public_key);
283 }
284
285 #[test]
286 fn test_device_pairing_symmetric() {
287 let alice = DeviceIdentity::generate();
288 let bob = DeviceIdentity::generate();
289
290 let alice_view_of_bob = alice
292 .pair_with(&bob.public_key, &bob.x25519_public_key())
293 .unwrap();
294
295 let bob_view_of_alice = bob
297 .pair_with(&alice.public_key, &alice.x25519_public_key())
298 .unwrap();
299
300 assert_eq!(
302 alice_view_of_bob.encryption_key,
303 bob_view_of_alice.encryption_key
304 );
305 }
306
307 #[test]
308 fn test_signing_and_verification() {
309 let identity = DeviceIdentity::generate();
310 let data = b"test message";
311
312 let signature = identity.sign(data);
313
314 let verifying_key = VerifyingKey::from_bytes(&identity.public_key).unwrap();
316 let sig = Signature::from_bytes(&signature);
317 assert!(verifying_key.verify_strict(data, &sig).is_ok());
318 }
319
320 #[test]
321 fn test_pairing_request_and_confirmation() {
322 let alice = DeviceIdentity::generate();
323 let bob = DeviceIdentity::generate();
324
325 let request = PairingRequest::from_identity(&alice, Some("Alice's Phone".into()));
327
328 let confirmation = PairingConfirmation::create(&bob, &request);
330
331 assert!(confirmation.verify().is_ok());
333 }
334
335 #[test]
338 fn test_identity_generate() {
339 let identity = DeviceIdentity::generate();
340
341 assert_ne!(identity.device_id, [0u8; 16]);
343 assert_ne!(identity.public_key, [0u8; 32]);
344
345 let x25519_pub = identity.x25519_public_key();
347 assert_ne!(x25519_pub, [0u8; 32]);
348 }
349
350 #[test]
351 fn test_identity_public_key_derivation() {
352 let seed = [1u8; 32];
353 let identity = DeviceIdentity::from_seed(seed);
354
355 let verifying_key = VerifyingKey::from_bytes(&identity.public_key);
357 assert!(verifying_key.is_ok());
358
359 let derived_pubkey = verifying_key.unwrap().to_bytes();
361 assert_eq!(identity.public_key, derived_pubkey);
362 }
363
364 #[test]
365 fn test_identity_device_id_derivation() {
366 let identity = DeviceIdentity::generate();
367
368 let expected_device_id: [u8; 16] = blake3::hash(&identity.public_key).as_bytes()[0..16]
370 .try_into()
371 .unwrap();
372 assert_eq!(identity.device_id, expected_device_id);
373 }
374
375 #[test]
376 fn test_identity_serialization() {
377 let identity = DeviceIdentity::generate();
378
379 let paired = identity.pair_with(&[2u8; 32], &[3u8; 32]).unwrap();
381
382 let mut serialized = Vec::new();
384 ciborium::ser::into_writer(&paired, &mut serialized).unwrap();
385 assert!(!serialized.is_empty());
386
387 let deserialized: PairedDevice = ciborium::de::from_reader(serialized.as_slice()).unwrap();
389 assert_eq!(paired.device_id, deserialized.device_id);
390 assert_eq!(paired.public_key, deserialized.public_key);
391 }
392
393 #[test]
394 fn test_identity_persistence() {
395 let seed = [7u8; 32];
397 let original = DeviceIdentity::from_seed(seed);
398
399 let restored = DeviceIdentity::from_seed(seed);
401
402 assert_eq!(original.device_id, restored.device_id);
403 assert_eq!(original.public_key, restored.public_key);
404 assert_eq!(original.export_seed(), restored.export_seed());
405 }
406
407 #[test]
408 fn test_identity_pairing_x25519() {
409 let alice = DeviceIdentity::generate();
410 let bob = DeviceIdentity::generate();
411
412 let alice_x25519 = alice.x25519_public_key();
414 let bob_x25519 = bob.x25519_public_key();
415
416 assert_eq!(alice_x25519.len(), 32);
418 assert_eq!(bob_x25519.len(), 32);
419
420 let shared_alice = alice.agree(&bob_x25519).unwrap();
422 let shared_bob = bob.agree(&alice_x25519).unwrap();
423
424 assert_eq!(shared_alice, shared_bob);
426 }
427
428 #[test]
429 fn test_identity_shared_key_derivation() {
430 let alice = DeviceIdentity::generate();
431 let bob = DeviceIdentity::generate();
432
433 let alice_paired = alice
435 .pair_with(&bob.public_key, &bob.x25519_public_key())
436 .unwrap();
437 let bob_paired = bob
438 .pair_with(&alice.public_key, &alice.x25519_public_key())
439 .unwrap();
440
441 assert_eq!(alice_paired.encryption_key, bob_paired.encryption_key);
443
444 let direct_shared = alice.agree(&bob.x25519_public_key()).unwrap();
446 assert_ne!(alice_paired.encryption_key, direct_shared);
447 }
448
449 #[test]
450 fn test_identity_unpairing() {
451 let alice = DeviceIdentity::generate();
452 let bob = DeviceIdentity::generate();
453
454 let paired = alice
456 .pair_with(&bob.public_key, &bob.x25519_public_key())
457 .unwrap();
458 assert_eq!(paired.device_id, bob.device_id);
459
460 let charlie = DeviceIdentity::generate();
463 let paired_charlie = alice
464 .pair_with(&charlie.public_key, &charlie.x25519_public_key())
465 .unwrap();
466
467 assert_ne!(paired.encryption_key, paired_charlie.encryption_key);
469 }
470
471 #[test]
472 fn test_identity_pairing_device_id_computation() {
473 let alice = DeviceIdentity::generate();
474 let bob = DeviceIdentity::generate();
475
476 let paired = alice
478 .pair_with(&bob.public_key, &bob.x25519_public_key())
479 .unwrap();
480
481 assert_eq!(paired.device_id, bob.device_id);
483
484 assert_eq!(paired.public_key, bob.public_key);
486 assert_eq!(paired.x25519_public_key, bob.x25519_public_key());
487 }
488
489 #[test]
490 fn test_identity_pairing_order_independence() {
491 let alice = DeviceIdentity::generate();
492 let bob = DeviceIdentity::generate();
493
494 let paired_ab = alice
496 .pair_with(&bob.public_key, &bob.x25519_public_key())
497 .unwrap();
498 let paired_ba = bob
499 .pair_with(&alice.public_key, &alice.x25519_public_key())
500 .unwrap();
501
502 assert_eq!(paired_ab.encryption_key, paired_ba.encryption_key);
503 }
504
505 #[test]
506 fn test_identity_sign_deterministic() {
507 let identity = DeviceIdentity::from_seed([5u8; 32]);
508 let data = b"test data";
509
510 let sig1 = identity.sign(data);
511 let sig2 = identity.sign(data);
512
513 assert_eq!(sig1, sig2);
515 }
516
517 #[test]
518 fn test_identity_sign_different_data() {
519 let identity = DeviceIdentity::generate();
520 let data1 = b"data one";
521 let data2 = b"data two";
522
523 let sig1 = identity.sign(data1);
524 let sig2 = identity.sign(data2);
525
526 assert_ne!(sig1, sig2);
528 }
529
530 #[test]
531 fn test_identity_x25519_public_key_format() {
532 let identity = DeviceIdentity::generate();
533 let pubkey = identity.x25519_public_key();
534
535 assert_eq!(pubkey.len(), 32);
537
538 assert!(pubkey[31] != 0 || pubkey.iter().all(|&b| b == 0)); }
542
543 #[test]
544 fn test_identity_agree_invalid_key() {
545 let identity = DeviceIdentity::generate();
546
547 let invalid_key = [0u8; 32];
549 let result = identity.agree(&invalid_key);
550 assert!(result.is_err(), "All-zero key should produce an error");
551 }
552
553 #[test]
554 fn test_pairing_request_from_identity() {
555 let identity = DeviceIdentity::generate();
556
557 let request_no_name = PairingRequest::from_identity(&identity, None);
559 assert_eq!(request_no_name.device_id, identity.device_id);
560 assert_eq!(request_no_name.public_key, identity.public_key);
561 assert_eq!(
562 request_no_name.x25519_public_key,
563 identity.x25519_public_key()
564 );
565 assert!(request_no_name.device_name.is_none());
566
567 let request_with_name =
569 PairingRequest::from_identity(&identity, Some("Test Device".to_string()));
570 assert_eq!(
571 request_with_name.device_name,
572 Some("Test Device".to_string())
573 );
574 }
575
576 #[test]
577 fn test_pairing_confirmation_verify_fails_wrong_key() {
578 let alice = DeviceIdentity::generate();
579 let bob = DeviceIdentity::generate();
580 let _charlie = DeviceIdentity::generate();
581
582 let request = PairingRequest::from_identity(&alice, None);
584
585 let confirmation = PairingConfirmation::create(&bob, &request);
587
588 let mut modified_request = request.clone();
592 modified_request.device_name = Some("Modified".to_string());
593
594 let _confirmation_wrong = PairingConfirmation::create(&bob, &modified_request);
595
596 assert!(confirmation.verify().is_ok());
598 }
599
600 #[test]
601 fn test_paired_device_update_last_synced() {
602 let alice = DeviceIdentity::generate();
603 let bob = DeviceIdentity::generate();
604
605 let mut paired = alice
606 .pair_with(&bob.public_key, &bob.x25519_public_key())
607 .unwrap();
608
609 assert_eq!(paired.last_synced_seq, 0);
611
612 paired.update_last_synced(10);
614 assert_eq!(paired.last_synced_seq, 10);
615
616 paired.update_last_synced(20);
618 assert_eq!(paired.last_synced_seq, 20);
619 }
620
621 #[test]
622 fn test_paired_device_device_id_hex() {
623 let alice = DeviceIdentity::generate();
624 let bob = DeviceIdentity::generate();
625
626 let paired = alice
627 .pair_with(&bob.public_key, &bob.x25519_public_key())
628 .unwrap();
629
630 let hex = paired.device_id_hex();
631 assert_eq!(hex.len(), 32);
633
634 assert_eq!(hex, hex::encode(paired.device_id));
636 }
637
638 #[test]
639 fn test_identity_export_seed() {
640 let identity = DeviceIdentity::generate();
641 let seed = identity.export_seed();
642
643 assert_eq!(seed.len(), 32);
645
646 let recreated = DeviceIdentity::from_seed(seed);
648 assert_eq!(identity.device_id, recreated.device_id);
649 assert_eq!(identity.public_key, recreated.public_key);
650 }
651
652 #[test]
653 fn test_identity_debug_format() {
654 let identity = DeviceIdentity::generate();
655 let debug_str = format!("{identity:?}");
656
657 assert!(debug_str.contains("DeviceIdentity"));
659 }
660
661 #[test]
662 fn test_identity_pairing_with_different_seeds() {
663 let seed_a = [1u8; 32];
665 let seed_b = [2u8; 32];
666
667 let alice = DeviceIdentity::from_seed(seed_a);
668 let bob = DeviceIdentity::from_seed(seed_b);
669
670 let paired = alice
671 .pair_with(&bob.public_key, &bob.x25519_public_key())
672 .unwrap();
673
674 assert_ne!(paired.encryption_key, [0u8; 32]);
676 }
677
678 #[test]
679 fn test_confirmation_serialization() {
680 let alice = DeviceIdentity::generate();
681 let bob = DeviceIdentity::generate();
682
683 let request = PairingRequest::from_identity(&alice, None);
684 let confirmation = PairingConfirmation::create(&bob, &request);
685
686 let serialized = serde_json::to_vec(&confirmation).unwrap();
688 assert!(!serialized.is_empty());
689
690 let deserialized: PairingConfirmation = serde_json::from_slice(&serialized).unwrap();
692
693 assert_eq!(
694 confirmation.request.device_id,
695 deserialized.request.device_id
696 );
697 assert_eq!(confirmation.signature, deserialized.signature);
698 assert_eq!(
699 confirmation.confirmer_public_key,
700 deserialized.confirmer_public_key
701 );
702 }
703}