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, AeadCore, KeyInit},
18};
19use rand::{CryptoRng, RngCore};
20use zeroize::Zeroize;
21
22use crate::{
23    Felt,
24    aead::{DataType, EncryptionError},
25    utils::{
26        ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable,
27        bytes_to_elements_exact, elements_to_bytes,
28    },
29};
30
31#[cfg(test)]
32mod test;
33
34// CONSTANTS
35// ================================================================================================
36
37/// Size of nonce in bytes
38const NONCE_SIZE_BYTES: usize = 24;
39/// Size of secret key in bytes
40const SK_SIZE_BYTES: usize = 32;
41
42// STRUCTS AND IMPLEMENTATIONS
43// ================================================================================================
44
45/// Encrypted data
46#[derive(Debug, PartialEq, Eq)]
47pub struct EncryptedData {
48    /// Indicates the original format of the data before encryption
49    data_type: DataType,
50    /// The encrypted ciphertext, including the authentication tag
51    ciphertext: Vec<u8>,
52    /// The nonce used during encryption
53    nonce: Nonce,
54}
55
56/// A 192-bit nonce
57///
58/// Note: This should be drawn randomly from a CSPRNG.
59#[derive(Clone, Debug, PartialEq, Eq)]
60pub struct Nonce {
61    inner: chacha20poly1305::XNonce,
62}
63
64impl Nonce {
65    /// Creates a new random nonce using the provided random number generator
66    pub fn with_rng<R: RngCore + CryptoRng>(rng: &mut R) -> Self {
67        // we use a seedable CSPRNG and seed it with `rng`
68        // this is a work around the fact that the version of the `rand` dependency in our crate
69        // is different than the one used in the `chacha20poly1305`. This solution will
70        // no longer be needed once `chacha20poly1305` gets a new release with a version of
71        // the `rand` dependency matching ours
72        use chacha20poly1305::aead::rand_core::SeedableRng;
73        let mut seed = [0_u8; 32];
74        rand::RngCore::fill_bytes(rng, &mut seed);
75        let rng = rand_hc::Hc128Rng::from_seed(seed);
76
77        Nonce {
78            inner: XChaCha20Poly1305::generate_nonce(rng),
79        }
80    }
81
82    /// Creates a new nonce from the provided array of bytes
83    pub fn from_slice(bytes: &[u8; NONCE_SIZE_BYTES]) -> Self {
84        Nonce { inner: (*bytes).into() }
85    }
86}
87
88/// A 256-bit secret key
89#[derive(Debug, PartialEq, Eq)]
90pub struct SecretKey([u8; SK_SIZE_BYTES]);
91
92impl SecretKey {
93    // CONSTRUCTORS
94    // --------------------------------------------------------------------------------------------
95
96    /// Creates a new random secret key using the default random number generator
97    #[cfg(feature = "std")]
98    #[allow(clippy::new_without_default)]
99    pub fn new() -> Self {
100        let mut rng = rand::rng();
101        Self::with_rng(&mut rng)
102    }
103
104    /// Creates a new random secret key using the provided random number generator
105    pub fn with_rng<R: RngCore + CryptoRng>(rng: &mut R) -> Self {
106        // we use a seedable CSPRNG and seed it with `rng`
107        // this is a work around the fact that the version of the `rand` dependency in our crate
108        // is different than the one used in the `chacha20poly1305`. This solution will
109        // no longer be needed once `chacha20poly1305` gets a new release with a version of
110        // the `rand` dependency matching ours
111        use chacha20poly1305::aead::rand_core::SeedableRng;
112        let mut seed = [0_u8; 32];
113        rand::RngCore::fill_bytes(rng, &mut seed);
114        let rng = rand_hc::Hc128Rng::from_seed(seed);
115
116        let key = XChaCha20Poly1305::generate_key(rng);
117        Self(key.into())
118    }
119
120    // BYTE ENCRYPTION
121    // --------------------------------------------------------------------------------------------
122
123    /// Encrypts and authenticates the provided data using this secret key and a random
124    /// nonce
125    #[cfg(feature = "std")]
126    pub fn encrypt_bytes(&self, data: &[u8]) -> Result<EncryptedData, EncryptionError> {
127        self.encrypt_bytes_with_associated_data(data, &[])
128    }
129
130    /// Encrypts the provided data and authenticates both the ciphertext as well as
131    /// the provided associated data using this secret key and a random nonce
132    #[cfg(feature = "std")]
133    pub fn encrypt_bytes_with_associated_data(
134        &self,
135        data: &[u8],
136        associated_data: &[u8],
137    ) -> Result<EncryptedData, EncryptionError> {
138        let mut rng = rand::rng();
139        let nonce = Nonce::with_rng(&mut rng);
140
141        self.encrypt_bytes_with_nonce(data, associated_data, nonce)
142    }
143
144    /// Encrypts the provided data using this secret key and a specified nonce
145    pub fn encrypt_bytes_with_nonce(
146        &self,
147        data: &[u8],
148        associated_data: &[u8],
149        nonce: Nonce,
150    ) -> Result<EncryptedData, EncryptionError> {
151        let payload = chacha20poly1305::aead::Payload { msg: data, aad: associated_data };
152
153        let cipher = XChaCha20Poly1305::new(&self.0.into());
154
155        let ciphertext = cipher
156            .encrypt(&nonce.inner, payload)
157            .map_err(|_| EncryptionError::FailedOperation)?;
158
159        Ok(EncryptedData {
160            data_type: DataType::Bytes,
161            ciphertext,
162            nonce,
163        })
164    }
165
166    // ELEMENT ENCRYPTION
167    // --------------------------------------------------------------------------------------------
168
169    /// Encrypts and authenticates the provided sequence of field elements using this secret key
170    /// and a random nonce.
171    #[cfg(feature = "std")]
172    pub fn encrypt_elements(&self, data: &[Felt]) -> Result<EncryptedData, EncryptionError> {
173        self.encrypt_elements_with_associated_data(data, &[])
174    }
175
176    /// Encrypts the provided sequence of field elements and authenticates both the ciphertext as
177    /// well as the provided associated data using this secret key and a random nonce.
178    #[cfg(feature = "std")]
179    pub fn encrypt_elements_with_associated_data(
180        &self,
181        data: &[Felt],
182        associated_data: &[Felt],
183    ) -> Result<EncryptedData, EncryptionError> {
184        use rand::{SeedableRng, rngs::StdRng};
185        let mut rng = StdRng::from_os_rng();
186        let nonce = Nonce::with_rng(&mut rng);
187
188        self.encrypt_elements_with_nonce(data, associated_data, nonce)
189    }
190
191    /// Encrypts the provided sequence of field elements and authenticates both the ciphertext as
192    /// well as the provided associated data using this secret key and the specified nonce.
193    pub fn encrypt_elements_with_nonce(
194        &self,
195        data: &[Felt],
196        associated_data: &[Felt],
197        nonce: Nonce,
198    ) -> Result<EncryptedData, EncryptionError> {
199        let data_bytes = elements_to_bytes(data);
200        let ad_bytes = elements_to_bytes(associated_data);
201
202        let mut encrypted_data = self.encrypt_bytes_with_nonce(&data_bytes, &ad_bytes, nonce)?;
203        encrypted_data.data_type = DataType::Elements;
204        Ok(encrypted_data)
205    }
206
207    // BYTE DECRYPTION
208    // --------------------------------------------------------------------------------------------
209
210    /// Decrypts the provided encrypted data using this secret key.
211    ///
212    /// # Errors
213    /// Returns an error if decryption fails or if the underlying data was encrypted as elements
214    /// rather than as bytes.
215    pub fn decrypt_bytes(
216        &self,
217        encrypted_data: &EncryptedData,
218    ) -> Result<Vec<u8>, EncryptionError> {
219        self.decrypt_bytes_with_associated_data(encrypted_data, &[])
220    }
221
222    /// Decrypts the provided encrypted data given some associated data using this secret key.
223    ///
224    /// # Errors
225    /// Returns an error if decryption fails or if the underlying data was encrypted as elements
226    /// rather than as bytes.
227    pub fn decrypt_bytes_with_associated_data(
228        &self,
229        encrypted_data: &EncryptedData,
230        associated_data: &[u8],
231    ) -> Result<Vec<u8>, EncryptionError> {
232        if encrypted_data.data_type != DataType::Bytes {
233            return Err(EncryptionError::InvalidDataType {
234                expected: DataType::Elements,
235                found: encrypted_data.data_type,
236            });
237        }
238        self.decrypt_bytes_with_associated_data_unchecked(encrypted_data, associated_data)
239    }
240
241    /// Decrypts the provided encrypted data given some associated data using this secret key.
242    fn decrypt_bytes_with_associated_data_unchecked(
243        &self,
244        encrypted_data: &EncryptedData,
245        associated_data: &[u8],
246    ) -> Result<Vec<u8>, EncryptionError> {
247        let EncryptedData { ciphertext, nonce, data_type: _ } = encrypted_data;
248        let payload = chacha20poly1305::aead::Payload { msg: ciphertext, aad: associated_data };
249
250        let cipher = XChaCha20Poly1305::new(&self.0.into());
251
252        cipher
253            .decrypt(&nonce.inner, payload)
254            .map_err(|_| EncryptionError::FailedOperation)
255    }
256
257    // ELEMENT DECRYPTION
258    // --------------------------------------------------------------------------------------------
259
260    /// Decrypts the provided encrypted data using this secret key.
261    ///
262    /// # Errors
263    /// Returns an error if decryption fails or if the underlying data was encrypted as bytes
264    /// rather than as field elements.
265    pub fn decrypt_elements(
266        &self,
267        encrypted_data: &EncryptedData,
268    ) -> Result<Vec<Felt>, EncryptionError> {
269        self.decrypt_elements_with_associated_data(encrypted_data, &[])
270    }
271
272    /// Decrypts the provided encrypted data, given some associated data, using this secret key.
273    ///
274    /// # Errors
275    /// Returns an error if decryption fails or if the underlying data was encrypted as bytes
276    /// rather than as field elements.
277    pub fn decrypt_elements_with_associated_data(
278        &self,
279        encrypted_data: &EncryptedData,
280        associated_data: &[Felt],
281    ) -> Result<Vec<Felt>, EncryptionError> {
282        if encrypted_data.data_type != DataType::Elements {
283            return Err(EncryptionError::InvalidDataType {
284                expected: DataType::Elements,
285                found: encrypted_data.data_type,
286            });
287        }
288
289        let ad_bytes = elements_to_bytes(associated_data);
290
291        let plaintext_bytes =
292            self.decrypt_bytes_with_associated_data_unchecked(encrypted_data, &ad_bytes)?;
293        match bytes_to_elements_exact(&plaintext_bytes) {
294            Some(elements) => Ok(elements),
295            None => Err(EncryptionError::FailedBytesToElementsConversion),
296        }
297    }
298}
299
300impl AsRef<[u8]> for SecretKey {
301    fn as_ref(&self) -> &[u8] {
302        &self.0
303    }
304}
305
306impl Drop for SecretKey {
307    fn drop(&mut self) {
308        self.zeroize();
309    }
310}
311
312impl Zeroize for SecretKey {
313    fn zeroize(&mut self) {
314        self.0.zeroize();
315    }
316}
317
318// SERIALIZATION / DESERIALIZATION
319// ================================================================================================
320
321impl Serializable for SecretKey {
322    fn write_into<W: ByteWriter>(&self, target: &mut W) {
323        target.write_bytes(&self.0);
324    }
325}
326
327impl Deserializable for SecretKey {
328    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
329        let inner: [u8; SK_SIZE_BYTES] = source.read_array()?;
330
331        Ok(SecretKey(inner))
332    }
333}
334
335impl Serializable for Nonce {
336    fn write_into<W: ByteWriter>(&self, target: &mut W) {
337        target.write_bytes(self.inner.as_slice());
338    }
339}
340
341impl Deserializable for Nonce {
342    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
343        let inner: [u8; NONCE_SIZE_BYTES] = source.read_array()?;
344
345        Ok(Nonce {
346            inner: chacha20poly1305::XNonce::clone_from_slice(&inner),
347        })
348    }
349}
350
351impl Serializable for EncryptedData {
352    fn write_into<W: ByteWriter>(&self, target: &mut W) {
353        target.write_u8(self.data_type as u8);
354        target.write_usize(self.ciphertext.len());
355        target.write_bytes(&self.ciphertext);
356        target.write_bytes(&self.nonce.inner);
357    }
358}
359
360impl Deserializable for EncryptedData {
361    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
362        let data_type_value: u8 = source.read_u8()?;
363        let data_type = data_type_value.try_into().map_err(|_| {
364            DeserializationError::InvalidValue("invalid data type value".to_string())
365        })?;
366
367        let ciphertext_len = source.read_usize()?;
368        let ciphertext = source.read_vec(ciphertext_len)?;
369
370        let inner: [u8; NONCE_SIZE_BYTES] = source.read_array()?;
371
372        Ok(Self {
373            ciphertext,
374            nonce: Nonce { inner: inner.into() },
375            data_type,
376        })
377    }
378}