1use chacha20poly1305::{
6 aead::{Aead, AeadCore, KeyInit, OsRng},
7 XChaCha20Poly1305, XNonce as InternalXNonce,
8};
9use zeroize::Zeroize;
10use crate::{CryptoError, Result};
11
12#[derive(Clone, Zeroize)]
14#[zeroize(drop)]
15pub struct XChaChaKey([u8; 32]);
16
17impl XChaChaKey {
18 pub fn generate() -> Self {
20 let key = XChaCha20Poly1305::generate_key(&mut OsRng);
21 Self(key.into())
22 }
23
24 pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
26 if bytes.len() != 32 {
27 return Err(CryptoError::InvalidKeyLength {
28 expected: 32,
29 actual: bytes.len(),
30 });
31 }
32 let mut key = [0u8; 32];
33 key.copy_from_slice(bytes);
34 Ok(Self(key))
35 }
36
37 pub fn as_bytes(&self) -> &[u8] {
39 &self.0
40 }
41}
42
43#[derive(Clone, Copy)]
45pub struct XChaChaNonce([u8; 24]);
46
47impl XChaChaNonce {
48 pub fn generate() -> Self {
50 let nonce = XChaCha20Poly1305::generate_nonce(&mut OsRng);
51 Self(nonce.into())
52 }
53
54 pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
56 if bytes.len() != 24 {
57 return Err(CryptoError::InvalidNonceLength {
58 expected: 24,
59 actual: bytes.len(),
60 });
61 }
62 let mut nonce = [0u8; 24];
63 nonce.copy_from_slice(bytes);
64 Ok(Self(nonce))
65 }
66
67 pub fn as_bytes(&self) -> &[u8] {
69 &self.0
70 }
71}
72
73pub struct XChaChaCrypto;
75
76impl XChaChaCrypto {
77 pub fn encrypt(
88 key: &XChaChaKey,
89 nonce: &XChaChaNonce,
90 plaintext: &[u8],
91 associated_data: Option<&[u8]>,
92 ) -> Result<Vec<u8>> {
93 let cipher = XChaCha20Poly1305::new(key.as_bytes().into());
94 let nonce = InternalXNonce::from_slice(nonce.as_bytes());
95 let aad = associated_data.unwrap_or(&[]);
96
97 cipher
98 .encrypt(nonce, aead::Payload { msg: plaintext, aad })
99 .map_err(|e| CryptoError::EncryptionError(e.to_string()))
100 }
101
102 pub fn decrypt(
113 key: &XChaChaKey,
114 nonce: &XChaChaNonce,
115 ciphertext: &[u8],
116 associated_data: Option<&[u8]>,
117 ) -> Result<Vec<u8>> {
118 let cipher = XChaCha20Poly1305::new(key.as_bytes().into());
119 let nonce = InternalXNonce::from_slice(nonce.as_bytes());
120 let aad = associated_data.unwrap_or(&[]);
121
122 cipher
123 .decrypt(nonce, aead::Payload { msg: ciphertext, aad })
124 .map_err(|e| CryptoError::DecryptionError(e.to_string()))
125 }
126
127 pub fn encrypt_with_random_nonce(
137 key: &XChaChaKey,
138 plaintext: &[u8],
139 associated_data: Option<&[u8]>,
140 ) -> Result<(XChaChaNonce, Vec<u8>)> {
141 let nonce = XChaChaNonce::generate();
142 let ciphertext = Self::encrypt(key, &nonce, plaintext, associated_data)?;
143 Ok((nonce, ciphertext))
144 }
145}
146
147#[cfg(test)]
148mod tests {
149 use super::*;
150
151 #[test]
152 fn test_key_generation() {
153 let key = XChaChaKey::generate();
154 assert_eq!(key.as_bytes().len(), 32);
155 }
156
157 #[test]
158 fn test_nonce_generation() {
159 let nonce = XChaChaNonce::generate();
160 assert_eq!(nonce.as_bytes().len(), 24);
161 }
162
163 #[test]
164 fn test_encrypt_decrypt() {
165 let key = XChaChaKey::generate();
166 let plaintext = b"Hello, World!";
167 let aad = b"Additional data";
168
169 let (nonce, ciphertext) = XChaChaCrypto::encrypt_with_random_nonce(
171 &key,
172 plaintext,
173 Some(aad),
174 ).unwrap();
175
176 let decrypted = XChaChaCrypto::decrypt(
177 &key,
178 &nonce,
179 &ciphertext,
180 Some(aad),
181 ).unwrap();
182
183 assert_eq!(plaintext, &decrypted[..]);
184 }
185
186 #[test]
187 fn test_encrypt_decrypt_no_aad() {
188 let key = XChaChaKey::generate();
189 let nonce = XChaChaNonce::generate();
190 let plaintext = b"Secret message";
191
192 let ciphertext = XChaChaCrypto::encrypt(&key, &nonce, plaintext, None).unwrap();
193 let decrypted = XChaChaCrypto::decrypt(&key, &nonce, &ciphertext, None).unwrap();
194
195 assert_eq!(plaintext, &decrypted[..]);
196 }
197
198 #[test]
199 fn test_wrong_key_fails() {
200 let key1 = XChaChaKey::generate();
201 let key2 = XChaChaKey::generate();
202 let nonce = XChaChaNonce::generate();
203 let plaintext = b"Secret";
204
205 let ciphertext = XChaChaCrypto::encrypt(&key1, &nonce, plaintext, None).unwrap();
206 let result = XChaChaCrypto::decrypt(&key2, &nonce, &ciphertext, None);
207
208 assert!(result.is_err());
209 }
210}