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        let mut rng = rand::rng();
185        let nonce = Nonce::with_rng(&mut rng);
186
187        self.encrypt_elements_with_nonce(data, associated_data, nonce)
188    }
189
190    /// Encrypts the provided sequence of field elements and authenticates both the ciphertext as
191    /// well as the provided associated data using this secret key and the specified nonce.
192    pub fn encrypt_elements_with_nonce(
193        &self,
194        data: &[Felt],
195        associated_data: &[Felt],
196        nonce: Nonce,
197    ) -> Result<EncryptedData, EncryptionError> {
198        let data_bytes = elements_to_bytes(data);
199        let ad_bytes = elements_to_bytes(associated_data);
200
201        let mut encrypted_data = self.encrypt_bytes_with_nonce(&data_bytes, &ad_bytes, nonce)?;
202        encrypted_data.data_type = DataType::Elements;
203        Ok(encrypted_data)
204    }
205
206    // BYTE DECRYPTION
207    // --------------------------------------------------------------------------------------------
208
209    /// Decrypts the provided encrypted data using this secret key.
210    ///
211    /// # Errors
212    /// Returns an error if decryption fails or if the underlying data was encrypted as elements
213    /// rather than as bytes.
214    pub fn decrypt_bytes(
215        &self,
216        encrypted_data: &EncryptedData,
217    ) -> Result<Vec<u8>, EncryptionError> {
218        self.decrypt_bytes_with_associated_data(encrypted_data, &[])
219    }
220
221    /// Decrypts the provided encrypted data given some associated data using this secret key.
222    ///
223    /// # Errors
224    /// Returns an error if decryption fails or if the underlying data was encrypted as elements
225    /// rather than as bytes.
226    pub fn decrypt_bytes_with_associated_data(
227        &self,
228        encrypted_data: &EncryptedData,
229        associated_data: &[u8],
230    ) -> Result<Vec<u8>, EncryptionError> {
231        if encrypted_data.data_type != DataType::Bytes {
232            return Err(EncryptionError::InvalidDataType {
233                expected: DataType::Bytes,
234                found: encrypted_data.data_type,
235            });
236        }
237        self.decrypt_bytes_with_associated_data_unchecked(encrypted_data, associated_data)
238    }
239
240    /// Decrypts the provided encrypted data given some associated data using this secret key.
241    fn decrypt_bytes_with_associated_data_unchecked(
242        &self,
243        encrypted_data: &EncryptedData,
244        associated_data: &[u8],
245    ) -> Result<Vec<u8>, EncryptionError> {
246        let EncryptedData { ciphertext, nonce, data_type: _ } = encrypted_data;
247        let payload = chacha20poly1305::aead::Payload { msg: ciphertext, aad: associated_data };
248
249        let cipher = XChaCha20Poly1305::new(&self.0.into());
250
251        cipher
252            .decrypt(&nonce.inner, payload)
253            .map_err(|_| EncryptionError::FailedOperation)
254    }
255
256    // ELEMENT DECRYPTION
257    // --------------------------------------------------------------------------------------------
258
259    /// Decrypts the provided encrypted data using this secret key.
260    ///
261    /// # Errors
262    /// Returns an error if decryption fails or if the underlying data was encrypted as bytes
263    /// rather than as field elements.
264    pub fn decrypt_elements(
265        &self,
266        encrypted_data: &EncryptedData,
267    ) -> Result<Vec<Felt>, EncryptionError> {
268        self.decrypt_elements_with_associated_data(encrypted_data, &[])
269    }
270
271    /// Decrypts the provided encrypted data, given some associated data, using this secret key.
272    ///
273    /// # Errors
274    /// Returns an error if decryption fails or if the underlying data was encrypted as bytes
275    /// rather than as field elements.
276    pub fn decrypt_elements_with_associated_data(
277        &self,
278        encrypted_data: &EncryptedData,
279        associated_data: &[Felt],
280    ) -> Result<Vec<Felt>, EncryptionError> {
281        if encrypted_data.data_type != DataType::Elements {
282            return Err(EncryptionError::InvalidDataType {
283                expected: DataType::Elements,
284                found: encrypted_data.data_type,
285            });
286        }
287
288        let ad_bytes = elements_to_bytes(associated_data);
289
290        let plaintext_bytes =
291            self.decrypt_bytes_with_associated_data_unchecked(encrypted_data, &ad_bytes)?;
292        match bytes_to_elements_exact(&plaintext_bytes) {
293            Some(elements) => Ok(elements),
294            None => Err(EncryptionError::FailedBytesToElementsConversion),
295        }
296    }
297}
298
299impl AsRef<[u8]> for SecretKey {
300    fn as_ref(&self) -> &[u8] {
301        &self.0
302    }
303}
304
305impl Drop for SecretKey {
306    fn drop(&mut self) {
307        self.zeroize();
308    }
309}
310
311impl Zeroize for SecretKey {
312    fn zeroize(&mut self) {
313        self.0.zeroize();
314    }
315}
316
317impl ZeroizeOnDrop for SecretKey {}
318
319// IES IMPLEMENTATION
320// ================================================================================================
321
322pub struct XChaCha;
323
324impl AeadScheme for XChaCha {
325    const KEY_SIZE: usize = SK_SIZE_BYTES;
326
327    type Key = SecretKey;
328
329    fn key_from_bytes(bytes: &[u8]) -> Result<Self::Key, EncryptionError> {
330        SecretKey::read_from_bytes(bytes).map_err(|_| EncryptionError::FailedOperation)
331    }
332
333    fn encrypt_bytes<R: rand::CryptoRng + rand::RngCore>(
334        key: &Self::Key,
335        rng: &mut R,
336        plaintext: &[u8],
337        associated_data: &[u8],
338    ) -> Result<Vec<u8>, EncryptionError> {
339        let nonce = Nonce::with_rng(rng);
340        let encrypted_data = key
341            .encrypt_bytes_with_nonce(plaintext, associated_data, nonce)
342            .map_err(|_| EncryptionError::FailedOperation)?;
343        Ok(encrypted_data.to_bytes())
344    }
345
346    fn decrypt_bytes_with_associated_data(
347        key: &Self::Key,
348        ciphertext: &[u8],
349        associated_data: &[u8],
350    ) -> Result<Vec<u8>, EncryptionError> {
351        let encrypted_data = &EncryptedData::read_from_bytes(ciphertext).unwrap();
352        key.decrypt_bytes_with_associated_data(encrypted_data, associated_data)
353            .map_err(|_| EncryptionError::FailedOperation)
354    }
355}
356
357// SERIALIZATION / DESERIALIZATION
358// ================================================================================================
359
360impl Serializable for SecretKey {
361    fn write_into<W: ByteWriter>(&self, target: &mut W) {
362        target.write_bytes(&self.0);
363    }
364}
365
366impl Deserializable for SecretKey {
367    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
368        let inner: [u8; SK_SIZE_BYTES] = source.read_array()?;
369
370        Ok(SecretKey(inner))
371    }
372}
373
374impl Serializable for Nonce {
375    fn write_into<W: ByteWriter>(&self, target: &mut W) {
376        target.write_bytes(&self.inner);
377    }
378}
379
380impl Deserializable for Nonce {
381    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
382        let inner: [u8; NONCE_SIZE_BYTES] = source.read_array()?;
383
384        Ok(Nonce { inner: inner.into() })
385    }
386}
387
388impl Serializable for EncryptedData {
389    fn write_into<W: ByteWriter>(&self, target: &mut W) {
390        target.write_u8(self.data_type as u8);
391        target.write_usize(self.ciphertext.len());
392        target.write_bytes(&self.ciphertext);
393        target.write_bytes(&self.nonce.inner);
394    }
395}
396
397impl Deserializable for EncryptedData {
398    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
399        let data_type_value: u8 = source.read_u8()?;
400        let data_type = data_type_value.try_into().map_err(|_| {
401            DeserializationError::InvalidValue("invalid data type value".to_string())
402        })?;
403
404        let ciphertext = Vec::<u8>::read_from(source)?;
405
406        let inner: [u8; NONCE_SIZE_BYTES] = source.read_array()?;
407
408        Ok(Self {
409            ciphertext,
410            nonce: Nonce { inner: inner.into() },
411            data_type,
412        })
413    }
414}