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};
20
21use crate::{
22    Felt,
23    aead::{AeadScheme, DataType, EncryptionError},
24    utils::{
25        ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable,
26        bytes_to_elements_exact, elements_to_bytes,
27    },
28    zeroize::{Zeroize, ZeroizeOnDrop},
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::Bytes,
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
318impl ZeroizeOnDrop for SecretKey {}
319
320// IES IMPLEMENTATION
321// ================================================================================================
322
323pub struct XChaCha;
324
325impl AeadScheme for XChaCha {
326    const KEY_SIZE: usize = SK_SIZE_BYTES;
327
328    type Key = SecretKey;
329
330    fn key_from_bytes(bytes: &[u8]) -> Result<Self::Key, EncryptionError> {
331        SecretKey::read_from_bytes(bytes).map_err(|_| EncryptionError::FailedOperation)
332    }
333
334    fn encrypt_bytes<R: rand::CryptoRng + rand::RngCore>(
335        key: &Self::Key,
336        rng: &mut R,
337        plaintext: &[u8],
338        associated_data: &[u8],
339    ) -> Result<Vec<u8>, EncryptionError> {
340        let nonce = Nonce::with_rng(rng);
341        let encrypted_data = key
342            .encrypt_bytes_with_nonce(plaintext, associated_data, nonce)
343            .map_err(|_| EncryptionError::FailedOperation)?;
344        Ok(encrypted_data.to_bytes())
345    }
346
347    fn decrypt_bytes_with_associated_data(
348        key: &Self::Key,
349        ciphertext: &[u8],
350        associated_data: &[u8],
351    ) -> Result<Vec<u8>, EncryptionError> {
352        let encrypted_data = &EncryptedData::read_from_bytes(ciphertext).unwrap();
353        key.decrypt_bytes_with_associated_data(encrypted_data, associated_data)
354            .map_err(|_| EncryptionError::FailedOperation)
355    }
356}
357
358// SERIALIZATION / DESERIALIZATION
359// ================================================================================================
360
361impl Serializable for SecretKey {
362    fn write_into<W: ByteWriter>(&self, target: &mut W) {
363        target.write_bytes(&self.0);
364    }
365}
366
367impl Deserializable for SecretKey {
368    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
369        let inner: [u8; SK_SIZE_BYTES] = source.read_array()?;
370
371        Ok(SecretKey(inner))
372    }
373}
374
375impl Serializable for Nonce {
376    fn write_into<W: ByteWriter>(&self, target: &mut W) {
377        target.write_bytes(&self.inner);
378    }
379}
380
381impl Deserializable for Nonce {
382    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
383        let inner: [u8; NONCE_SIZE_BYTES] = source.read_array()?;
384
385        Ok(Nonce { inner: inner.into() })
386    }
387}
388
389impl Serializable for EncryptedData {
390    fn write_into<W: ByteWriter>(&self, target: &mut W) {
391        target.write_u8(self.data_type as u8);
392        target.write_usize(self.ciphertext.len());
393        target.write_bytes(&self.ciphertext);
394        target.write_bytes(&self.nonce.inner);
395    }
396}
397
398impl Deserializable for EncryptedData {
399    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
400        let data_type_value: u8 = source.read_u8()?;
401        let data_type = data_type_value.try_into().map_err(|_| {
402            DeserializationError::InvalidValue("invalid data type value".to_string())
403        })?;
404
405        let ciphertext = Vec::<u8>::read_from(source)?;
406
407        let inner: [u8; NONCE_SIZE_BYTES] = source.read_array()?;
408
409        Ok(Self {
410            ciphertext,
411            nonce: Nonce { inner: inner.into() },
412            data_type,
413        })
414    }
415}