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};
15use subtle::{Choice, ConstantTimeEq};
16
17use super::{AeadScheme, DataType, EncryptionError};
18use crate::{
19    Felt, Word, ZERO,
20    field::PrimeCharacteristicRing,
21    utils::{
22        BINARY_CHUNK_SIZE, BudgetedReader, ByteReader, ByteWriter, Deserializable,
23        DeserializationError, Serializable, SliceReader, bytes_to_elements_exact,
24        bytes_to_elements_with_padding, elements_to_bytes, padded_elements_to_bytes,
25        read_sensitive_array,
26        zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing},
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/// Degree budget for failed and pending MAC verification attempts under one key.
59///
60/// Senders must use a fresh nonce for each message. Before checking a tag, reserve the larger
61/// polynomial degree of the submitted message and the sender's message under that nonce, if any.
62/// The degree is `padded_input_len / 2 + 1`, with length measured in Felts. If the sender's message
63/// length is unknown, use a size limit enforced for all messages sent and received under the key,
64/// or the maximum degree `2^27 + 1`.
65///
66/// Keep the charge on a mismatch; release it on a match. Refuse checks whose reservation would
67/// exceed the remaining budget. Reservations must be atomic and shared across all receivers using
68/// the key. The library does not track this budget.
69///
70/// With unique sender nonces and independent ideal session keys, the forgery bound is
71/// `(512 / 511) * 2^-96`. The concrete bound also includes the Eidos PRF distinguishing advantage
72/// and any nonce-collision probability. See the
73/// [Eidos AEAD usage limits](https://docs.miden.xyz/miden-vm/design/eidos-aead#usage-limits).
74pub const MAX_VERIFICATION_DEGREE_BUDGET_PER_KEY: u64 = 1 << 28;
75
76/// Ciphertext and authentication data produced by [`SecretKey`].
77#[derive(Debug, Clone, PartialEq, Eq)]
78#[cfg_attr(
79    all(feature = "arbitrary", test),
80    miden_test_serialization_macros::serialization_test
81)]
82pub struct EncryptedData {
83    data_type: DataType,
84    ciphertext: Vec<Felt>,
85    auth_tag: AuthTag,
86    nonce: Nonce,
87}
88
89impl EncryptedData {
90    /// Constructs encrypted data after validating its expanded ciphertext representation.
91    pub fn from_parts(
92        data_type: DataType,
93        ciphertext: Vec<Felt>,
94        auth_tag: AuthTag,
95        nonce: Nonce,
96    ) -> Result<Self, EncryptionError> {
97        validate_ciphertext(&ciphertext)?;
98        Ok(Self { data_type, ciphertext, auth_tag, nonce })
99    }
100
101    /// Returns the representation of the plaintext before encryption.
102    pub fn data_type(&self) -> DataType {
103        self.data_type
104    }
105
106    /// Returns the expanded u32-limb ciphertext.
107    pub fn ciphertext(&self) -> &[Felt] {
108        &self.ciphertext
109    }
110
111    /// Returns the authentication tag.
112    pub fn auth_tag(&self) -> &AuthTag {
113        &self.auth_tag
114    }
115
116    /// Returns the nonce.
117    pub fn nonce(&self) -> &Nonce {
118        &self.nonce
119    }
120}
121
122/// Authentication tag over two elements of the Miden base field.
123///
124/// Equality compares both field elements in constant time.
125#[derive(Debug, Default, Clone, Copy)]
126pub struct AuthTag([Felt; AUTH_TAG_SIZE]);
127
128impl AuthTag {
129    /// Constructs a tag from its field elements.
130    pub fn new(elements: [Felt; AUTH_TAG_SIZE]) -> Self {
131        Self(elements)
132    }
133
134    /// Returns the field elements in this tag.
135    pub fn to_elements(self) -> [Felt; AUTH_TAG_SIZE] {
136        self.0
137    }
138}
139
140impl ConstantTimeEq for AuthTag {
141    fn ct_eq(&self, other: &Self) -> Choice {
142        self.0.iter().zip(other.0).fold(Choice::from(1), |equal, (left, right)| {
143            equal & left.as_canonical_u64_ct().ct_eq(&right.as_canonical_u64_ct())
144        })
145    }
146}
147
148impl PartialEq for AuthTag {
149    fn eq(&self, other: &Self) -> bool {
150        self.ct_eq(other).into()
151    }
152}
153
154impl Eq for AuthTag {}
155
156/// Eidos AEAD secret key.
157#[derive(Clone, SilentDebug, SilentDisplay)]
158#[cfg_attr(
159    all(feature = "arbitrary", test),
160    miden_test_serialization_macros::serialization_test
161)]
162pub struct SecretKey([Felt; SECRET_KEY_SIZE]);
163
164impl SecretKey {
165    /// Generates a key with the operating system's random-number generator.
166    #[cfg(feature = "std")]
167    #[allow(clippy::new_without_default)]
168    pub fn new() -> Self {
169        Self::with_rng(&mut rand::rng())
170    }
171
172    /// Generates a key with the supplied cryptographic random-number generator.
173    pub fn with_rng<R: CryptoRng + ?Sized>(rng: &mut R) -> Self {
174        Self(sample_felts(rng))
175    }
176
177    /// Constructs a key from four field elements.
178    pub fn from_elements(elements: [Felt; SECRET_KEY_SIZE]) -> Self {
179        Self(elements)
180    }
181
182    /// Returns the key as four field elements.
183    ///
184    /// The returned value contains secret key material and should be cleared after use.
185    pub fn to_elements(&self) -> [Felt; SECRET_KEY_SIZE] {
186        self.0
187    }
188
189    /// Encrypts field elements with a fresh random nonce.
190    #[cfg(feature = "std")]
191    pub fn encrypt_elements(&self, plaintext: &[Felt]) -> Result<EncryptedData, EncryptionError> {
192        self.encrypt_elements_with_associated_data(plaintext, &[])
193    }
194
195    /// Encrypts field elements and authenticates the supplied associated data.
196    #[cfg(feature = "std")]
197    pub fn encrypt_elements_with_associated_data(
198        &self,
199        plaintext: &[Felt],
200        associated_data: &[Felt],
201    ) -> Result<EncryptedData, EncryptionError> {
202        self.encrypt_elements_with_nonce(
203            plaintext,
204            associated_data,
205            Nonce::with_rng(&mut rand::rng()),
206        )
207    }
208
209    /// Encrypts field elements with an explicit nonce.
210    ///
211    /// The caller must ensure that the nonce has not been used with this key before.
212    pub fn encrypt_elements_with_nonce(
213        &self,
214        plaintext: &[Felt],
215        associated_data: &[Felt],
216        nonce: Nonce,
217    ) -> Result<EncryptedData, EncryptionError> {
218        validate_encryption_lengths(plaintext.len(), associated_data.len())?;
219        let authenticated_data = bind_data_type(DataType::Elements, associated_data)?;
220        let (ciphertext, tag) = expanded::encrypt_felts_expanded_authenticated(
221            self.as_word(),
222            nonce.as_word(),
223            &authenticated_data,
224            plaintext,
225        );
226
227        EncryptedData::from_parts(DataType::Elements, ciphertext, AuthTag(tag), nonce)
228    }
229
230    /// Encrypts bytes with a fresh random nonce.
231    #[cfg(feature = "std")]
232    pub fn encrypt_bytes(&self, plaintext: &[u8]) -> Result<EncryptedData, EncryptionError> {
233        self.encrypt_bytes_with_associated_data(plaintext, &[])
234    }
235
236    /// Encrypts bytes and authenticates the supplied associated data.
237    #[cfg(feature = "std")]
238    pub fn encrypt_bytes_with_associated_data(
239        &self,
240        plaintext: &[u8],
241        associated_data: &[u8],
242    ) -> Result<EncryptedData, EncryptionError> {
243        self.encrypt_bytes_with_nonce(plaintext, associated_data, Nonce::with_rng(&mut rand::rng()))
244    }
245
246    /// Encrypts bytes with an explicit nonce.
247    ///
248    /// The caller must ensure that the nonce has not been used with this key before.
249    pub fn encrypt_bytes_with_nonce(
250        &self,
251        plaintext: &[u8],
252        associated_data: &[u8],
253        nonce: Nonce,
254    ) -> Result<EncryptedData, EncryptionError> {
255        validate_encryption_lengths(
256            plaintext.len().div_ceil(BINARY_CHUNK_SIZE),
257            associated_data.len().div_ceil(BINARY_CHUNK_SIZE),
258        )?;
259        let plaintext = bytes_to_elements_with_padding(plaintext);
260        let associated_data = bytes_to_elements_with_padding(associated_data);
261        let authenticated_data = bind_data_type(DataType::Bytes, &associated_data)?;
262        let (ciphertext, tag) = expanded::encrypt_felts_expanded_authenticated(
263            self.as_word(),
264            nonce.as_word(),
265            &authenticated_data,
266            &plaintext,
267        );
268
269        EncryptedData::from_parts(DataType::Bytes, ciphertext, AuthTag(tag), nonce)
270    }
271
272    /// Decrypts field elements after verifying their authentication tag.
273    pub fn decrypt_elements(
274        &self,
275        encrypted_data: &EncryptedData,
276    ) -> Result<Vec<Felt>, EncryptionError> {
277        self.decrypt_elements_with_associated_data(encrypted_data, &[])
278    }
279
280    /// Decrypts field elements after authenticating the ciphertext and associated data.
281    pub fn decrypt_elements_with_associated_data(
282        &self,
283        encrypted_data: &EncryptedData,
284        associated_data: &[Felt],
285    ) -> Result<Vec<Felt>, EncryptionError> {
286        ensure_data_type(encrypted_data, DataType::Elements)?;
287        validate_authentication_lengths(encrypted_data.ciphertext.len(), associated_data.len())?;
288        let authenticated_data = bind_data_type(DataType::Elements, associated_data)?;
289        self.decrypt_felts(encrypted_data, &authenticated_data)
290    }
291
292    /// Decrypts bytes after verifying their authentication tag.
293    pub fn decrypt_bytes(
294        &self,
295        encrypted_data: &EncryptedData,
296    ) -> Result<Vec<u8>, EncryptionError> {
297        self.decrypt_bytes_with_associated_data(encrypted_data, &[])
298    }
299
300    /// Decrypts bytes after authenticating the ciphertext and associated data.
301    pub fn decrypt_bytes_with_associated_data(
302        &self,
303        encrypted_data: &EncryptedData,
304        associated_data: &[u8],
305    ) -> Result<Vec<u8>, EncryptionError> {
306        ensure_data_type(encrypted_data, DataType::Bytes)?;
307        validate_authentication_lengths(
308            encrypted_data.ciphertext.len(),
309            associated_data.len().div_ceil(BINARY_CHUNK_SIZE),
310        )?;
311        let associated_data = bytes_to_elements_with_padding(associated_data);
312        let authenticated_data = bind_data_type(DataType::Bytes, &associated_data)?;
313        let plaintext = self.decrypt_felts(encrypted_data, &authenticated_data)?;
314        let bytes =
315            padded_elements_to_bytes(&plaintext).ok_or(EncryptionError::MalformedPadding)?;
316
317        if bytes_to_elements_with_padding(&bytes) != plaintext {
318            return Err(EncryptionError::MalformedPadding);
319        }
320        Ok(bytes)
321    }
322
323    fn decrypt_felts(
324        &self,
325        encrypted_data: &EncryptedData,
326        authenticated_data: &[Felt],
327    ) -> Result<Vec<Felt>, EncryptionError> {
328        validate_ciphertext(&encrypted_data.ciphertext)?;
329        expanded::checked_mac_input_len(authenticated_data.len(), encrypted_data.ciphertext.len())
330            .ok_or(EncryptionError::InputTooLong)?;
331        expanded::decrypt_felts_expanded_authenticated(
332            self.as_word(),
333            encrypted_data.nonce.as_word(),
334            authenticated_data,
335            &encrypted_data.ciphertext,
336            encrypted_data.auth_tag.0,
337        )
338        .ok_or(EncryptionError::InvalidAuthTag)
339    }
340
341    fn as_word(&self) -> Word {
342        Word::new(self.0)
343    }
344}
345
346#[cfg(any(test, feature = "testing"))]
347impl PartialEq for SecretKey {
348    fn eq(&self, other: &Self) -> bool {
349        self.0.iter().zip(other.0).fold(true, |equal, (left, right)| {
350            equal & bool::from(left.as_canonical_u64_ct().ct_eq(&right.as_canonical_u64_ct()))
351        })
352    }
353}
354
355#[cfg(any(test, feature = "testing"))]
356impl Eq for SecretKey {}
357
358impl Zeroize for SecretKey {
359    fn zeroize(&mut self) {
360        for element in &mut self.0 {
361            unsafe { core::ptr::write_volatile(element, ZERO) };
362        }
363        core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);
364    }
365}
366
367impl Drop for SecretKey {
368    fn drop(&mut self) {
369        self.zeroize();
370    }
371}
372
373impl ZeroizeOnDrop for SecretKey {}
374
375/// Nonce for one Eidos AEAD invocation.
376#[derive(Debug, Clone, Copy, PartialEq, Eq)]
377#[cfg_attr(
378    all(feature = "arbitrary", test),
379    miden_test_serialization_macros::serialization_test
380)]
381pub struct Nonce([Felt; NONCE_SIZE]);
382
383impl Nonce {
384    /// Generates a nonce with the supplied cryptographic random-number generator.
385    pub fn with_rng<R: CryptoRng + ?Sized>(rng: &mut R) -> Self {
386        Self(sample_felts(rng))
387    }
388
389    /// Returns the nonce as four field elements.
390    pub fn to_elements(self) -> [Felt; NONCE_SIZE] {
391        self.0
392    }
393
394    fn as_word(self) -> Word {
395        Word::new(self.0)
396    }
397}
398
399impl From<Word> for Nonce {
400    fn from(word: Word) -> Self {
401        Self(word.into())
402    }
403}
404
405impl From<[Felt; NONCE_SIZE]> for Nonce {
406    fn from(elements: [Felt; NONCE_SIZE]) -> Self {
407        Self(elements)
408    }
409}
410
411impl From<Nonce> for Word {
412    fn from(nonce: Nonce) -> Self {
413        nonce.as_word()
414    }
415}
416
417#[cfg(any(test, feature = "arbitrary"))]
418mod secret_key_arbitrary {
419    use proptest::prelude::*;
420
421    use super::{SECRET_KEY_SIZE, SecretKey};
422    use crate::Felt;
423
424    impl Arbitrary for SecretKey {
425        type Parameters = ();
426        type Strategy = BoxedStrategy<Self>;
427
428        fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
429            // The wire carries the raw key elements; any Felts are valid (SilentDebug provides
430            // the redacted Debug the macro oracle needs).
431            any::<[Felt; SECRET_KEY_SIZE]>().prop_map(Self).boxed()
432        }
433    }
434}
435
436impl Serializable for SecretKey {
437    fn write_into<W: ByteWriter>(&self, target: &mut W) {
438        let mut bytes = Zeroizing::new([0_u8; SK_SIZE_BYTES]);
439        for (element, chunk) in self.0.iter().zip(bytes.chunks_exact_mut(Felt::NUM_BYTES)) {
440            chunk.copy_from_slice(&element.as_canonical_u64_ct().to_le_bytes());
441        }
442        target.write_bytes(bytes.as_slice());
443    }
444}
445
446impl Deserializable for SecretKey {
447    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
448        let bytes = read_sensitive_array::<SK_SIZE_BYTES, _>(source)?;
449        // Decode into a key so its Drop implementation clears partially decoded elements on error.
450        let mut key = Self([ZERO; SECRET_KEY_SIZE]);
451        for (element, chunk) in key.0.iter_mut().zip(bytes.as_chunks::<{ Felt::NUM_BYTES }>().0) {
452            *element = Felt::new(u64::from_le_bytes(*chunk)).map_err(|_| {
453                DeserializationError::InvalidValue("malformed secret key".to_string())
454            })?;
455        }
456        Ok(key)
457    }
458}
459
460#[cfg(any(test, feature = "arbitrary"))]
461mod nonce_arbitrary {
462    use proptest::prelude::*;
463
464    use super::{NONCE_SIZE, Nonce};
465    use crate::Felt;
466
467    impl Arbitrary for Nonce {
468        type Parameters = ();
469        type Strategy = BoxedStrategy<Self>;
470
471        fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
472            // The wire carries the four canonical Felts; any Felts are valid input.
473            any::<[Felt; NONCE_SIZE]>().prop_map(Self).boxed()
474        }
475    }
476}
477
478impl Serializable for Nonce {
479    fn write_into<W: ByteWriter>(&self, target: &mut W) {
480        target.write_bytes(&elements_to_bytes(&self.0));
481    }
482}
483
484impl Deserializable for Nonce {
485    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
486        let bytes: [u8; NONCE_SIZE_BYTES] = source.read_array()?;
487        let elements = bytes_to_elements_exact(&bytes)
488            .and_then(|elements| elements.try_into().ok())
489            .ok_or_else(|| DeserializationError::InvalidValue("malformed nonce".to_string()))?;
490        Ok(Self(elements))
491    }
492}
493
494#[cfg(any(test, feature = "arbitrary"))]
495mod encrypted_data_arbitrary {
496    use proptest::prelude::*;
497
498    use super::{AuthTag, DataType, EncryptedData, Nonce};
499    use crate::Felt;
500
501    impl Arbitrary for EncryptedData {
502        type Parameters = ();
503        type Strategy = BoxedStrategy<Self>;
504
505        fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
506            // validate_ciphertext requires an even element count and every Felt to fit in a
507            // u32; generate pairs of u32-bounded Felts so both hold by construction.
508            (
509                prop_oneof![Just(DataType::Elements), Just(DataType::Bytes)],
510                proptest::collection::vec((any::<u32>(), any::<u32>()), 0..=4),
511                any::<[Felt; 2]>(),
512                any::<Nonce>(),
513            )
514                .prop_map(|(data_type, pairs, tag_elements, nonce)| {
515                    let ciphertext = pairs
516                        .into_iter()
517                        .flat_map(|(a, b)| [Felt::from(a), Felt::from(b)])
518                        .collect();
519                    Self::from_parts(data_type, ciphertext, AuthTag::new(tag_elements), nonce)
520                        .expect("even-length u32-bounded ciphertext passes validation")
521                })
522                .boxed()
523        }
524    }
525}
526
527impl Serializable for EncryptedData {
528    fn write_into<W: ByteWriter>(&self, target: &mut W) {
529        target.write_u8(self.data_type as u8);
530        self.ciphertext.write_into(target);
531        target.write_many(self.nonce.0);
532        target.write_many(self.auth_tag.0);
533    }
534}
535
536impl Deserializable for EncryptedData {
537    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
538        let data_type = source.read_u8()?.try_into().map_err(|_| {
539            DeserializationError::InvalidValue("invalid encrypted-data type".to_string())
540        })?;
541        let ciphertext_len = source.read_usize()?;
542        validate_ciphertext_length(ciphertext_len).map_err(|error| {
543            DeserializationError::InvalidValue(format!("malformed Eidos ciphertext: {error}"))
544        })?;
545        let ciphertext =
546            source.read_many_iter::<Felt>(ciphertext_len)?.collect::<Result<_, _>>()?;
547        let nonce = Nonce(source.read()?);
548        let auth_tag = AuthTag(source.read()?);
549
550        Self::from_parts(data_type, ciphertext, auth_tag, nonce).map_err(|error| {
551            DeserializationError::InvalidValue(format!("malformed Eidos ciphertext: {error}"))
552        })
553    }
554}
555
556/// Eidos authenticated-encryption implementation.
557pub struct AeadEidos;
558
559impl AeadScheme for AeadEidos {
560    const KEY_SIZE: usize = SK_SIZE_BYTES;
561
562    type Key = SecretKey;
563
564    fn key_from_bytes(bytes: &[u8]) -> Result<Self::Key, EncryptionError> {
565        if bytes.len() != SK_SIZE_BYTES {
566            return Err(EncryptionError::FailedOperation);
567        }
568        SecretKey::read_from_bytes_with_budget(bytes, SK_SIZE_BYTES)
569            .map_err(|_| EncryptionError::FailedOperation)
570    }
571
572    fn key_from_uniform_bytes(bytes: &[u8]) -> Result<Self::Key, EncryptionError> {
573        if bytes.len() != SK_SIZE_BYTES {
574            return Err(EncryptionError::FailedOperation);
575        }
576
577        let (chunks, remainder) = bytes.as_chunks::<{ Felt::NUM_BYTES }>();
578        debug_assert!(remainder.is_empty());
579        Ok(SecretKey::from_elements(core::array::from_fn(|i| {
580            Felt::from_u64(u64::from_le_bytes(chunks[i]))
581        })))
582    }
583
584    fn encrypt_bytes<R: CryptoRng>(
585        key: &Self::Key,
586        rng: &mut R,
587        plaintext: &[u8],
588        associated_data: &[u8],
589    ) -> Result<Vec<u8>, EncryptionError> {
590        let encrypted =
591            key.encrypt_bytes_with_nonce(plaintext, associated_data, Nonce::with_rng(rng))?;
592        Ok(encrypted.to_bytes())
593    }
594
595    fn decrypt_bytes_with_associated_data(
596        key: &Self::Key,
597        ciphertext: &[u8],
598        associated_data: &[u8],
599    ) -> Result<Vec<u8>, EncryptionError> {
600        let encrypted = read_encrypted_data_strict(ciphertext)?;
601        key.decrypt_bytes_with_associated_data(&encrypted, associated_data)
602    }
603
604    fn encrypt_elements<R: CryptoRng>(
605        key: &Self::Key,
606        rng: &mut R,
607        plaintext: &[Felt],
608        associated_data: &[Felt],
609    ) -> Result<Vec<u8>, EncryptionError> {
610        let encrypted =
611            key.encrypt_elements_with_nonce(plaintext, associated_data, Nonce::with_rng(rng))?;
612        Ok(encrypted.to_bytes())
613    }
614
615    fn decrypt_elements_with_associated_data(
616        key: &Self::Key,
617        ciphertext: &[u8],
618        associated_data: &[Felt],
619    ) -> Result<Vec<Felt>, EncryptionError> {
620        let encrypted = read_encrypted_data_strict(ciphertext)?;
621        key.decrypt_elements_with_associated_data(&encrypted, associated_data)
622    }
623}
624
625fn read_encrypted_data_strict(ciphertext: &[u8]) -> Result<EncryptedData, EncryptionError> {
626    let mut reader = BudgetedReader::new(SliceReader::new(ciphertext), ciphertext.len());
627    let encrypted =
628        EncryptedData::read_from(&mut reader).map_err(|_| EncryptionError::FailedOperation)?;
629    if reader.has_more_bytes() {
630        return Err(EncryptionError::FailedOperation);
631    }
632    Ok(encrypted)
633}
634
635fn sample_felts<R: CryptoRng + ?Sized, const N: usize>(rng: &mut R) -> [Felt; N] {
636    let distribution =
637        Uniform::new(0, Felt::ORDER).expect("the field order defines a valid sampling range");
638    core::array::from_fn(|_| Felt::new_unchecked(distribution.sample(rng)))
639}
640
641fn bind_data_type(
642    data_type: DataType,
643    associated_data: &[Felt],
644) -> Result<Vec<Felt>, EncryptionError> {
645    let capacity = associated_data.len().checked_add(1).ok_or(EncryptionError::InputTooLong)?;
646    u32::try_from(capacity).map_err(|_| EncryptionError::InputTooLong)?;
647
648    let mut bound = Vec::with_capacity(capacity);
649    bound.push(Felt::from_u32(data_type as u8 as u32));
650    bound.extend_from_slice(associated_data);
651    Ok(bound)
652}
653
654fn validate_encryption_lengths(
655    plaintext_len: usize,
656    associated_data_len: usize,
657) -> Result<(), EncryptionError> {
658    let ciphertext_len = plaintext_len.checked_mul(2).ok_or(EncryptionError::InputTooLong)?;
659    validate_authentication_lengths(ciphertext_len, associated_data_len)
660}
661
662fn validate_authentication_lengths(
663    ciphertext_len: usize,
664    associated_data_len: usize,
665) -> Result<(), EncryptionError> {
666    // The high-level APIs prepend a data-type marker to the caller's associated data.
667    let authenticated_data_len =
668        associated_data_len.checked_add(1).ok_or(EncryptionError::InputTooLong)?;
669    expanded::checked_mac_input_len(authenticated_data_len, ciphertext_len)
670        .ok_or(EncryptionError::InputTooLong)?;
671    Ok(())
672}
673
674fn validate_ciphertext(ciphertext: &[Felt]) -> Result<(), EncryptionError> {
675    validate_ciphertext_length(ciphertext.len())?;
676    if ciphertext.iter().any(|felt| felt.as_canonical_u64() > u64::from(u32::MAX)) {
677        return Err(EncryptionError::MalformedCiphertext);
678    }
679    Ok(())
680}
681
682fn validate_ciphertext_length(ciphertext_len: usize) -> Result<(), EncryptionError> {
683    if !ciphertext_len.is_multiple_of(2) {
684        return Err(EncryptionError::MalformedCiphertext);
685    }
686    // Check the MAC limit with empty caller AD; decryption checks it again with the supplied AD.
687    validate_authentication_lengths(ciphertext_len, 0)
688}
689
690fn ensure_data_type(
691    encrypted_data: &EncryptedData,
692    expected: DataType,
693) -> Result<(), EncryptionError> {
694    if encrypted_data.data_type != expected {
695        return Err(EncryptionError::InvalidDataType {
696            expected,
697            found: encrypted_data.data_type,
698        });
699    }
700    Ok(())
701}