1use 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
30pub mod expanded;
32
33#[cfg(test)]
34mod tests;
35
36pub const SECRET_KEY_SIZE: usize = 4;
38
39pub const SK_SIZE_BYTES: usize = SECRET_KEY_SIZE * Felt::NUM_BYTES;
41
42pub const NONCE_SIZE: usize = 4;
44
45pub const NONCE_SIZE_BYTES: usize = NONCE_SIZE * Felt::NUM_BYTES;
47
48pub const AUTH_TAG_SIZE: usize = 2;
50
51pub const MAX_AUTHENTICATED_INPUT_FELTS: usize = 1 << 28;
57
58pub const MAX_VERIFICATION_DEGREE_BUDGET_PER_KEY: u64 = 1 << 28;
75
76#[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 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 pub fn data_type(&self) -> DataType {
103 self.data_type
104 }
105
106 pub fn ciphertext(&self) -> &[Felt] {
108 &self.ciphertext
109 }
110
111 pub fn auth_tag(&self) -> &AuthTag {
113 &self.auth_tag
114 }
115
116 pub fn nonce(&self) -> &Nonce {
118 &self.nonce
119 }
120}
121
122#[derive(Debug, Default, Clone, Copy)]
126pub struct AuthTag([Felt; AUTH_TAG_SIZE]);
127
128impl AuthTag {
129 pub fn new(elements: [Felt; AUTH_TAG_SIZE]) -> Self {
131 Self(elements)
132 }
133
134 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#[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 #[cfg(feature = "std")]
167 #[allow(clippy::new_without_default)]
168 pub fn new() -> Self {
169 Self::with_rng(&mut rand::rng())
170 }
171
172 pub fn with_rng<R: CryptoRng + ?Sized>(rng: &mut R) -> Self {
174 Self(sample_felts(rng))
175 }
176
177 pub fn from_elements(elements: [Felt; SECRET_KEY_SIZE]) -> Self {
179 Self(elements)
180 }
181
182 pub fn to_elements(&self) -> [Felt; SECRET_KEY_SIZE] {
186 self.0
187 }
188
189 #[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 #[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 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 #[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 #[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 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 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 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 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 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#[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 pub fn with_rng<R: CryptoRng + ?Sized>(rng: &mut R) -> Self {
386 Self(sample_felts(rng))
387 }
388
389 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 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 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 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 (
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
556pub 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 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 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}