clementine_core/
encryption.rs1use bitcoin::secp256k1::rand::{self, RngCore};
2use chacha20poly1305::{
3 aead::{Aead, KeyInit},
4 XChaCha20Poly1305, XNonce,
5};
6use x25519_dalek::{
7 EphemeralSecret, PublicKey as X25519PublicKey, StaticSecret as X25519StaticSecret,
8};
9
10const MIN_ENCRYPTED_LEN: usize = 56;
11const EPHEMERAL_PUBKEY_LEN: usize = 32;
12
13pub fn encrypt_bytes(recipient_pubkey: [u8; 32], message: &[u8]) -> Result<Vec<u8>, eyre::Report> {
28 let recipient_pubkey = X25519PublicKey::from(recipient_pubkey);
29
30 let ephemeral_secret = EphemeralSecret::random_from_rng(rand::thread_rng());
31 let ephemeral_public = X25519PublicKey::from(&ephemeral_secret);
32
33 let shared_secret = ephemeral_secret.diffie_hellman(&recipient_pubkey);
34 let cipher = XChaCha20Poly1305::new_from_slice(shared_secret.as_bytes())
35 .map_err(|e| eyre::eyre!("Failed to create cipher: {e}"))?;
36
37 let mut nonce_bytes = [0u8; 24];
38 rand::thread_rng().fill_bytes(&mut nonce_bytes);
39 let nonce = XNonce::from_slice(&nonce_bytes);
40
41 let ciphertext = cipher
42 .encrypt(nonce, message)
43 .map_err(|e| eyre::eyre!("Failed to encrypt message: {e}"))?;
44
45 let mut output = vec![];
46 output.extend_from_slice(ephemeral_public.as_bytes());
47 output.extend_from_slice(&nonce_bytes);
48 output.extend_from_slice(&ciphertext);
49 Ok(output)
50}
51
52pub fn decrypt_bytes(recipient_privkey: &[u8], encrypted: &[u8]) -> Result<Vec<u8>, eyre::Report> {
65 if encrypted.len() < MIN_ENCRYPTED_LEN {
66 return Err(eyre::eyre!("Invalid encrypted length"));
67 }
68
69 let ephemeral_pubkey_bytes: [u8; EPHEMERAL_PUBKEY_LEN] = encrypted[0..EPHEMERAL_PUBKEY_LEN]
70 .try_into()
71 .map_err(|_| eyre::eyre!("Invalid ephemeral public key length"))?;
72 let ephemeral_pubkey = X25519PublicKey::from(ephemeral_pubkey_bytes);
73 let nonce = XNonce::from_slice(&encrypted[EPHEMERAL_PUBKEY_LEN..MIN_ENCRYPTED_LEN]);
74 let ciphertext = &encrypted[MIN_ENCRYPTED_LEN..];
75
76 let recipient_priv_bytes: [u8; 32] = recipient_privkey
77 .try_into()
78 .map_err(|_| eyre::eyre!("Invalid recipient private key length"))?;
79 let recipient_secret = X25519StaticSecret::from(recipient_priv_bytes);
80
81 let shared_secret = recipient_secret.diffie_hellman(&ephemeral_pubkey);
82 let cipher = XChaCha20Poly1305::new_from_slice(shared_secret.as_bytes())
83 .map_err(|_| eyre::eyre!("Failed to create cipher"))?;
84
85 cipher
86 .decrypt(nonce, ciphertext)
87 .map_err(|_| eyre::eyre!("Failed to decrypt message"))
88}
89
90#[cfg(test)]
91mod tests {
92 use super::*;
93 use hex::FromHex;
94
95 #[test]
96 fn test_encrypt_decrypt() {
97 let privkey = <[u8; 32]>::from_hex(
99 "a80bc8cf095c2b37d4c6233114e0dd91f43d75de5602466232dbfcc1fc66c542",
100 )
101 .unwrap();
102 let pubkey = <[u8; 32]>::from_hex(
103 "025d32d10ec7b899df4eeb4d80918b7f0a1f2a28f6af24f71aa2a59c69c0d531",
104 )
105 .unwrap();
106
107 let message = b"Hello, Clementine!";
109
110 let encrypted = encrypt_bytes(pubkey, message).unwrap();
112
113 let decrypted = decrypt_bytes(&privkey, &encrypted).unwrap();
115
116 assert_eq!(message, decrypted.as_slice());
118 }
119}