1mod crypto;
11mod merkle;
12mod identity;
13
14pub use crypto::CryptoEngine;
15pub use merkle::MerkleTree;
16pub use identity::{DeviceIdentity, PairedDevice, PairingRequest, PairingConfirmation};
17
18use cp_core::{CognitiveDiff, CPError, Result};
19
20use serde::{Deserialize, Serialize};
21use serde_big_array::BigArray;
22
23#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct EncryptedPayload {
26 pub ciphertext: Vec<u8>,
28 #[serde(with = "BigArray")]
30 pub nonce: [u8; 24],
31 #[serde(with = "BigArray")]
33 pub signature: [u8; 64],
34 pub public_key: [u8; 32],
36}
37
38#[derive(Debug, Clone, Serialize, Deserialize)]
43pub struct SignedDiff {
44 pub encrypted_diff: EncryptedPayload,
46
47 #[serde(with = "BigArray")]
49 pub signature: [u8; 64],
50
51 pub sender_public_key: [u8; 32],
53
54 pub sender_device_id: [u8; 16],
56
57 pub target_device_id: [u8; 16],
59
60 pub sequence: u64,
62}
63
64impl SignedDiff {
65 pub fn new(
67 identity: &DeviceIdentity,
68 encrypted_diff: EncryptedPayload,
69 target_device_id: [u8; 16],
70 sequence: u64,
71 ) -> Self {
72 let signing_data = Self::compute_signing_data(
73 &encrypted_diff.nonce,
74 &encrypted_diff.ciphertext,
75 &identity.device_id,
76 &target_device_id,
77 sequence,
78 );
79
80 let signature = identity.sign(&signing_data);
81
82 Self {
83 encrypted_diff,
84 signature,
85 sender_public_key: identity.public_key,
86 sender_device_id: identity.device_id,
87 target_device_id,
88 sequence,
89 }
90 }
91
92 fn compute_signing_data(
94 nonce: &[u8; 24],
95 ciphertext: &[u8],
96 sender_id: &[u8; 16],
97 target_id: &[u8; 16],
98 sequence: u64,
99 ) -> Vec<u8> {
100 let mut data = Vec::with_capacity(24 + ciphertext.len() + 16 + 16 + 8);
101 data.extend_from_slice(nonce);
102 data.extend_from_slice(ciphertext);
103 data.extend_from_slice(sender_id);
104 data.extend_from_slice(target_id);
105 data.extend_from_slice(&sequence.to_le_bytes());
106 data
107 }
108
109 pub fn verify(&self) -> Result<()> {
111 use ed25519_dalek::{Signature, VerifyingKey, Verifier};
112
113 let verifying_key = VerifyingKey::from_bytes(&self.sender_public_key)
114 .map_err(|e| CPError::Crypto(format!("Invalid public key: {}", e)))?;
115
116 let expected_device_id: [u8; 16] = blake3::hash(&self.sender_public_key).as_bytes()[0..16]
118 .try_into()
119 .unwrap();
120 if expected_device_id != self.sender_device_id {
121 return Err(CPError::Verification("Device ID doesn't match public key".into()));
122 }
123
124 let signing_data = Self::compute_signing_data(
125 &self.encrypted_diff.nonce,
126 &self.encrypted_diff.ciphertext,
127 &self.sender_device_id,
128 &self.target_device_id,
129 self.sequence,
130 );
131
132 let signature = Signature::from_bytes(&self.signature);
133
134 verifying_key
135 .verify(&signing_data, &signature)
136 .map_err(|_| CPError::Verification("Invalid SignedDiff signature".into()))
137 }
138
139 pub fn sender_device_id_hex(&self) -> String {
141 self.sender_device_id
142 .iter()
143 .map(|b| format!("{:02x}", b))
144 .collect()
145 }
146
147 pub fn target_device_id_hex(&self) -> String {
149 self.target_device_id
150 .iter()
151 .map(|b| format!("{:02x}", b))
152 .collect()
153 }
154}
155
156pub fn serialize_diff(diff: &CognitiveDiff) -> Result<Vec<u8>> {
158 let mut cbor_bytes = Vec::new();
159 ciborium::into_writer(diff, &mut cbor_bytes)
160 .map_err(|e| cp_core::CPError::Serialization(e.to_string()))?;
161
162 let compressed = zstd::encode_all(cbor_bytes.as_slice(), 3)
163 .map_err(|e| cp_core::CPError::Serialization(e.to_string()))?;
164
165 Ok(compressed)
166}
167
168pub fn deserialize_diff(data: &[u8]) -> Result<CognitiveDiff> {
170 let decompressed = zstd::decode_all(data)
171 .map_err(|e| cp_core::CPError::Serialization(e.to_string()))?;
172
173 let diff: CognitiveDiff = ciborium::from_reader(decompressed.as_slice())
174 .map_err(|e| cp_core::CPError::Serialization(e.to_string()))?;
175
176 Ok(diff)
177}
178
179#[cfg(test)]
180mod tests {
181 use super::*;
182 use cp_core::Hlc;
183 use uuid::Uuid;
184
185 #[test]
186 fn test_signed_diff_roundtrip() {
187 let alice = DeviceIdentity::generate();
188 let bob = DeviceIdentity::generate();
189
190 let diff = CognitiveDiff::empty(
192 [0u8; 32],
193 Uuid::from_bytes(alice.device_id),
194 1,
195 Hlc::new(1000, alice.device_id),
196 );
197
198 let crypto = CryptoEngine::new();
200 let encrypted = crypto.encrypt_diff(&diff).unwrap();
201
202 let signed = SignedDiff::new(&alice, encrypted, bob.device_id, 1);
203
204 assert!(signed.verify().is_ok());
206
207 assert_eq!(signed.sender_device_id, alice.device_id);
209 assert_eq!(signed.target_device_id, bob.device_id);
210 assert_eq!(signed.sequence, 1);
211 }
212
213 #[test]
214 fn test_signed_diff_verification_fails_on_tamper() {
215 let alice = DeviceIdentity::generate();
216 let bob = DeviceIdentity::generate();
217
218 let diff = CognitiveDiff::empty(
219 [0u8; 32],
220 Uuid::from_bytes(alice.device_id),
221 1,
222 Hlc::new(1000, alice.device_id),
223 );
224
225 let crypto = CryptoEngine::new();
226 let encrypted = crypto.encrypt_diff(&diff).unwrap();
227
228 let mut signed = SignedDiff::new(&alice, encrypted, bob.device_id, 1);
229
230 signed.sequence = 2;
232
233 assert!(signed.verify().is_err());
235 }
236}