Skip to main content

miden_crypto/aead/aead_eidos/
mod.rs

1//! Authenticated encryption built from the Eidos compression function.
2//!
3//! Encryption uses a counter-mode stream over canonical field-element limbs. Authentication uses
4//! a polynomial MAC over the quadratic extension of the Miden base field. A `(key, nonce)` pair
5//! must never be reused. See [`MAX_AUTHENTICATED_INPUT_FELTS`] and
6//! [`MAX_VERIFICATION_DEGREE_BUDGET_PER_KEY`] for the message and key-usage limits.
7
8use alloc::{format, string::ToString, vec::Vec};
9
10use miden_crypto_derive::{SilentDebug, SilentDisplay};
11use rand::{
12    CryptoRng,
13    distr::{Distribution, Uniform},
14};
15#[cfg(any(test, feature = "testing"))]
16use subtle::ConstantTimeEq;
17
18use super::{AeadScheme, DataType, EncryptionError};
19use crate::{
20    Felt, Word, ZERO,
21    field::PrimeCharacteristicRing,
22    utils::{
23        BudgetedReader, ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable,
24        SliceReader, bytes_to_elements_exact, bytes_to_elements_with_padding, elements_to_bytes,
25        padded_elements_to_bytes, read_sensitive_array,
26        zeroize::{Zeroize, ZeroizeOnDrop},
27    },
28};
29
30/// Low-level stream and authentication operations over expanded `u32` limbs.
31pub mod expanded;
32
33#[cfg(test)]
34mod tests;
35
36/// Number of field elements in an Eidos AEAD secret key.
37pub const SECRET_KEY_SIZE: usize = 4;
38
39/// Serialized size of an Eidos AEAD secret key.
40pub const SK_SIZE_BYTES: usize = SECRET_KEY_SIZE * Felt::NUM_BYTES;
41
42/// Number of field elements in an Eidos AEAD nonce.
43pub const NONCE_SIZE: usize = 4;
44
45/// Serialized size of an Eidos AEAD nonce.
46pub const NONCE_SIZE_BYTES: usize = NONCE_SIZE * Felt::NUM_BYTES;
47
48/// Number of field elements in an Eidos AEAD authentication tag.
49pub const AUTH_TAG_SIZE: usize = 2;
50
51/// Maximum number of base-field elements in the padded polynomial-MAC input.
52///
53/// This count includes the nonce, associated data, expanded ciphertext, encoded lengths, and
54/// padding. It limits each authentication polynomial to at most `2^27` quadratic-extension
55/// coefficients.
56pub const MAX_AUTHENTICATED_INPUT_FELTS: usize = 1 << 28;
57
58/// Maximum sum of polynomial-degree bounds across verification attempts under one key when
59/// targeting 96-bit authentication security.
60///
61/// For each attempt, the degree bound is the larger coefficient count of the submitted message and
62/// any message previously authenticated with the same nonce. Applications are responsible for
63/// enforcing this lifetime limit. Reusing a nonce is forbidden regardless of this budget.
64pub const MAX_VERIFICATION_DEGREE_BUDGET_PER_KEY: u64 = 1 << 28;
65
66/// Ciphertext and authentication data produced by [`SecretKey`].
67#[derive(Debug, Clone, PartialEq, Eq)]
68pub struct EncryptedData {
69    data_type: DataType,
70    ciphertext: Vec<Felt>,
71    auth_tag: AuthTag,
72    nonce: Nonce,
73}
74
75impl EncryptedData {
76    /// Constructs encrypted data after validating its expanded ciphertext representation.
77    pub fn from_parts(
78        data_type: DataType,
79        ciphertext: Vec<Felt>,
80        auth_tag: AuthTag,
81        nonce: Nonce,
82    ) -> Result<Self, EncryptionError> {
83        validate_ciphertext(&ciphertext)?;
84        Ok(Self { data_type, ciphertext, auth_tag, nonce })
85    }
86
87    /// Returns the representation of the plaintext before encryption.
88    pub fn data_type(&self) -> DataType {
89        self.data_type
90    }
91
92    /// Returns the expanded u32-limb ciphertext.
93    pub fn ciphertext(&self) -> &[Felt] {
94        &self.ciphertext
95    }
96
97    /// Returns the authentication tag.
98    pub fn auth_tag(&self) -> &AuthTag {
99        &self.auth_tag
100    }
101
102    /// Returns the nonce.
103    pub fn nonce(&self) -> &Nonce {
104        &self.nonce
105    }
106}
107
108/// Authentication tag over two elements of the Miden base field.
109#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
110pub struct AuthTag([Felt; AUTH_TAG_SIZE]);
111
112impl AuthTag {
113    /// Constructs a tag from its field elements.
114    pub fn new(elements: [Felt; AUTH_TAG_SIZE]) -> Self {
115        Self(elements)
116    }
117
118    /// Returns the field elements in this tag.
119    pub fn to_elements(self) -> [Felt; AUTH_TAG_SIZE] {
120        self.0
121    }
122}
123
124/// Eidos AEAD secret key.
125#[derive(Clone, SilentDebug, SilentDisplay)]
126pub struct SecretKey([Felt; SECRET_KEY_SIZE]);
127
128impl SecretKey {
129    /// Generates a key with the operating system's random-number generator.
130    #[cfg(feature = "std")]
131    #[allow(clippy::new_without_default)]
132    pub fn new() -> Self {
133        Self::with_rng(&mut rand::rng())
134    }
135
136    /// Generates a key with the supplied cryptographic random-number generator.
137    pub fn with_rng<R: CryptoRng + ?Sized>(rng: &mut R) -> Self {
138        Self(sample_felts(rng))
139    }
140
141    /// Constructs a key from four field elements.
142    pub fn from_elements(elements: [Felt; SECRET_KEY_SIZE]) -> Self {
143        Self(elements)
144    }
145
146    /// Returns the key as four field elements.
147    ///
148    /// The returned value contains secret key material and should be cleared after use.
149    pub fn to_elements(&self) -> [Felt; SECRET_KEY_SIZE] {
150        self.0
151    }
152
153    /// Encrypts field elements with a fresh random nonce.
154    #[cfg(feature = "std")]
155    pub fn encrypt_elements(&self, plaintext: &[Felt]) -> Result<EncryptedData, EncryptionError> {
156        self.encrypt_elements_with_associated_data(plaintext, &[])
157    }
158
159    /// Encrypts field elements and authenticates the supplied associated data.
160    #[cfg(feature = "std")]
161    pub fn encrypt_elements_with_associated_data(
162        &self,
163        plaintext: &[Felt],
164        associated_data: &[Felt],
165    ) -> Result<EncryptedData, EncryptionError> {
166        self.encrypt_elements_with_nonce(
167            plaintext,
168            associated_data,
169            Nonce::with_rng(&mut rand::rng()),
170        )
171    }
172
173    /// Encrypts field elements with an explicit nonce.
174    ///
175    /// The caller must ensure that the nonce has not been used with this key before.
176    pub fn encrypt_elements_with_nonce(
177        &self,
178        plaintext: &[Felt],
179        associated_data: &[Felt],
180        nonce: Nonce,
181    ) -> Result<EncryptedData, EncryptionError> {
182        validate_encryption_lengths(plaintext.len(), associated_data.len())?;
183        let authenticated_data = bind_data_type(DataType::Elements, associated_data)?;
184        let (ciphertext, tag) = expanded::encrypt_felts_expanded_authenticated(
185            self.as_word(),
186            nonce.as_word(),
187            &authenticated_data,
188            plaintext,
189        );
190
191        EncryptedData::from_parts(DataType::Elements, ciphertext, AuthTag(tag), nonce)
192    }
193
194    /// Encrypts bytes with a fresh random nonce.
195    #[cfg(feature = "std")]
196    pub fn encrypt_bytes(&self, plaintext: &[u8]) -> Result<EncryptedData, EncryptionError> {
197        self.encrypt_bytes_with_associated_data(plaintext, &[])
198    }
199
200    /// Encrypts bytes and authenticates the supplied associated data.
201    #[cfg(feature = "std")]
202    pub fn encrypt_bytes_with_associated_data(
203        &self,
204        plaintext: &[u8],
205        associated_data: &[u8],
206    ) -> Result<EncryptedData, EncryptionError> {
207        self.encrypt_bytes_with_nonce(plaintext, associated_data, Nonce::with_rng(&mut rand::rng()))
208    }
209
210    /// Encrypts bytes with an explicit nonce.
211    ///
212    /// The caller must ensure that the nonce has not been used with this key before.
213    pub fn encrypt_bytes_with_nonce(
214        &self,
215        plaintext: &[u8],
216        associated_data: &[u8],
217        nonce: Nonce,
218    ) -> Result<EncryptedData, EncryptionError> {
219        let plaintext = bytes_to_elements_with_padding(plaintext);
220        let associated_data = bytes_to_elements_with_padding(associated_data);
221        validate_encryption_lengths(plaintext.len(), associated_data.len())?;
222        let authenticated_data = bind_data_type(DataType::Bytes, &associated_data)?;
223        let (ciphertext, tag) = expanded::encrypt_felts_expanded_authenticated(
224            self.as_word(),
225            nonce.as_word(),
226            &authenticated_data,
227            &plaintext,
228        );
229
230        EncryptedData::from_parts(DataType::Bytes, ciphertext, AuthTag(tag), nonce)
231    }
232
233    /// Decrypts field elements after verifying their authentication tag.
234    pub fn decrypt_elements(
235        &self,
236        encrypted_data: &EncryptedData,
237    ) -> Result<Vec<Felt>, EncryptionError> {
238        self.decrypt_elements_with_associated_data(encrypted_data, &[])
239    }
240
241    /// Decrypts field elements after authenticating the ciphertext and associated data.
242    pub fn decrypt_elements_with_associated_data(
243        &self,
244        encrypted_data: &EncryptedData,
245        associated_data: &[Felt],
246    ) -> Result<Vec<Felt>, EncryptionError> {
247        ensure_data_type(encrypted_data, DataType::Elements)?;
248        let authenticated_data = bind_data_type(DataType::Elements, associated_data)?;
249        self.decrypt_felts(encrypted_data, &authenticated_data)
250    }
251
252    /// Decrypts bytes after verifying their authentication tag.
253    pub fn decrypt_bytes(
254        &self,
255        encrypted_data: &EncryptedData,
256    ) -> Result<Vec<u8>, EncryptionError> {
257        self.decrypt_bytes_with_associated_data(encrypted_data, &[])
258    }
259
260    /// Decrypts bytes after authenticating the ciphertext and associated data.
261    pub fn decrypt_bytes_with_associated_data(
262        &self,
263        encrypted_data: &EncryptedData,
264        associated_data: &[u8],
265    ) -> Result<Vec<u8>, EncryptionError> {
266        ensure_data_type(encrypted_data, DataType::Bytes)?;
267        let associated_data = bytes_to_elements_with_padding(associated_data);
268        let authenticated_data = bind_data_type(DataType::Bytes, &associated_data)?;
269        let plaintext = self.decrypt_felts(encrypted_data, &authenticated_data)?;
270        let bytes =
271            padded_elements_to_bytes(&plaintext).ok_or(EncryptionError::MalformedPadding)?;
272
273        if bytes_to_elements_with_padding(&bytes) != plaintext {
274            return Err(EncryptionError::MalformedPadding);
275        }
276        Ok(bytes)
277    }
278
279    fn decrypt_felts(
280        &self,
281        encrypted_data: &EncryptedData,
282        authenticated_data: &[Felt],
283    ) -> Result<Vec<Felt>, EncryptionError> {
284        validate_ciphertext(&encrypted_data.ciphertext)?;
285        expanded::checked_mac_input_len(authenticated_data.len(), encrypted_data.ciphertext.len())
286            .ok_or(EncryptionError::InputTooLong)?;
287        expanded::decrypt_felts_expanded_authenticated(
288            self.as_word(),
289            encrypted_data.nonce.as_word(),
290            authenticated_data,
291            &encrypted_data.ciphertext,
292            encrypted_data.auth_tag.0,
293        )
294        .ok_or(EncryptionError::InvalidAuthTag)
295    }
296
297    fn as_word(&self) -> Word {
298        Word::new(self.0)
299    }
300}
301
302#[cfg(any(test, feature = "testing"))]
303impl PartialEq for SecretKey {
304    fn eq(&self, other: &Self) -> bool {
305        self.0.iter().zip(other.0).fold(true, |equal, (left, right)| {
306            equal & bool::from(left.as_canonical_u64_ct().ct_eq(&right.as_canonical_u64_ct()))
307        })
308    }
309}
310
311#[cfg(any(test, feature = "testing"))]
312impl Eq for SecretKey {}
313
314impl Zeroize for SecretKey {
315    fn zeroize(&mut self) {
316        for element in &mut self.0 {
317            unsafe { core::ptr::write_volatile(element, ZERO) };
318        }
319        core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);
320    }
321}
322
323impl Drop for SecretKey {
324    fn drop(&mut self) {
325        self.zeroize();
326    }
327}
328
329impl ZeroizeOnDrop for SecretKey {}
330
331/// Nonce for one Eidos AEAD invocation.
332#[derive(Debug, Clone, Copy, PartialEq, Eq)]
333pub struct Nonce([Felt; NONCE_SIZE]);
334
335impl Nonce {
336    /// Generates a nonce with the supplied cryptographic random-number generator.
337    pub fn with_rng<R: CryptoRng + ?Sized>(rng: &mut R) -> Self {
338        Self(sample_felts(rng))
339    }
340
341    /// Returns the nonce as four field elements.
342    pub fn to_elements(self) -> [Felt; NONCE_SIZE] {
343        self.0
344    }
345
346    fn as_word(self) -> Word {
347        Word::new(self.0)
348    }
349}
350
351impl From<Word> for Nonce {
352    fn from(word: Word) -> Self {
353        Self(word.into())
354    }
355}
356
357impl From<[Felt; NONCE_SIZE]> for Nonce {
358    fn from(elements: [Felt; NONCE_SIZE]) -> Self {
359        Self(elements)
360    }
361}
362
363impl From<Nonce> for Word {
364    fn from(nonce: Nonce) -> Self {
365        nonce.as_word()
366    }
367}
368
369impl Serializable for SecretKey {
370    fn write_into<W: ByteWriter>(&self, target: &mut W) {
371        target.write_bytes(&elements_to_bytes(&self.0));
372    }
373}
374
375impl Deserializable for SecretKey {
376    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
377        let bytes = read_sensitive_array::<SK_SIZE_BYTES, _>(source)?;
378        let elements = bytes_to_elements_exact(bytes.as_slice())
379            .and_then(|elements| elements.try_into().ok())
380            .ok_or_else(|| {
381                DeserializationError::InvalidValue("malformed secret key".to_string())
382            })?;
383        Ok(Self(elements))
384    }
385}
386
387impl Serializable for Nonce {
388    fn write_into<W: ByteWriter>(&self, target: &mut W) {
389        target.write_bytes(&elements_to_bytes(&self.0));
390    }
391}
392
393impl Deserializable for Nonce {
394    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
395        let bytes: [u8; NONCE_SIZE_BYTES] = source.read_array()?;
396        let elements = bytes_to_elements_exact(&bytes)
397            .and_then(|elements| elements.try_into().ok())
398            .ok_or_else(|| DeserializationError::InvalidValue("malformed nonce".to_string()))?;
399        Ok(Self(elements))
400    }
401}
402
403impl Serializable for EncryptedData {
404    fn write_into<W: ByteWriter>(&self, target: &mut W) {
405        target.write_u8(self.data_type as u8);
406        self.ciphertext.write_into(target);
407        target.write_many(self.nonce.0);
408        target.write_many(self.auth_tag.0);
409    }
410}
411
412impl Deserializable for EncryptedData {
413    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
414        let data_type = source.read_u8()?.try_into().map_err(|_| {
415            DeserializationError::InvalidValue("invalid encrypted-data type".to_string())
416        })?;
417        let ciphertext = Vec::<Felt>::read_from(source)?;
418        let nonce = Nonce(source.read()?);
419        let auth_tag = AuthTag(source.read()?);
420
421        Self::from_parts(data_type, ciphertext, auth_tag, nonce).map_err(|error| {
422            DeserializationError::InvalidValue(format!("malformed Eidos ciphertext: {error}"))
423        })
424    }
425}
426
427/// Eidos authenticated-encryption implementation.
428pub struct AeadEidos;
429
430impl AeadScheme for AeadEidos {
431    const KEY_SIZE: usize = SK_SIZE_BYTES;
432
433    type Key = SecretKey;
434
435    fn key_from_bytes(bytes: &[u8]) -> Result<Self::Key, EncryptionError> {
436        if bytes.len() != SK_SIZE_BYTES {
437            return Err(EncryptionError::FailedOperation);
438        }
439        SecretKey::read_from_bytes_with_budget(bytes, SK_SIZE_BYTES)
440            .map_err(|_| EncryptionError::FailedOperation)
441    }
442
443    fn key_from_uniform_bytes(bytes: &[u8]) -> Result<Self::Key, EncryptionError> {
444        if bytes.len() != SK_SIZE_BYTES {
445            return Err(EncryptionError::FailedOperation);
446        }
447
448        let (chunks, remainder) = bytes.as_chunks::<{ Felt::NUM_BYTES }>();
449        debug_assert!(remainder.is_empty());
450        Ok(SecretKey::from_elements(core::array::from_fn(|i| {
451            Felt::from_u64(u64::from_le_bytes(chunks[i]))
452        })))
453    }
454
455    fn encrypt_bytes<R: CryptoRng>(
456        key: &Self::Key,
457        rng: &mut R,
458        plaintext: &[u8],
459        associated_data: &[u8],
460    ) -> Result<Vec<u8>, EncryptionError> {
461        let encrypted =
462            key.encrypt_bytes_with_nonce(plaintext, associated_data, Nonce::with_rng(rng))?;
463        Ok(encrypted.to_bytes())
464    }
465
466    fn decrypt_bytes_with_associated_data(
467        key: &Self::Key,
468        ciphertext: &[u8],
469        associated_data: &[u8],
470    ) -> Result<Vec<u8>, EncryptionError> {
471        let encrypted = read_encrypted_data_strict(ciphertext)?;
472        key.decrypt_bytes_with_associated_data(&encrypted, associated_data)
473    }
474
475    fn encrypt_elements<R: CryptoRng>(
476        key: &Self::Key,
477        rng: &mut R,
478        plaintext: &[Felt],
479        associated_data: &[Felt],
480    ) -> Result<Vec<u8>, EncryptionError> {
481        let encrypted =
482            key.encrypt_elements_with_nonce(plaintext, associated_data, Nonce::with_rng(rng))?;
483        Ok(encrypted.to_bytes())
484    }
485
486    fn decrypt_elements_with_associated_data(
487        key: &Self::Key,
488        ciphertext: &[u8],
489        associated_data: &[Felt],
490    ) -> Result<Vec<Felt>, EncryptionError> {
491        let encrypted = read_encrypted_data_strict(ciphertext)?;
492        key.decrypt_elements_with_associated_data(&encrypted, associated_data)
493    }
494}
495
496fn read_encrypted_data_strict(ciphertext: &[u8]) -> Result<EncryptedData, EncryptionError> {
497    let mut reader = BudgetedReader::new(SliceReader::new(ciphertext), ciphertext.len());
498    let encrypted =
499        EncryptedData::read_from(&mut reader).map_err(|_| EncryptionError::FailedOperation)?;
500    if reader.has_more_bytes() {
501        return Err(EncryptionError::FailedOperation);
502    }
503    Ok(encrypted)
504}
505
506fn sample_felts<R: CryptoRng + ?Sized, const N: usize>(rng: &mut R) -> [Felt; N] {
507    let distribution =
508        Uniform::new(0, Felt::ORDER).expect("the field order defines a valid sampling range");
509    core::array::from_fn(|_| Felt::new_unchecked(distribution.sample(rng)))
510}
511
512fn bind_data_type(
513    data_type: DataType,
514    associated_data: &[Felt],
515) -> Result<Vec<Felt>, EncryptionError> {
516    let capacity = associated_data.len().checked_add(1).ok_or(EncryptionError::InputTooLong)?;
517    u32::try_from(capacity).map_err(|_| EncryptionError::InputTooLong)?;
518
519    let mut bound = Vec::with_capacity(capacity);
520    bound.push(Felt::from_u32(data_type as u8 as u32));
521    bound.extend_from_slice(associated_data);
522    Ok(bound)
523}
524
525fn validate_encryption_lengths(
526    plaintext_len: usize,
527    associated_data_len: usize,
528) -> Result<(), EncryptionError> {
529    let ciphertext_len = plaintext_len.checked_mul(2).ok_or(EncryptionError::InputTooLong)?;
530    let authenticated_data_len =
531        associated_data_len.checked_add(1).ok_or(EncryptionError::InputTooLong)?;
532    expanded::checked_mac_input_len(authenticated_data_len, ciphertext_len)
533        .ok_or(EncryptionError::InputTooLong)?;
534    Ok(())
535}
536
537fn validate_ciphertext(ciphertext: &[Felt]) -> Result<(), EncryptionError> {
538    if !ciphertext.len().is_multiple_of(2)
539        || ciphertext.iter().any(|felt| felt.as_canonical_u64() > u64::from(u32::MAX))
540    {
541        return Err(EncryptionError::MalformedCiphertext);
542    }
543    // Every high-level invocation authenticates a one-Felt data-type marker, even when the caller
544    // supplies no associated data.
545    expanded::checked_mac_input_len(1, ciphertext.len()).ok_or(EncryptionError::InputTooLong)?;
546    Ok(())
547}
548
549fn ensure_data_type(
550    encrypted_data: &EncryptedData,
551    expected: DataType,
552) -> Result<(), EncryptionError> {
553    if encrypted_data.data_type != expected {
554        return Err(EncryptionError::InvalidDataType {
555            expected,
556            found: encrypted_data.data_type,
557        });
558    }
559    Ok(())
560}