agent_first_pay/rpc/
crypto.rs1use aes_gcm::aead::{Aead, OsRng};
2use aes_gcm::{AeadCore, Aes256Gcm, KeyInit};
3use sha2::{Digest, Sha256};
4
5pub struct Cipher {
6 key: [u8; 32],
7}
8
9impl Cipher {
10 pub fn from_secret(secret: &str) -> Self {
12 let hash = Sha256::digest(secret.as_bytes());
13 let mut key = [0u8; 32];
14 key.copy_from_slice(&hash);
15 Self { key }
16 }
17
18 pub fn encrypt(&self, plaintext: &[u8]) -> Result<(Vec<u8>, Vec<u8>), String> {
20 let compressed =
21 zstd::bulk::compress(plaintext, 1).map_err(|e| format!("zstd compress: {e}"))?;
22 let cipher =
23 Aes256Gcm::new_from_slice(&self.key).map_err(|e| format!("cipher init: {e}"))?;
24 let nonce = Aes256Gcm::generate_nonce(&mut OsRng);
25 let ciphertext = cipher
26 .encrypt(&nonce, compressed.as_slice())
27 .map_err(|e| format!("encrypt: {e}"))?;
28 Ok((nonce.to_vec(), ciphertext))
29 }
30
31 pub fn decrypt(&self, nonce: &[u8], ciphertext: &[u8]) -> Result<Vec<u8>, String> {
33 let cipher =
34 Aes256Gcm::new_from_slice(&self.key).map_err(|e| format!("cipher init: {e}"))?;
35 let nonce = aes_gcm::Nonce::from_slice(nonce);
36 let compressed = cipher
37 .decrypt(nonce, ciphertext)
38 .map_err(|e| format!("decrypt: {e}"))?;
39 zstd::bulk::decompress(&compressed, 64 * 1024 * 1024)
41 .map_err(|e| format!("zstd decompress: {e}"))
42 }
43}
44
45#[cfg(test)]
46mod tests {
47 use super::*;
48
49 #[test]
50 fn roundtrip() {
51 let cipher = Cipher::from_secret("test-password");
52 let plaintext = b"hello world";
53 let (nonce, ct) = cipher.encrypt(plaintext).ok().unwrap(); let decrypted = cipher.decrypt(&nonce, &ct).ok().unwrap(); assert_eq!(decrypted, plaintext);
56 }
57
58 #[test]
59 fn wrong_key_fails() {
60 let c1 = Cipher::from_secret("key-a");
61 let c2 = Cipher::from_secret("key-b");
62 let (nonce, ct) = c1.encrypt(b"secret").ok().unwrap(); assert!(c2.decrypt(&nonce, &ct).is_err());
64 }
65}