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