1mod crypto;
11mod identity;
12mod merkle;
13
14pub use crypto::CryptoEngine;
15pub use identity::{DeviceIdentity, PairedDevice, PairingConfirmation, PairingRequest};
16pub use merkle::MerkleTree;
17
18use cp_core::{CPError, CognitiveDiff, 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, Verifier, VerifyingKey};
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(
122 "Device ID doesn't match public key".into(),
123 ));
124 }
125
126 let signing_data = Self::compute_signing_data(
127 &self.encrypted_diff.nonce,
128 &self.encrypted_diff.ciphertext,
129 &self.sender_device_id,
130 &self.target_device_id,
131 self.sequence,
132 );
133
134 let signature = Signature::from_bytes(&self.signature);
135
136 verifying_key
137 .verify(&signing_data, &signature)
138 .map_err(|_| CPError::Verification("Invalid SignedDiff signature".into()))
139 }
140
141 pub fn sender_device_id_hex(&self) -> String {
143 use std::fmt::Write;
144 self.sender_device_id
145 .iter()
146 .fold(String::new(), |mut s, b| {
147 write!(s, "{b:02x}").unwrap();
148 s
149 })
150 }
151
152 pub fn target_device_id_hex(&self) -> String {
154 use std::fmt::Write;
155 self.target_device_id
156 .iter()
157 .fold(String::new(), |mut s, b| {
158 write!(s, "{b:02x}").unwrap();
159 s
160 })
161 }
162}
163
164pub fn serialize_diff(diff: &CognitiveDiff) -> Result<Vec<u8>> {
166 let mut cbor_bytes = Vec::new();
167 ciborium::into_writer(diff, &mut cbor_bytes)
168 .map_err(|e| CPError::Serialization(e.to_string()))?;
169
170 let compressed = zstd::encode_all(cbor_bytes.as_slice(), 3)
171 .map_err(|e| CPError::Serialization(e.to_string()))?;
172
173 Ok(compressed)
174}
175
176pub fn deserialize_diff(data: &[u8]) -> Result<CognitiveDiff> {
178 let decompressed = zstd::decode_all(data).map_err(|e| CPError::Serialization(e.to_string()))?;
179
180 let diff: CognitiveDiff = ciborium::from_reader(decompressed.as_slice())
181 .map_err(|e| CPError::Serialization(e.to_string()))?;
182
183 Ok(diff)
184}
185
186#[cfg(test)]
187mod tests {
188 use super::*;
189 use cp_core::Hlc;
190 use uuid::Uuid;
191
192 #[test]
193 fn test_signed_diff_roundtrip() {
194 let alice = DeviceIdentity::generate();
195 let bob = DeviceIdentity::generate();
196
197 let diff = CognitiveDiff::empty(
199 [0u8; 32],
200 Uuid::from_bytes(alice.device_id),
201 1,
202 Hlc::new(1000, alice.device_id),
203 );
204
205 let crypto = CryptoEngine::new();
207 let encrypted = crypto.encrypt_diff(&diff).unwrap();
208
209 let signed = SignedDiff::new(&alice, encrypted, bob.device_id, 1);
210
211 assert!(signed.verify().is_ok());
213
214 assert_eq!(signed.sender_device_id, alice.device_id);
216 assert_eq!(signed.target_device_id, bob.device_id);
217 assert_eq!(signed.sequence, 1);
218 }
219
220 #[test]
221 fn test_signed_diff_verification_fails_on_tamper() {
222 let alice = DeviceIdentity::generate();
223 let bob = DeviceIdentity::generate();
224
225 let diff = CognitiveDiff::empty(
226 [0u8; 32],
227 Uuid::from_bytes(alice.device_id),
228 1,
229 Hlc::new(1000, alice.device_id),
230 );
231
232 let crypto = CryptoEngine::new();
233 let encrypted = crypto.encrypt_diff(&diff).unwrap();
234
235 let mut signed = SignedDiff::new(&alice, encrypted, bob.device_id, 1);
236
237 signed.sequence = 2;
239
240 assert!(signed.verify().is_err());
242 }
243}