Skip to main content

miden_crypto/aead/xchacha/
mod.rs

1//! Cryptographic utilities for encrypting and decrypting data using XChaCha20-Poly1305 AEAD.
2//!
3//! This module provides secure encryption and decryption functionality. It uses
4//! the XChaCha20-Poly1305 authenticated encryption with associated data (AEAD) algorithm,
5//! which provides both confidentiality and integrity.
6//!
7//! # Key Components
8//!
9//! - [`SecretKey`]: A 256-bit secret key for encryption and decryption operations
10//! - [`Nonce`]: A 192-bit nonce that should be sampled randomly per encryption operation
11//! - [`EncryptedData`]: Encrypted data
12
13use alloc::{string::ToString, vec::Vec};
14
15use chacha20poly1305::{
16    XChaCha20Poly1305,
17    aead::{Aead, KeyInit, Payload},
18};
19use rand::CryptoRng;
20#[cfg(any(test, feature = "testing"))]
21use subtle::ConstantTimeEq;
22
23use crate::{
24    Felt,
25    aead::{AeadScheme, DataType, EncryptionError},
26    utils::{
27        ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable,
28        bytes_to_elements_exact, elements_to_bytes,
29        zeroize::{Zeroize, ZeroizeOnDrop},
30    },
31};
32
33#[cfg(test)]
34mod test;
35
36// CONSTANTS
37// ================================================================================================
38
39/// Size of nonce in bytes
40const NONCE_SIZE_BYTES: usize = 24;
41/// Size of secret key in bytes
42const SK_SIZE_BYTES: usize = 32;
43
44// STRUCTS AND IMPLEMENTATIONS
45// ================================================================================================
46
47/// Encrypted data
48#[derive(Debug, PartialEq, Eq)]
49pub struct EncryptedData {
50    /// Indicates the original format of the data before encryption
51    data_type: DataType,
52    /// The encrypted ciphertext, including the authentication tag
53    ciphertext: Vec<u8>,
54    /// The nonce used during encryption
55    nonce: Nonce,
56}
57
58/// A 192-bit nonce
59///
60/// Note: This should be drawn randomly from a CSPRNG.
61#[derive(Clone, Debug, PartialEq, Eq)]
62pub struct Nonce {
63    inner: chacha20poly1305::XNonce,
64}
65
66impl Nonce {
67    /// Creates a new random nonce using the provided random number generator
68    pub fn with_rng<R: CryptoRng>(rng: &mut R) -> Self {
69        let mut bytes = [0u8; NONCE_SIZE_BYTES];
70        rng.fill_bytes(&mut bytes);
71        Self::from_slice(&bytes)
72    }
73
74    /// Creates a new nonce from the provided array of bytes
75    pub fn from_slice(bytes: &[u8; NONCE_SIZE_BYTES]) -> Self {
76        Nonce { inner: (*bytes).into() }
77    }
78}
79
80/// A 256-bit secret key
81#[derive(Debug)]
82pub struct SecretKey([u8; SK_SIZE_BYTES]);
83
84#[cfg(any(test, feature = "testing"))]
85impl PartialEq for SecretKey {
86    fn eq(&self, other: &Self) -> bool {
87        // Constant-time comparison to avoid timing side channels in test builds.
88        bool::from(self.0.ct_eq(&other.0))
89    }
90}
91
92#[cfg(any(test, feature = "testing"))]
93impl Eq for SecretKey {}
94
95impl SecretKey {
96    // CONSTRUCTORS
97    // --------------------------------------------------------------------------------------------
98
99    /// Creates a new random secret key using the default random number generator
100    #[cfg(feature = "std")]
101    #[allow(clippy::new_without_default)]
102    pub fn new() -> Self {
103        let mut rng = rand::rng();
104        Self::with_rng(&mut rng)
105    }
106
107    /// Creates a new random secret key using the provided random number generator
108    pub fn with_rng<R: CryptoRng>(rng: &mut R) -> Self {
109        let mut key = [0u8; SK_SIZE_BYTES];
110        rng.fill_bytes(&mut key);
111        Self(key)
112    }
113
114    // BYTE ENCRYPTION
115    // --------------------------------------------------------------------------------------------
116
117    /// Encrypts and authenticates the provided data using this secret key and a random
118    /// nonce
119    #[cfg(feature = "std")]
120    pub fn encrypt_bytes(&self, data: &[u8]) -> Result<EncryptedData, EncryptionError> {
121        self.encrypt_bytes_with_associated_data(data, &[])
122    }
123
124    /// Encrypts the provided data and authenticates both the ciphertext as well as
125    /// the provided associated data using this secret key and a random nonce
126    #[cfg(feature = "std")]
127    pub fn encrypt_bytes_with_associated_data(
128        &self,
129        data: &[u8],
130        associated_data: &[u8],
131    ) -> Result<EncryptedData, EncryptionError> {
132        let mut rng = rand::rng();
133        let nonce = Nonce::with_rng(&mut rng);
134
135        self.encrypt_bytes_with_nonce(data, associated_data, nonce)
136    }
137
138    /// Encrypts the provided data using this secret key and a specified nonce
139    pub fn encrypt_bytes_with_nonce(
140        &self,
141        data: &[u8],
142        associated_data: &[u8],
143        nonce: Nonce,
144    ) -> Result<EncryptedData, EncryptionError> {
145        let payload = Payload { msg: data, aad: associated_data };
146
147        let cipher = XChaCha20Poly1305::new(&self.0.into());
148
149        let ciphertext = cipher
150            .encrypt(&nonce.inner, payload)
151            .map_err(|_| EncryptionError::FailedOperation)?;
152
153        Ok(EncryptedData {
154            data_type: DataType::Bytes,
155            ciphertext,
156            nonce,
157        })
158    }
159
160    // ELEMENT ENCRYPTION
161    // --------------------------------------------------------------------------------------------
162
163    /// Encrypts and authenticates the provided sequence of field elements using this secret key
164    /// and a random nonce.
165    #[cfg(feature = "std")]
166    pub fn encrypt_elements(&self, data: &[Felt]) -> Result<EncryptedData, EncryptionError> {
167        self.encrypt_elements_with_associated_data(data, &[])
168    }
169
170    /// Encrypts the provided sequence of field elements and authenticates both the ciphertext as
171    /// well as the provided associated data using this secret key and a random nonce.
172    #[cfg(feature = "std")]
173    pub fn encrypt_elements_with_associated_data(
174        &self,
175        data: &[Felt],
176        associated_data: &[Felt],
177    ) -> Result<EncryptedData, EncryptionError> {
178        let mut rng = rand::rng();
179        let nonce = Nonce::with_rng(&mut rng);
180
181        self.encrypt_elements_with_nonce(data, associated_data, nonce)
182    }
183
184    /// Encrypts the provided sequence of field elements and authenticates both the ciphertext as
185    /// well as the provided associated data using this secret key and the specified nonce.
186    pub fn encrypt_elements_with_nonce(
187        &self,
188        data: &[Felt],
189        associated_data: &[Felt],
190        nonce: Nonce,
191    ) -> Result<EncryptedData, EncryptionError> {
192        let data_bytes = elements_to_bytes(data);
193        let ad_bytes = elements_to_bytes(associated_data);
194
195        let mut encrypted_data = self.encrypt_bytes_with_nonce(&data_bytes, &ad_bytes, nonce)?;
196        encrypted_data.data_type = DataType::Elements;
197        Ok(encrypted_data)
198    }
199
200    // BYTE DECRYPTION
201    // --------------------------------------------------------------------------------------------
202
203    /// Decrypts the provided encrypted data using this secret key.
204    ///
205    /// # Errors
206    /// Returns an error if decryption fails or if the underlying data was encrypted as elements
207    /// rather than as bytes.
208    pub fn decrypt_bytes(
209        &self,
210        encrypted_data: &EncryptedData,
211    ) -> Result<Vec<u8>, EncryptionError> {
212        self.decrypt_bytes_with_associated_data(encrypted_data, &[])
213    }
214
215    /// Decrypts the provided encrypted data given some associated data using this secret key.
216    ///
217    /// # Errors
218    /// Returns an error if decryption fails or if the underlying data was encrypted as elements
219    /// rather than as bytes.
220    pub fn decrypt_bytes_with_associated_data(
221        &self,
222        encrypted_data: &EncryptedData,
223        associated_data: &[u8],
224    ) -> Result<Vec<u8>, EncryptionError> {
225        if encrypted_data.data_type != DataType::Bytes {
226            return Err(EncryptionError::InvalidDataType {
227                expected: DataType::Bytes,
228                found: encrypted_data.data_type,
229            });
230        }
231        self.decrypt_bytes_with_associated_data_unchecked(encrypted_data, associated_data)
232    }
233
234    /// Decrypts the provided encrypted data given some associated data using this secret key.
235    fn decrypt_bytes_with_associated_data_unchecked(
236        &self,
237        encrypted_data: &EncryptedData,
238        associated_data: &[u8],
239    ) -> Result<Vec<u8>, EncryptionError> {
240        let EncryptedData { ciphertext, nonce, data_type: _ } = encrypted_data;
241        let payload = Payload { msg: ciphertext, aad: associated_data };
242
243        let cipher = XChaCha20Poly1305::new(&self.0.into());
244
245        cipher
246            .decrypt(&nonce.inner, payload)
247            .map_err(|_| EncryptionError::FailedOperation)
248    }
249
250    // ELEMENT DECRYPTION
251    // --------------------------------------------------------------------------------------------
252
253    /// Decrypts the provided encrypted data using this secret key.
254    ///
255    /// # Errors
256    /// Returns an error if decryption fails or if the underlying data was encrypted as bytes
257    /// rather than as field elements.
258    pub fn decrypt_elements(
259        &self,
260        encrypted_data: &EncryptedData,
261    ) -> Result<Vec<Felt>, EncryptionError> {
262        self.decrypt_elements_with_associated_data(encrypted_data, &[])
263    }
264
265    /// Decrypts the provided encrypted data, given some associated data, using this secret key.
266    ///
267    /// # Errors
268    /// Returns an error if decryption fails or if the underlying data was encrypted as bytes
269    /// rather than as field elements.
270    pub fn decrypt_elements_with_associated_data(
271        &self,
272        encrypted_data: &EncryptedData,
273        associated_data: &[Felt],
274    ) -> Result<Vec<Felt>, EncryptionError> {
275        if encrypted_data.data_type != DataType::Elements {
276            return Err(EncryptionError::InvalidDataType {
277                expected: DataType::Elements,
278                found: encrypted_data.data_type,
279            });
280        }
281
282        let ad_bytes = elements_to_bytes(associated_data);
283
284        let plaintext_bytes =
285            self.decrypt_bytes_with_associated_data_unchecked(encrypted_data, &ad_bytes)?;
286        match bytes_to_elements_exact(&plaintext_bytes) {
287            Some(elements) => Ok(elements),
288            None => Err(EncryptionError::FailedBytesToElementsConversion),
289        }
290    }
291}
292
293impl AsRef<[u8]> for SecretKey {
294    fn as_ref(&self) -> &[u8] {
295        &self.0
296    }
297}
298
299impl Drop for SecretKey {
300    fn drop(&mut self) {
301        self.zeroize();
302    }
303}
304
305impl Zeroize for SecretKey {
306    fn zeroize(&mut self) {
307        self.0.zeroize();
308    }
309}
310
311impl ZeroizeOnDrop for SecretKey {}
312
313// IES IMPLEMENTATION
314// ================================================================================================
315
316pub struct XChaCha;
317
318impl AeadScheme for XChaCha {
319    const KEY_SIZE: usize = SK_SIZE_BYTES;
320
321    type Key = SecretKey;
322
323    fn key_from_bytes(bytes: &[u8]) -> Result<Self::Key, EncryptionError> {
324        if bytes.len() != SK_SIZE_BYTES {
325            return Err(EncryptionError::FailedOperation);
326        }
327
328        SecretKey::read_from_bytes_with_budget(bytes, SK_SIZE_BYTES)
329            .map_err(|_| EncryptionError::FailedOperation)
330    }
331
332    fn encrypt_bytes<R: CryptoRng>(
333        key: &Self::Key,
334        rng: &mut R,
335        plaintext: &[u8],
336        associated_data: &[u8],
337    ) -> Result<Vec<u8>, EncryptionError> {
338        let nonce = Nonce::with_rng(rng);
339        let encrypted_data = key
340            .encrypt_bytes_with_nonce(plaintext, associated_data, nonce)
341            .map_err(|_| EncryptionError::FailedOperation)?;
342        Ok(encrypted_data.to_bytes())
343    }
344
345    fn decrypt_bytes_with_associated_data(
346        key: &Self::Key,
347        ciphertext: &[u8],
348        associated_data: &[u8],
349    ) -> Result<Vec<u8>, EncryptionError> {
350        let encrypted_data =
351            EncryptedData::read_from_bytes_with_budget(ciphertext, ciphertext.len())
352                .map_err(|_| EncryptionError::FailedOperation)?;
353
354        key.decrypt_bytes_with_associated_data(&encrypted_data, associated_data)
355            .map_err(|_| EncryptionError::FailedOperation)
356    }
357}
358
359// SERIALIZATION / DESERIALIZATION
360// ================================================================================================
361
362impl Serializable for SecretKey {
363    fn write_into<W: ByteWriter>(&self, target: &mut W) {
364        target.write_bytes(&self.0);
365    }
366}
367
368impl Deserializable for SecretKey {
369    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
370        let inner: [u8; SK_SIZE_BYTES] = source.read_array()?;
371
372        Ok(SecretKey(inner))
373    }
374}
375
376impl Serializable for Nonce {
377    fn write_into<W: ByteWriter>(&self, target: &mut W) {
378        target.write_bytes(&self.inner);
379    }
380}
381
382impl Deserializable for Nonce {
383    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
384        let inner: [u8; NONCE_SIZE_BYTES] = source.read_array()?;
385
386        Ok(Nonce { inner: inner.into() })
387    }
388}
389
390impl Serializable for EncryptedData {
391    fn write_into<W: ByteWriter>(&self, target: &mut W) {
392        target.write_u8(self.data_type as u8);
393        target.write_usize(self.ciphertext.len());
394        target.write_bytes(&self.ciphertext);
395        target.write_bytes(&self.nonce.inner);
396    }
397}
398
399impl Deserializable for EncryptedData {
400    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
401        let data_type_value: u8 = source.read_u8()?;
402        let data_type = data_type_value.try_into().map_err(|_| {
403            DeserializationError::InvalidValue("invalid data type value".to_string())
404        })?;
405
406        let ciphertext = Vec::<u8>::read_from(source)?;
407
408        let inner: [u8; NONCE_SIZE_BYTES] = source.read_array()?;
409
410        Ok(Self {
411            ciphertext,
412            nonce: Nonce { inner: inner.into() },
413            data_type,
414        })
415    }
416}