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 use crate::stamp_value::{stamp_value, StampCost, StampValue};
42
43pub fn generate_stamped_key(
46 rng: &mut dyn Rng,
47 material: &[u8],
48 stamp_cost: StampCost,
49) -> (X25519PrivateKey, X25519PublicKey, StampValue) {
50 generate_stamped_key_until(rng, material, stamp_cost, &mut || false)
51 .expect("stamped key generation without a stop condition always completes")
52}
53
54pub fn generate_stamped_key_until(
59 rng: &mut dyn Rng,
60 material: &[u8],
61 stamp_cost: StampCost,
62 should_stop: &mut dyn FnMut() -> bool,
63) -> Option<(X25519PrivateKey, X25519PublicKey, StampValue)> {
64 loop {
65 if should_stop() {
66 return None;
67 }
68 let prv = X25519PrivateKey::generate(rng);
69 let public = prv.public_key();
70 let mut input = Vec::with_capacity(material.len() + 32);
71 input.extend_from_slice(material);
72 input.extend_from_slice(&public.public_bytes());
73 let product = crate::sha256::sha256(&input);
74 if stamp_cost.admits(&product) {
75 let value = crate::stamp_value::value_of_product(&product);
76 return Some((prv, public, value));
77 }
78 }
79}
80
81pub struct Identity {
82 prv: Option<X25519PrivateKey>,
83 sig_prv: Option<Ed25519PrivateKey>,
84 pub_key: Option<X25519PublicKey>,
85 sig_pub: Option<Ed25519PublicKey>,
86 hash: [u8; 16],
87}
88
89impl Identity {
90 pub fn new(rng: &mut dyn Rng) -> Self {
91 let prv = X25519PrivateKey::generate(rng);
92 let sig_prv = Ed25519PrivateKey::generate(rng);
93
94 let pub_key = prv.public_key();
95 let sig_pub = sig_prv.public_key();
96
97 let mut pub_bytes = [0u8; 64];
98 pub_bytes[..32].copy_from_slice(&pub_key.public_bytes());
99 pub_bytes[32..].copy_from_slice(&sig_pub.public_bytes());
100
101 let hash = truncated_hash(&pub_bytes);
102
103 Identity {
104 prv: Some(prv),
105 sig_prv: Some(sig_prv),
106 pub_key: Some(pub_key),
107 sig_pub: Some(sig_pub),
108 hash,
109 }
110 }
111
112 pub fn new_stamped(rng: &mut dyn Rng, value: StampCost) -> Self {
116 let sig_prv = Ed25519PrivateKey::generate(rng);
117 let sig_pub = sig_prv.public_key();
118 let (prv, pub_key, _value) = generate_stamped_key(rng, &sig_pub.public_bytes(), value);
119
120 let mut pub_bytes = [0u8; 64];
121 pub_bytes[..32].copy_from_slice(&pub_key.public_bytes());
122 pub_bytes[32..].copy_from_slice(&sig_pub.public_bytes());
123 let hash = truncated_hash(&pub_bytes);
124
125 Identity {
126 prv: Some(prv),
127 sig_prv: Some(sig_prv),
128 pub_key: Some(pub_key),
129 sig_pub: Some(sig_pub),
130 hash,
131 }
132 }
133
134 pub fn get_value(&self) -> Option<StampValue> {
136 match (&self.sig_pub, &self.pub_key) {
137 (Some(sig_pub), Some(pub_key)) => Some(stamp_value(
138 &sig_pub.public_bytes(),
139 &pub_key.public_bytes(),
140 )),
141 _ => None,
142 }
143 }
144
145 pub fn from_private_key(prv_bytes: &[u8; 64]) -> Self {
150 let x_prv_bytes: [u8; 32] = prv_bytes[..32].try_into().unwrap();
151 let ed_seed: [u8; 32] = prv_bytes[32..].try_into().unwrap();
152
153 let prv = X25519PrivateKey::from_bytes(&x_prv_bytes);
154 let sig_prv = Ed25519PrivateKey::from_bytes(&ed_seed);
155
156 let pub_key = prv.public_key();
157 let sig_pub = sig_prv.public_key();
158
159 let mut pub_bytes = [0u8; 64];
160 pub_bytes[..32].copy_from_slice(&pub_key.public_bytes());
161 pub_bytes[32..].copy_from_slice(&sig_pub.public_bytes());
162
163 let hash = truncated_hash(&pub_bytes);
164
165 Identity {
166 prv: Some(prv),
167 sig_prv: Some(sig_prv),
168 pub_key: Some(pub_key),
169 sig_pub: Some(sig_pub),
170 hash,
171 }
172 }
173
174 pub fn from_public_key(pub_bytes: &[u8; 64]) -> Result<Self, CryptoError> {
180 let x_pub_bytes: [u8; 32] = pub_bytes[..32].try_into().unwrap();
181 let ed_pub_bytes: [u8; 32] = pub_bytes[32..].try_into().unwrap();
182
183 let pub_key = X25519PublicKey::from_bytes(&x_pub_bytes);
184 let sig_pub = Ed25519PublicKey::from_bytes(&ed_pub_bytes)
185 .map_err(|_| CryptoError::InvalidPublicKey)?;
186
187 let hash = truncated_hash(pub_bytes);
188
189 Ok(Identity {
190 prv: None,
191 sig_prv: None,
192 pub_key: Some(pub_key),
193 sig_pub: Some(sig_pub),
194 hash,
195 })
196 }
197
198 pub fn get_private_key(&self) -> Option<[u8; 64]> {
199 match (&self.prv, &self.sig_prv) {
200 (Some(prv), Some(sig_prv)) => {
201 let mut result = [0u8; 64];
202 result[..32].copy_from_slice(&prv.private_bytes());
203 result[32..].copy_from_slice(&sig_prv.private_bytes());
204 Some(result)
205 }
206 _ => None,
207 }
208 }
209
210 pub fn get_public_key(&self) -> Option<[u8; 64]> {
211 match (&self.pub_key, &self.sig_pub) {
212 (Some(pub_key), Some(sig_pub)) => {
213 let mut result = [0u8; 64];
214 result[..32].copy_from_slice(&pub_key.public_bytes());
215 result[32..].copy_from_slice(&sig_pub.public_bytes());
216 Some(result)
217 }
218 _ => None,
219 }
220 }
221
222 pub fn hash(&self) -> &[u8; 16] {
223 &self.hash
224 }
225
226 pub fn encrypt(&self, plaintext: &[u8], rng: &mut dyn Rng) -> Result<Vec<u8>, CryptoError> {
227 let pub_key = self.pub_key.as_ref().ok_or(CryptoError::NoPublicKey)?;
228 self.encrypt_to_public_key(plaintext, pub_key, rng)
229 }
230
231 pub fn encrypt_with_ratchet(
232 &self,
233 plaintext: &[u8],
234 ratchet: Option<&[u8; 32]>,
235 rng: &mut dyn Rng,
236 ) -> Result<Vec<u8>, CryptoError> {
237 match ratchet {
238 Some(ratchet_pub_bytes) => {
239 let ratchet_pub = X25519PublicKey::from_bytes(ratchet_pub_bytes);
240 self.encrypt_to_public_key(plaintext, &ratchet_pub, rng)
241 }
242 None => self.encrypt(plaintext, rng),
243 }
244 }
245
246 fn encrypt_to_public_key(
247 &self,
248 plaintext: &[u8],
249 target_public_key: &X25519PublicKey,
250 rng: &mut dyn Rng,
251 ) -> Result<Vec<u8>, CryptoError> {
252 encrypt_to(&self.hash, target_public_key, plaintext, rng)
253 }
254
255 pub fn encrypt_for_public_key(
260 public: &[u8; 64],
261 plaintext: &[u8],
262 ratchet: Option<&[u8; 32]>,
263 rng: &mut dyn Rng,
264 ) -> Result<Vec<u8>, CryptoError> {
265 let x_pub: [u8; 32] = public[..32].try_into().unwrap();
266 let target = X25519PublicKey::from_bytes(ratchet.unwrap_or(&x_pub));
267 encrypt_to(&truncated_hash(public), &target, plaintext, rng)
268 }
269
270 pub fn encrypt_deterministic(
272 &self,
273 plaintext: &[u8],
274 ephemeral_prv: &[u8; 32],
275 iv: &[u8; 16],
276 ) -> Result<Vec<u8>, CryptoError> {
277 let pub_key = self.pub_key.as_ref().ok_or(CryptoError::NoPublicKey)?;
278
279 let ephemeral = X25519PrivateKey::from_bytes(ephemeral_prv);
280 let ephemeral_pub_bytes = ephemeral.public_key().public_bytes();
281 let shared_key = ephemeral.exchange(pub_key);
282
283 let derived_key = hkdf::hkdf(DERIVED_KEY_LENGTH, &shared_key, Some(&self.hash), None)
284 .map_err(CryptoError::HkdfError)?;
285
286 let token = Token::new(&derived_key).map_err(CryptoError::TokenError)?;
287 let ciphertext = token.encrypt_with_iv(plaintext, iv);
288
289 let mut result = Vec::with_capacity(32 + ciphertext.len());
290 result.extend_from_slice(&ephemeral_pub_bytes);
291 result.extend_from_slice(&ciphertext);
292 Ok(result)
293 }
294
295 pub fn decrypt(&self, ciphertext_token: &[u8]) -> Result<Vec<u8>, CryptoError> {
296 let prv = self.prv.as_ref().ok_or(CryptoError::NoPrivateKey)?;
297 self.decrypt_with_key(ciphertext_token, prv)
298 }
299
300 pub fn decrypt_with_ratchets(
303 &self,
304 ciphertext: &[u8],
305 ratchets: &crate::ratchet::RatchetRing,
306 enforce: bool,
307 ) -> Result<crate::ratchet::Decrypted, CryptoError> {
308 if ciphertext.len() > 32 + crate::token::TOKEN_OVERHEAD {
312 for key in ratchets.keys() {
313 let private = X25519PrivateKey::from_bytes(key);
314 if let Ok(plaintext) = self.decrypt_with_key(ciphertext, &private) {
315 return Ok(crate::ratchet::Decrypted {
316 plaintext,
317 ratchet_id: Some(crate::ratchet::ratchet_id(
318 &private.public_key().public_bytes(),
319 )),
320 });
321 }
322 }
323 }
324 if enforce {
325 return Err(CryptoError::InvalidCiphertext);
326 }
327 Ok(crate::ratchet::Decrypted {
328 plaintext: self.decrypt(ciphertext)?,
329 ratchet_id: None,
330 })
331 }
332
333 fn decrypt_with_key(
334 &self,
335 ciphertext_token: &[u8],
336 private: &X25519PrivateKey,
337 ) -> Result<Vec<u8>, CryptoError> {
338 if ciphertext_token.len() <= 32 {
339 return Err(CryptoError::InvalidCiphertext);
340 }
341 if ciphertext_token.len() <= 32 + crate::token::TOKEN_OVERHEAD {
342 return Err(CryptoError::TokenError(TokenError::InvalidToken));
343 }
344 let peer_pub_bytes: [u8; 32] = ciphertext_token[..32].try_into().unwrap();
345 let peer_pub = X25519PublicKey::from_bytes(&peer_pub_bytes);
346 let shared_key = zeroize::Zeroizing::new(private.exchange(&peer_pub));
347 let derived_key = zeroize::Zeroizing::new(
348 hkdf::hkdf(DERIVED_KEY_LENGTH, &*shared_key, Some(&self.hash), None)
349 .map_err(CryptoError::HkdfError)?,
350 );
351 let token = Token::new(&derived_key).map_err(CryptoError::TokenError)?;
352 token
353 .decrypt(&ciphertext_token[32..])
354 .map_err(CryptoError::TokenError)
355 }
356
357 pub fn signing_key(&self) -> Option<&Ed25519PrivateKey> {
359 self.sig_prv.as_ref()
360 }
361
362 pub fn sign(&self, message: &[u8]) -> Result<[u8; 64], CryptoError> {
363 let sig_prv = self.sig_prv.as_ref().ok_or(CryptoError::NoPrivateKey)?;
364 Ok(sig_prv.sign(message))
365 }
366
367 pub fn verify(&self, signature: &[u8; 64], message: &[u8]) -> bool {
368 match &self.sig_pub {
369 Some(sig_pub) => sig_pub.verify(signature, message),
370 None => false,
371 }
372 }
373}
374
375fn encrypt_to(
378 hash: &[u8; 16],
379 target: &X25519PublicKey,
380 plaintext: &[u8],
381 rng: &mut dyn Rng,
382) -> Result<Vec<u8>, CryptoError> {
383 let ephemeral = X25519PrivateKey::generate(rng);
384 let ephemeral_pub_bytes = ephemeral.public_key().public_bytes();
385 let shared_key = ephemeral.exchange(target);
386
387 let derived_key = hkdf::hkdf(DERIVED_KEY_LENGTH, &shared_key, Some(hash), None)
388 .map_err(CryptoError::HkdfError)?;
389
390 let token = Token::new(&derived_key).map_err(CryptoError::TokenError)?;
391 let ciphertext = token.encrypt(plaintext, rng);
392
393 let mut result = Vec::with_capacity(32 + ciphertext.len());
394 result.extend_from_slice(&ephemeral_pub_bytes);
395 result.extend_from_slice(&ciphertext);
396 Ok(result)
397}
398
399fn truncated_hash(data: &[u8]) -> [u8; 16] {
400 let full = sha256::sha256(data);
401 let mut result = [0u8; 16];
402 result.copy_from_slice(&full[..16]);
403 result
404}
405
406#[cfg(test)]
407mod tests {
408 use super::*;
409 use crate::FixedRng;
410
411 #[test]
412 fn from_public_key_with_invalid_signing_key_returns_error() {
413 let identity = Identity::from_private_key(&[0x37; 64]);
414 let mut public_key = identity.get_public_key().unwrap();
415 public_key[32..].copy_from_slice(&[0; 32]);
417 public_key[32] = 2;
418 assert!(matches!(
419 Identity::from_public_key(&public_key),
420 Err(CryptoError::InvalidPublicKey)
421 ));
422 }
423
424 #[test]
425 fn encrypting_from_key_bytes_matches_the_identity() {
426 let identity = Identity::from_private_key(&[0x37; 64]);
427 let public_key = identity.get_public_key().unwrap();
428 let public = Identity::from_public_key(&public_key).unwrap();
429 let ratchet = crate::ratchet::RatchetRing::from_keys(vec![[0x42; 32]]).unwrap();
430 let ratchet_public = ratchet.current_public().unwrap();
431 for ratchet_key in [None, Some(&ratchet_public)] {
432 let seed = [0x63; 64];
433 let expected = public
434 .encrypt_with_ratchet(b"payload", ratchet_key, &mut FixedRng::new(&seed))
435 .unwrap();
436 let actual = Identity::encrypt_for_public_key(
437 &public_key,
438 b"payload",
439 ratchet_key,
440 &mut FixedRng::new(&seed),
441 )
442 .unwrap();
443 assert_eq!(actual, expected);
444 let plaintext = identity
445 .decrypt_with_ratchets(&actual, &ratchet, false)
446 .unwrap()
447 .plaintext;
448 assert_eq!(plaintext, b"payload");
449 }
450 }
451
452 #[test]
453 fn from_public_key_accepts_valid_public_key() {
454 let identity = Identity::from_private_key(&[0x37; 64]);
455 let public_key = identity.get_public_key().unwrap();
456 let public = Identity::from_public_key(&public_key).unwrap();
457 assert_eq!(public.get_public_key(), Some(public_key));
458 assert_eq!(public.hash(), identity.hash());
459 }
460
461 #[test]
462 fn short_ratchet_ciphertexts_preserve_error_and_fallback_policy() {
463 use crate::ratchet::RatchetRing;
464 let identity = Identity::from_private_key(&[0x37; 64]);
465 let public = Identity::from_public_key(&identity.get_public_key().unwrap()).unwrap();
466 for retained in [0, 1, 32] {
467 let ring = RatchetRing::from_keys(vec![[0x42; 32]; retained]).unwrap();
468 for length in [0, 1, 31, 32, 33, 48, 79, 80] {
469 let ciphertext = vec![0x63; length];
470 for owner in [&identity, &public] {
471 assert!(matches!(
472 owner.decrypt_with_ratchets(&ciphertext, &ring, true),
473 Err(CryptoError::InvalidCiphertext)
474 ));
475 let expected = owner.decrypt(&ciphertext).unwrap_err();
476 let actual = owner
477 .decrypt_with_ratchets(&ciphertext, &ring, false)
478 .err()
479 .unwrap();
480 assert_eq!(format!("{actual:?}"), format!("{expected:?}"));
481 }
482 assert!(matches!(
483 public.decrypt(&ciphertext),
484 Err(CryptoError::NoPrivateKey)
485 ));
486 if length <= 32 {
487 assert!(matches!(
488 identity.decrypt(&ciphertext),
489 Err(CryptoError::InvalidCiphertext)
490 ));
491 } else {
492 assert!(matches!(
493 identity.decrypt(&ciphertext),
494 Err(CryptoError::TokenError(TokenError::InvalidToken))
495 ));
496 }
497 }
498 }
499 }
500
501 #[test]
502 fn minimum_valid_ratchet_ciphertext_still_authenticates() {
503 use crate::ratchet::{ratchet_id, RatchetRing};
504 let identity = Identity::from_private_key(&[0x37; 64]);
505 let public_identity =
506 Identity::from_public_key(&identity.get_public_key().unwrap()).unwrap();
507 let ring = RatchetRing::from_keys(vec![[0x42; 32]]).unwrap();
508 let key = ring.current_public().unwrap();
509 let mut ciphertext = identity
510 .encrypt_with_ratchet(b"", Some(&key), &mut crate::FixedRng::new(&[0x63; 64]))
511 .unwrap();
512 assert_eq!(ciphertext.len(), 96);
513 let result = public_identity
514 .decrypt_with_ratchets(&ciphertext, &ring, true)
515 .unwrap();
516 assert!(result.plaintext.is_empty());
517 assert_eq!(result.ratchet_id, Some(ratchet_id(&key)));
518 *ciphertext.last_mut().unwrap() ^= 1;
519 assert!(public_identity
520 .decrypt_with_ratchets(&ciphertext, &ring, true)
521 .is_err());
522 }
523
524 #[test]
525 fn test_identity_key_roundtrip() {
526 let mut rng = FixedRng::new(&(0..64).collect::<Vec<u8>>());
527 let id = Identity::new(&mut rng);
528 let prv_bytes = id.get_private_key().unwrap();
529 let id2 = Identity::from_private_key(&prv_bytes);
530 assert_eq!(id.get_public_key().unwrap(), id2.get_public_key().unwrap());
531 }
532
533 #[test]
534 fn test_identity_hash() {
535 let mut rng = FixedRng::new(&(0..64).collect::<Vec<u8>>());
536 let id = Identity::new(&mut rng);
537 let pub_key = id.get_public_key().unwrap();
538 let expected_hash = truncated_hash(&pub_key);
539 assert_eq!(*id.hash(), expected_hash);
540 }
541
542 #[test]
543 fn test_identity_encrypt_decrypt_roundtrip() {
544 let mut rng = FixedRng::new(&(0..128).collect::<Vec<u8>>());
545 let id = Identity::new(&mut rng);
546 let plaintext = b"Hello, Reticulum! This is a test of the encrypt/decrypt pipeline.";
547 let mut rng2 = FixedRng::new(&(128..255).collect::<Vec<u8>>());
548 let ciphertext = id.encrypt(plaintext, &mut rng2).unwrap();
549 let decrypted = id.decrypt(&ciphertext).unwrap();
550 assert_eq!(decrypted, plaintext);
551 }
552
553 #[test]
554 fn test_identity_encrypt_with_ratchet_targets_ratchet_key() {
555 let mut rng = FixedRng::new(&(0..128).collect::<Vec<u8>>());
556 let remote_identity = Identity::new(&mut rng);
557 let ratchet_prv = X25519PrivateKey::from_bytes(&[0x42; 32]);
558 let ratchet_pub = ratchet_prv.public_key().public_bytes();
559 let plaintext = b"ratcheted";
560
561 let mut encrypt_rng = FixedRng::new(&(128..255).collect::<Vec<u8>>());
562 let ciphertext = remote_identity
563 .encrypt_with_ratchet(plaintext, Some(&ratchet_pub), &mut encrypt_rng)
564 .unwrap();
565
566 let peer_pub_bytes: [u8; 32] = ciphertext[..32].try_into().unwrap();
567 let peer_pub = X25519PublicKey::from_bytes(&peer_pub_bytes);
568 let shared_key = ratchet_prv.exchange(&peer_pub);
569 let derived_key = hkdf::hkdf(
570 DERIVED_KEY_LENGTH,
571 &shared_key,
572 Some(remote_identity.hash()),
573 None,
574 )
575 .unwrap();
576 let token = Token::new(&derived_key).unwrap();
577 let decrypted = token.decrypt(&ciphertext[32..]).unwrap();
578
579 assert_eq!(decrypted, plaintext);
580 assert!(remote_identity.decrypt(&ciphertext).is_err());
581 }
582
583 #[test]
584 fn test_identity_sign_verify() {
585 let mut rng = FixedRng::new(&(0..64).collect::<Vec<u8>>());
586 let id = Identity::new(&mut rng);
587 let msg = b"Sign this message";
588 let sig = id.sign(msg).unwrap();
589 assert!(id.verify(&sig, msg));
590 assert!(!id.verify(&sig, b"Wrong message"));
591 }
592
593 #[test]
594 fn stamped_key_generation_until_meets_cost_when_never_stopped() {
595 let mut rng = crate::OsRng;
596 let material = b"link request material";
597 let (prv, public, value) =
598 generate_stamped_key_until(&mut rng, material, StampCost::from_whole(4), &mut || false)
599 .unwrap();
600 assert!(value.meets(StampCost::from_whole(4)));
601 assert_eq!(public.public_bytes(), prv.public_key().public_bytes());
602 assert_eq!(stamp_value(material, &public.public_bytes()), value);
603 }
604
605 #[test]
606 fn stamped_key_generation_stops_when_asked() {
607 let mut rng = crate::OsRng;
608 let mut polls = 0u32;
609 let result = generate_stamped_key_until(
610 &mut rng,
611 b"material",
612 StampCost::from_whole(100),
613 &mut || {
614 polls += 1;
615 polls > 3
616 },
617 );
618 assert!(result.is_none());
619 assert_eq!(polls, 4);
620 }
621
622 #[test]
623 fn stamped_identity_carries_required_value() {
624 let mut rng = crate::OsRng;
625 let id = Identity::new_stamped(&mut rng, StampCost::from_whole(4));
626 let value = id.get_value().unwrap();
627 assert!(value.meets(StampCost::from_whole(4)));
628
629 let public = id.get_public_key().unwrap();
630 assert_eq!(stamp_value(&public[32..], &public[..32]), value);
631 }
632}