Skip to main content

chaincraft_rust/crypto/
encrypt.rs

1//! Symmetric encryption (Fernet), matching Python crypto_primitives/encrypt.py
2
3use crate::error::{ChaincraftError, CryptoError, Result};
4use fernet::Fernet;
5use std::str;
6
7/// Symmetric encryption using Fernet (Python cryptography.fernet compatible)
8#[derive(Clone)]
9pub struct SymmetricEncryption {
10    fernet: Fernet,
11    key: String,
12}
13
14impl SymmetricEncryption {
15    /// Create with generated key, or use provided key (base64 URL-safe encoded)
16    pub fn new(key: Option<&str>) -> Result<Self> {
17        let key = match key {
18            None => Fernet::generate_key(),
19            Some(k) => k.to_string(),
20        };
21        let fernet = Fernet::new(&key)
22            .ok_or_else(|| ChaincraftError::Crypto(CryptoError::EncryptionFailed {
23                reason: "Invalid Fernet key".to_string(),
24            }))?;
25        Ok(Self { fernet, key })
26    }
27
28    /// Generate a new key and use it
29    pub fn generate_key(&mut self) -> Result<String> {
30        self.key = Fernet::generate_key();
31        self.fernet = Fernet::new(&self.key).ok_or_else(|| {
32            ChaincraftError::Crypto(CryptoError::KeyGenerationFailed {
33                reason: "Fernet key generation failed".to_string(),
34            })
35        })?;
36        Ok(self.key.clone())
37    }
38
39    /// Encrypt data (acts as "sign" in KeyCryptoPrimitive: encrypt-then-verify)
40    pub fn sign(&self, data: &[u8]) -> Result<Vec<u8>> {
41        let token = self.fernet.encrypt(data);
42        Ok(token.into_bytes())
43    }
44
45    /// Verify by decrypting and comparing to original data
46    pub fn verify(&self, data: &[u8], signature: &[u8]) -> Result<bool> {
47        let token = str::from_utf8(signature).map_err(|_| {
48            ChaincraftError::Crypto(CryptoError::DecryptionFailed {
49                reason: "Invalid UTF-8 token".to_string(),
50            })
51        })?;
52        let decrypted = self.fernet.decrypt(token).map_err(|_| {
53            ChaincraftError::Crypto(CryptoError::DecryptionFailed {
54                reason: "Decryption failed".to_string(),
55            })
56        })?;
57        Ok(decrypted == data)
58    }
59
60    /// Encrypt plaintext string, return base64 token as string
61    pub fn encrypt(&self, plaintext: &str) -> Result<String> {
62        let token = self.fernet.encrypt(plaintext.as_bytes());
63        Ok(token)
64    }
65
66    /// Decrypt ciphertext token, return plaintext string
67    pub fn decrypt(&self, ciphertext: &str) -> Result<String> {
68        let bytes = self.fernet.decrypt(ciphertext).map_err(|_| {
69            ChaincraftError::Crypto(CryptoError::DecryptionFailed {
70                reason: "Decryption failed".to_string(),
71            })
72        })?;
73        String::from_utf8(bytes).map_err(|e| {
74            ChaincraftError::Crypto(CryptoError::DecryptionFailed {
75                reason: format!("Invalid UTF-8: {}", e),
76            })
77        })
78    }
79
80    /// Return the key as string (base64)
81    pub fn get_key(&self) -> &str {
82        &self.key
83    }
84}
85
86impl Default for SymmetricEncryption {
87    fn default() -> Self {
88        Self::new(None).expect("Fernet default keygen")
89    }
90}
91
92#[cfg(test)]
93mod tests {
94    use super::*;
95
96    #[test]
97    fn test_encrypt_decrypt_roundtrip() {
98        let enc = SymmetricEncryption::new(None).unwrap();
99        let msg = "Hello, Chaincraft!";
100        let ciphertext = enc.encrypt(msg).unwrap();
101        let decrypted = enc.decrypt(&ciphertext).unwrap();
102        assert_eq!(decrypted, msg);
103    }
104
105    #[test]
106    fn test_sign_verify() {
107        let enc = SymmetricEncryption::new(None).unwrap();
108        let data = b"secret bytes";
109        let sig = enc.sign(data).unwrap();
110        assert!(enc.verify(data, &sig).unwrap());
111        assert!(!enc.verify(b"wrong", &sig).unwrap());
112    }
113
114    #[test]
115    fn test_with_provided_key() {
116        let key = Fernet::generate_key();
117        let enc1 = SymmetricEncryption::new(Some(&key)).unwrap();
118        let enc2 = SymmetricEncryption::new(Some(&key)).unwrap();
119        let ct = enc1.encrypt("test").unwrap();
120        let pt = enc2.decrypt(&ct).unwrap();
121        assert_eq!(pt, "test");
122    }
123}