1use alloc::vec::Vec;
2use core::fmt;
3
4use crate::ed25519::{Ed25519PrivateKey, Ed25519PublicKey};
5use crate::hkdf;
6use crate::sha256;
7use crate::token::{Token, TokenError};
8use crate::x25519::{X25519PrivateKey, X25519PublicKey};
9use crate::Rng;
10
11pub const KEYSIZE: usize = 512; pub const DERIVED_KEY_LENGTH: usize = 64; pub const TRUNCATED_HASHLENGTH: usize = 128; #[derive(Debug)]
16pub enum CryptoError {
17 NoPrivateKey,
18 NoPublicKey,
19 TokenError(TokenError),
20 HkdfError(hkdf::HkdfError),
21 InvalidCiphertext,
22 InvalidPublicKey,
23}
24
25impl fmt::Display for CryptoError {
26 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
27 match self {
28 CryptoError::NoPrivateKey => write!(f, "No private key"),
29 CryptoError::NoPublicKey => write!(f, "No public key"),
30 CryptoError::TokenError(e) => write!(f, "Token error: {}", e),
31 CryptoError::HkdfError(e) => write!(f, "HKDF error: {}", e),
32 CryptoError::InvalidCiphertext => write!(f, "Invalid ciphertext"),
33 CryptoError::InvalidPublicKey => write!(f, "Invalid public key"),
34 }
35 }
36}
37
38#[cfg(feature = "std")]
39impl std::error::Error for CryptoError {}
40
41pub struct Identity {
42 prv: Option<X25519PrivateKey>,
43 sig_prv: Option<Ed25519PrivateKey>,
44 pub_key: Option<X25519PublicKey>,
45 sig_pub: Option<Ed25519PublicKey>,
46 hash: [u8; 16],
47}
48
49impl Identity {
50 pub fn new(rng: &mut dyn Rng) -> Self {
51 let prv = X25519PrivateKey::generate(rng);
52 let sig_prv = Ed25519PrivateKey::generate(rng);
53
54 let pub_key = prv.public_key();
55 let sig_pub = sig_prv.public_key();
56
57 let mut pub_bytes = [0u8; 64];
58 pub_bytes[..32].copy_from_slice(&pub_key.public_bytes());
59 pub_bytes[32..].copy_from_slice(&sig_pub.public_bytes());
60
61 let hash = truncated_hash(&pub_bytes);
62
63 Identity {
64 prv: Some(prv),
65 sig_prv: Some(sig_prv),
66 pub_key: Some(pub_key),
67 sig_pub: Some(sig_pub),
68 hash,
69 }
70 }
71
72 pub fn from_private_key(prv_bytes: &[u8; 64]) -> Self {
77 let x_prv_bytes: [u8; 32] = prv_bytes[..32].try_into().unwrap();
78 let ed_seed: [u8; 32] = prv_bytes[32..].try_into().unwrap();
79
80 let prv = X25519PrivateKey::from_bytes(&x_prv_bytes);
81 let sig_prv = Ed25519PrivateKey::from_bytes(&ed_seed);
82
83 let pub_key = prv.public_key();
84 let sig_pub = sig_prv.public_key();
85
86 let mut pub_bytes = [0u8; 64];
87 pub_bytes[..32].copy_from_slice(&pub_key.public_bytes());
88 pub_bytes[32..].copy_from_slice(&sig_pub.public_bytes());
89
90 let hash = truncated_hash(&pub_bytes);
91
92 Identity {
93 prv: Some(prv),
94 sig_prv: Some(sig_prv),
95 pub_key: Some(pub_key),
96 sig_pub: Some(sig_pub),
97 hash,
98 }
99 }
100
101 pub fn from_public_key(pub_bytes: &[u8; 64]) -> Result<Self, CryptoError> {
107 let x_pub_bytes: [u8; 32] = pub_bytes[..32].try_into().unwrap();
108 let ed_pub_bytes: [u8; 32] = pub_bytes[32..].try_into().unwrap();
109
110 let pub_key = X25519PublicKey::from_bytes(&x_pub_bytes);
111 let sig_pub = Ed25519PublicKey::from_bytes(&ed_pub_bytes)
112 .map_err(|_| CryptoError::InvalidPublicKey)?;
113
114 let hash = truncated_hash(pub_bytes);
115
116 Ok(Identity {
117 prv: None,
118 sig_prv: None,
119 pub_key: Some(pub_key),
120 sig_pub: Some(sig_pub),
121 hash,
122 })
123 }
124
125 pub fn get_private_key(&self) -> Option<[u8; 64]> {
126 match (&self.prv, &self.sig_prv) {
127 (Some(prv), Some(sig_prv)) => {
128 let mut result = [0u8; 64];
129 result[..32].copy_from_slice(&prv.private_bytes());
130 result[32..].copy_from_slice(&sig_prv.private_bytes());
131 Some(result)
132 }
133 _ => None,
134 }
135 }
136
137 pub fn get_public_key(&self) -> Option<[u8; 64]> {
138 match (&self.pub_key, &self.sig_pub) {
139 (Some(pub_key), Some(sig_pub)) => {
140 let mut result = [0u8; 64];
141 result[..32].copy_from_slice(&pub_key.public_bytes());
142 result[32..].copy_from_slice(&sig_pub.public_bytes());
143 Some(result)
144 }
145 _ => None,
146 }
147 }
148
149 pub fn hash(&self) -> &[u8; 16] {
150 &self.hash
151 }
152
153 pub fn encrypt(&self, plaintext: &[u8], rng: &mut dyn Rng) -> Result<Vec<u8>, CryptoError> {
154 let pub_key = self.pub_key.as_ref().ok_or(CryptoError::NoPublicKey)?;
155 self.encrypt_to_public_key(plaintext, pub_key, rng)
156 }
157
158 pub fn encrypt_with_ratchet(
159 &self,
160 plaintext: &[u8],
161 ratchet: Option<&[u8; 32]>,
162 rng: &mut dyn Rng,
163 ) -> Result<Vec<u8>, CryptoError> {
164 match ratchet {
165 Some(ratchet_pub_bytes) => {
166 let ratchet_pub = X25519PublicKey::from_bytes(ratchet_pub_bytes);
167 self.encrypt_to_public_key(plaintext, &ratchet_pub, rng)
168 }
169 None => self.encrypt(plaintext, rng),
170 }
171 }
172
173 fn encrypt_to_public_key(
174 &self,
175 plaintext: &[u8],
176 target_public_key: &X25519PublicKey,
177 rng: &mut dyn Rng,
178 ) -> Result<Vec<u8>, CryptoError> {
179 let ephemeral = X25519PrivateKey::generate(rng);
180 let ephemeral_pub_bytes = ephemeral.public_key().public_bytes();
181 let shared_key = ephemeral.exchange(target_public_key);
182
183 let derived_key = hkdf::hkdf(DERIVED_KEY_LENGTH, &shared_key, Some(&self.hash), None)
184 .map_err(CryptoError::HkdfError)?;
185
186 let token = Token::new(&derived_key).map_err(CryptoError::TokenError)?;
187 let ciphertext = token.encrypt(plaintext, rng);
188
189 let mut result = Vec::with_capacity(32 + ciphertext.len());
190 result.extend_from_slice(&ephemeral_pub_bytes);
191 result.extend_from_slice(&ciphertext);
192 Ok(result)
193 }
194
195 pub fn encrypt_deterministic(
197 &self,
198 plaintext: &[u8],
199 ephemeral_prv: &[u8; 32],
200 iv: &[u8; 16],
201 ) -> Result<Vec<u8>, CryptoError> {
202 let pub_key = self.pub_key.as_ref().ok_or(CryptoError::NoPublicKey)?;
203
204 let ephemeral = X25519PrivateKey::from_bytes(ephemeral_prv);
205 let ephemeral_pub_bytes = ephemeral.public_key().public_bytes();
206 let shared_key = ephemeral.exchange(pub_key);
207
208 let derived_key = hkdf::hkdf(DERIVED_KEY_LENGTH, &shared_key, Some(&self.hash), None)
209 .map_err(CryptoError::HkdfError)?;
210
211 let token = Token::new(&derived_key).map_err(CryptoError::TokenError)?;
212 let ciphertext = token.encrypt_with_iv(plaintext, iv);
213
214 let mut result = Vec::with_capacity(32 + ciphertext.len());
215 result.extend_from_slice(&ephemeral_pub_bytes);
216 result.extend_from_slice(&ciphertext);
217 Ok(result)
218 }
219
220 pub fn decrypt(&self, ciphertext_token: &[u8]) -> Result<Vec<u8>, CryptoError> {
221 let prv = self.prv.as_ref().ok_or(CryptoError::NoPrivateKey)?;
222 self.decrypt_with_key(ciphertext_token, prv)
223 }
224
225 pub fn decrypt_with_ratchets(
228 &self,
229 ciphertext: &[u8],
230 ratchets: &crate::ratchet::RatchetRing,
231 enforce: bool,
232 ) -> Result<crate::ratchet::Decrypted, CryptoError> {
233 if ciphertext.len() > 32 + crate::token::TOKEN_OVERHEAD {
237 for key in ratchets.keys() {
238 let private = X25519PrivateKey::from_bytes(key);
239 if let Ok(plaintext) = self.decrypt_with_key(ciphertext, &private) {
240 return Ok(crate::ratchet::Decrypted {
241 plaintext,
242 ratchet_id: Some(crate::ratchet::ratchet_id(
243 &private.public_key().public_bytes(),
244 )),
245 });
246 }
247 }
248 }
249 if enforce {
250 return Err(CryptoError::InvalidCiphertext);
251 }
252 Ok(crate::ratchet::Decrypted {
253 plaintext: self.decrypt(ciphertext)?,
254 ratchet_id: None,
255 })
256 }
257
258 fn decrypt_with_key(
259 &self,
260 ciphertext_token: &[u8],
261 private: &X25519PrivateKey,
262 ) -> Result<Vec<u8>, CryptoError> {
263 if ciphertext_token.len() <= 32 {
264 return Err(CryptoError::InvalidCiphertext);
265 }
266 if ciphertext_token.len() <= 32 + crate::token::TOKEN_OVERHEAD {
267 return Err(CryptoError::TokenError(TokenError::InvalidToken));
268 }
269 let peer_pub_bytes: [u8; 32] = ciphertext_token[..32].try_into().unwrap();
270 let peer_pub = X25519PublicKey::from_bytes(&peer_pub_bytes);
271 let shared_key = zeroize::Zeroizing::new(private.exchange(&peer_pub));
272 let derived_key = zeroize::Zeroizing::new(
273 hkdf::hkdf(DERIVED_KEY_LENGTH, &*shared_key, Some(&self.hash), None)
274 .map_err(CryptoError::HkdfError)?,
275 );
276 let token = Token::new(&derived_key).map_err(CryptoError::TokenError)?;
277 token
278 .decrypt(&ciphertext_token[32..])
279 .map_err(CryptoError::TokenError)
280 }
281
282 pub fn sign(&self, message: &[u8]) -> Result<[u8; 64], CryptoError> {
283 let sig_prv = self.sig_prv.as_ref().ok_or(CryptoError::NoPrivateKey)?;
284 Ok(sig_prv.sign(message))
285 }
286
287 pub fn verify(&self, signature: &[u8; 64], message: &[u8]) -> bool {
288 match &self.sig_pub {
289 Some(sig_pub) => sig_pub.verify(signature, message),
290 None => false,
291 }
292 }
293}
294
295fn truncated_hash(data: &[u8]) -> [u8; 16] {
296 let full = sha256::sha256(data);
297 let mut result = [0u8; 16];
298 result.copy_from_slice(&full[..16]);
299 result
300}
301
302#[cfg(test)]
303mod tests {
304 use super::*;
305 use crate::FixedRng;
306
307 #[test]
308 fn from_public_key_with_invalid_signing_key_returns_error() {
309 let identity = Identity::from_private_key(&[0x37; 64]);
310 let mut public_key = identity.get_public_key().unwrap();
311 public_key[32..].copy_from_slice(&[0; 32]);
313 public_key[32] = 2;
314 assert!(matches!(
315 Identity::from_public_key(&public_key),
316 Err(CryptoError::InvalidPublicKey)
317 ));
318 }
319
320 #[test]
321 fn from_public_key_accepts_valid_public_key() {
322 let identity = Identity::from_private_key(&[0x37; 64]);
323 let public_key = identity.get_public_key().unwrap();
324 let public = Identity::from_public_key(&public_key).unwrap();
325 assert_eq!(public.get_public_key(), Some(public_key));
326 assert_eq!(public.hash(), identity.hash());
327 }
328
329 #[test]
330 fn short_ratchet_ciphertexts_preserve_error_and_fallback_policy() {
331 use crate::ratchet::RatchetRing;
332 let identity = Identity::from_private_key(&[0x37; 64]);
333 let public = Identity::from_public_key(&identity.get_public_key().unwrap()).unwrap();
334 for retained in [0, 1, 32] {
335 let ring = RatchetRing::from_keys(vec![[0x42; 32]; retained]).unwrap();
336 for length in [0, 1, 31, 32, 33, 48, 79, 80] {
337 let ciphertext = vec![0x63; length];
338 for owner in [&identity, &public] {
339 assert!(matches!(
340 owner.decrypt_with_ratchets(&ciphertext, &ring, true),
341 Err(CryptoError::InvalidCiphertext)
342 ));
343 let expected = owner.decrypt(&ciphertext).unwrap_err();
344 let actual = owner
345 .decrypt_with_ratchets(&ciphertext, &ring, false)
346 .err()
347 .unwrap();
348 assert_eq!(format!("{actual:?}"), format!("{expected:?}"));
349 }
350 assert!(matches!(
351 public.decrypt(&ciphertext),
352 Err(CryptoError::NoPrivateKey)
353 ));
354 if length <= 32 {
355 assert!(matches!(
356 identity.decrypt(&ciphertext),
357 Err(CryptoError::InvalidCiphertext)
358 ));
359 } else {
360 assert!(matches!(
361 identity.decrypt(&ciphertext),
362 Err(CryptoError::TokenError(TokenError::InvalidToken))
363 ));
364 }
365 }
366 }
367 }
368
369 #[test]
370 fn minimum_valid_ratchet_ciphertext_still_authenticates() {
371 use crate::ratchet::{ratchet_id, RatchetRing};
372 let identity = Identity::from_private_key(&[0x37; 64]);
373 let public_identity =
374 Identity::from_public_key(&identity.get_public_key().unwrap()).unwrap();
375 let ring = RatchetRing::from_keys(vec![[0x42; 32]]).unwrap();
376 let key = ring.current_public().unwrap();
377 let mut ciphertext = identity
378 .encrypt_with_ratchet(b"", Some(&key), &mut crate::FixedRng::new(&[0x63; 64]))
379 .unwrap();
380 assert_eq!(ciphertext.len(), 96);
381 let result = public_identity
382 .decrypt_with_ratchets(&ciphertext, &ring, true)
383 .unwrap();
384 assert!(result.plaintext.is_empty());
385 assert_eq!(result.ratchet_id, Some(ratchet_id(&key)));
386 *ciphertext.last_mut().unwrap() ^= 1;
387 assert!(public_identity
388 .decrypt_with_ratchets(&ciphertext, &ring, true)
389 .is_err());
390 }
391
392 #[test]
393 fn test_identity_key_roundtrip() {
394 let mut rng = FixedRng::new(&(0..64).collect::<Vec<u8>>());
395 let id = Identity::new(&mut rng);
396 let prv_bytes = id.get_private_key().unwrap();
397 let id2 = Identity::from_private_key(&prv_bytes);
398 assert_eq!(id.get_public_key().unwrap(), id2.get_public_key().unwrap());
399 }
400
401 #[test]
402 fn test_identity_hash() {
403 let mut rng = FixedRng::new(&(0..64).collect::<Vec<u8>>());
404 let id = Identity::new(&mut rng);
405 let pub_key = id.get_public_key().unwrap();
406 let expected_hash = truncated_hash(&pub_key);
407 assert_eq!(*id.hash(), expected_hash);
408 }
409
410 #[test]
411 fn test_identity_encrypt_decrypt_roundtrip() {
412 let mut rng = FixedRng::new(&(0..128).collect::<Vec<u8>>());
413 let id = Identity::new(&mut rng);
414 let plaintext = b"Hello, Reticulum! This is a test of the encrypt/decrypt pipeline.";
415 let mut rng2 = FixedRng::new(&(128..255).collect::<Vec<u8>>());
416 let ciphertext = id.encrypt(plaintext, &mut rng2).unwrap();
417 let decrypted = id.decrypt(&ciphertext).unwrap();
418 assert_eq!(decrypted, plaintext);
419 }
420
421 #[test]
422 fn test_identity_encrypt_with_ratchet_targets_ratchet_key() {
423 let mut rng = FixedRng::new(&(0..128).collect::<Vec<u8>>());
424 let remote_identity = Identity::new(&mut rng);
425 let ratchet_prv = X25519PrivateKey::from_bytes(&[0x42; 32]);
426 let ratchet_pub = ratchet_prv.public_key().public_bytes();
427 let plaintext = b"ratcheted";
428
429 let mut encrypt_rng = FixedRng::new(&(128..255).collect::<Vec<u8>>());
430 let ciphertext = remote_identity
431 .encrypt_with_ratchet(plaintext, Some(&ratchet_pub), &mut encrypt_rng)
432 .unwrap();
433
434 let peer_pub_bytes: [u8; 32] = ciphertext[..32].try_into().unwrap();
435 let peer_pub = X25519PublicKey::from_bytes(&peer_pub_bytes);
436 let shared_key = ratchet_prv.exchange(&peer_pub);
437 let derived_key = hkdf::hkdf(
438 DERIVED_KEY_LENGTH,
439 &shared_key,
440 Some(remote_identity.hash()),
441 None,
442 )
443 .unwrap();
444 let token = Token::new(&derived_key).unwrap();
445 let decrypted = token.decrypt(&ciphertext[32..]).unwrap();
446
447 assert_eq!(decrypted, plaintext);
448 assert!(remote_identity.decrypt(&ciphertext).is_err());
449 }
450
451 #[test]
452 fn test_identity_sign_verify() {
453 let mut rng = FixedRng::new(&(0..64).collect::<Vec<u8>>());
454 let id = Identity::new(&mut rng);
455 let msg = b"Sign this message";
456 let sig = id.sign(msg).unwrap();
457 assert!(id.verify(&sig, msg));
458 assert!(!id.verify(&sig, b"Wrong message"));
459 }
460}