1use alloc::{
2 collections::BTreeSet,
3 string::{String, ToString},
4 vec::Vec,
5};
6
7#[cfg(feature = "arbitrary")]
8use proptest::prelude::*;
9
10use crate::{
11 Word,
12 crypto::hash::Blake3_256,
13 deferred::{DeferredRoot, MAX_PRECOMPILE_ROOTS, PrecompileWitness, fold_deferred_root},
14 serde::{
15 BudgetedReader, ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable,
16 SliceReader,
17 },
18};
19
20pub const MAX_STARK_PROOF_BYTES: usize = 64 * 1024 * 1024;
25
26const DEFERRED_PROOF_DISCRIMINANT: u8 = 0;
27const COMPLETE_PROOF_DISCRIMINANT: u8 = 1;
28
29pub const CURRENT_VM_VERIFIER_ROOT: Word = Word::new([
31 crate::Felt::new_unchecked(6531317477902291971),
32 crate::Felt::new_unchecked(6330145662319840937),
33 crate::Felt::new_unchecked(1346989877885786889),
34 crate::Felt::new_unchecked(2977526316393511162),
35]);
36pub const CURRENT_PVM_VERIFIER_ROOT: Word = Word::new([
38 crate::Felt::new_unchecked(6808264455915748692),
39 crate::Felt::new_unchecked(7335842088828173809),
40 crate::Felt::new_unchecked(8794786252055803605),
41 crate::Felt::new_unchecked(8801448297059648773),
42]);
43
44#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
49#[cfg_attr(
50 all(feature = "arbitrary", test),
51 miden_test_serialization_macros::serialization_test
52)]
53#[repr(u8)]
54pub enum HashFunction {
55 Blake3_256 = 0x01,
57 Keccak = 0x05,
60 Eidos = 0x06,
62}
63
64impl HashFunction {
65 pub const fn collision_resistance(&self) -> u32 {
67 match self {
68 HashFunction::Blake3_256 => Blake3_256::COLLISION_RESISTANCE,
69 HashFunction::Keccak => 128,
70 HashFunction::Eidos => 126,
71 }
72 }
73}
74
75#[derive(Debug, thiserror::Error)]
77#[error("invalid hash function '{hash_function}'. Valid options are: blake3-256, keccak, eidos")]
78pub struct InvalidHashFunctionError {
79 pub hash_function: String,
80}
81
82impl TryFrom<u8> for HashFunction {
83 type Error = DeserializationError;
84
85 fn try_from(repr: u8) -> Result<Self, Self::Error> {
86 match repr {
87 0x01 => Ok(Self::Blake3_256),
88 0x05 => Ok(Self::Keccak),
89 0x06 => Ok(Self::Eidos),
90 _ => Err(DeserializationError::InvalidValue(format!(
91 "the hash function representation {repr} is not valid!"
92 ))),
93 }
94 }
95}
96
97impl TryFrom<&str> for HashFunction {
98 type Error = InvalidHashFunctionError;
99
100 fn try_from(hash_fn_str: &str) -> Result<Self, Self::Error> {
101 match hash_fn_str {
102 "blake3-256" => Ok(Self::Blake3_256),
103 "keccak" => Ok(Self::Keccak),
104 "eidos" => Ok(Self::Eidos),
105 _ => Err(InvalidHashFunctionError { hash_function: hash_fn_str.to_string() }),
106 }
107 }
108}
109
110#[cfg(feature = "arbitrary")]
111impl Arbitrary for HashFunction {
112 type Parameters = ();
113 type Strategy = BoxedStrategy<Self>;
114
115 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
116 any::<u8>()
117 .prop_map(|tag| match tag % 3 {
118 0 => Self::Blake3_256,
119 1 => Self::Keccak,
120 _ => Self::Eidos,
121 })
122 .boxed()
123 }
124}
125
126impl Serializable for HashFunction {
127 fn write_into<W: ByteWriter>(&self, target: &mut W) {
128 target.write_u8(*self as u8);
129 }
130}
131
132impl Deserializable for HashFunction {
133 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
134 source.read_u8()?.try_into()
135 }
136}
137
138#[derive(Debug, Clone, PartialEq, Eq)]
143#[cfg_attr(
144 all(feature = "arbitrary", test),
145 miden_test_serialization_macros::serialization_test
146)]
147pub struct VmProof {
148 pub proof: StarkProof,
149 pub precompile_root: DeferredRoot,
150}
151
152impl Serializable for VmProof {
153 fn write_into<W: ByteWriter>(&self, target: &mut W) {
154 self.proof.write_into(target);
155 self.precompile_root.write_into(target);
156 }
157}
158
159impl Deserializable for VmProof {
160 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
161 let proof = StarkProof::read_from(source)?;
162 let precompile_root = DeferredRoot::read_from(source)?;
163 Ok(Self { proof, precompile_root })
164 }
165
166 fn read_from_bytes(bytes: &[u8]) -> Result<Self, DeserializationError> {
167 let mut reader = BudgetedReader::new(SliceReader::new(bytes), bytes.len());
168 let proof = Self::read_from(&mut reader)?;
169 if reader.has_more_bytes() {
170 return Err(DeserializationError::InvalidValue(
171 "extra bytes after VM proof payload".into(),
172 ));
173 }
174 Ok(proof)
175 }
176
177 fn min_serialized_size() -> usize {
178 StarkProof::min_serialized_size() + DeferredRoot::min_serialized_size()
179 }
180}
181
182#[derive(Debug, Clone, PartialEq, Eq)]
187#[cfg_attr(
188 all(feature = "arbitrary", test),
189 miden_test_serialization_macros::serialization_test
190)]
191pub struct PrecompileProof {
192 pub proof: StarkProof,
193 pub roots: Vec<DeferredRoot>,
194}
195
196impl PrecompileProof {
197 pub fn aggregate_root(&self) -> Option<DeferredRoot> {
203 self.roots.iter().copied().reduce(fold_deferred_root)
204 }
205}
206
207impl Serializable for PrecompileProof {
208 fn write_into<W: ByteWriter>(&self, target: &mut W) {
209 self.proof.write_into(target);
210 self.roots.write_into(target);
211 }
212}
213
214impl Deserializable for PrecompileProof {
215 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
216 let proof = StarkProof::read_from(source)?;
217 let root_count = source.read_usize()?;
218 if root_count > MAX_PRECOMPILE_ROOTS {
219 return Err(DeserializationError::InvalidValue(format!(
220 "precompile proof contains too many roots: found {root_count}, maximum is {MAX_PRECOMPILE_ROOTS}"
221 )));
222 }
223 let roots = source
224 .read_many_iter::<DeferredRoot>(root_count)?
225 .collect::<Result<Vec<_>, _>>()?;
226 Ok(Self { proof, roots })
227 }
228
229 fn read_from_bytes(bytes: &[u8]) -> Result<Self, DeserializationError> {
230 let mut reader = BudgetedReader::new(SliceReader::new(bytes), bytes.len());
231 let proof = Self::read_from(&mut reader)?;
232 if reader.has_more_bytes() {
233 return Err(DeserializationError::InvalidValue(
234 "extra bytes after precompile proof payload".into(),
235 ));
236 }
237 Ok(proof)
238 }
239
240 fn min_serialized_size() -> usize {
241 StarkProof::min_serialized_size() + usize::min_serialized_size()
242 }
243}
244
245#[derive(Debug, Clone, PartialEq, Eq)]
250pub struct ExecutionProofCompatibility {
251 format: u8,
252 vm_verifier_roots: Vec<Word>,
253 pvm_verifier_roots: Vec<Word>,
254}
255
256impl ExecutionProofCompatibility {
257 pub const FORMAT_V2: u8 = 2;
259
260 pub fn new(
266 vm_verifier_roots: Vec<Word>,
267 pvm_verifier_roots: Vec<Word>,
268 ) -> Result<Self, ExecutionProofCompatibilityError> {
269 if has_duplicate(&vm_verifier_roots) {
270 return Err(ExecutionProofCompatibilityError::DuplicateVmVerifierRoot);
271 }
272 if has_duplicate(&pvm_verifier_roots) {
273 return Err(ExecutionProofCompatibilityError::DuplicatePvmVerifierRoot);
274 }
275
276 Ok(Self {
277 format: Self::FORMAT_V2,
278 vm_verifier_roots,
279 pvm_verifier_roots,
280 })
281 }
282
283 pub fn current() -> Self {
285 Self::new(alloc::vec![CURRENT_VM_VERIFIER_ROOT], alloc::vec![CURRENT_PVM_VERIFIER_ROOT])
286 .expect("current execution proof compatibility must not contain duplicate roots")
287 }
288
289 pub const fn format(&self) -> u8 {
291 self.format
292 }
293
294 pub fn vm_verifier_roots(&self) -> &[Word] {
299 &self.vm_verifier_roots
300 }
301
302 pub fn pvm_verifier_roots(&self) -> &[Word] {
308 &self.pvm_verifier_roots
309 }
310}
311
312#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
314pub enum ExecutionProofCompatibilityError {
315 #[error("VM verifier roots must not contain duplicates")]
317 DuplicateVmVerifierRoot,
318 #[error("PVM verifier roots must not contain duplicates")]
320 DuplicatePvmVerifierRoot,
321}
322
323#[derive(Debug, Clone, PartialEq, Eq)]
325pub enum PrecompileStatus {
326 Empty,
328 Deferred(PrecompileWitness),
330 Proven(PrecompileProof),
332}
333
334#[derive(Debug, Clone, PartialEq, Eq)]
338#[cfg_attr(
339 all(feature = "arbitrary", test),
340 miden_test_serialization_macros::serialization_test
341)]
342pub struct ExecutionProof {
343 compatibility: ExecutionProofCompatibility,
344 vm: VmProof,
345 precompile: PrecompileStatus,
346}
347
348impl ExecutionProof {
349 pub fn new(vm: VmProof, precompile: PrecompileStatus) -> Self {
351 Self::from_parts(ExecutionProofCompatibility::current(), vm, precompile)
352 }
353
354 pub const fn from_parts(
356 compatibility: ExecutionProofCompatibility,
357 vm: VmProof,
358 precompile: PrecompileStatus,
359 ) -> Self {
360 Self { compatibility, vm, precompile }
361 }
362
363 pub const fn compatibility(&self) -> &ExecutionProofCompatibility {
365 &self.compatibility
366 }
367
368 pub const fn vm(&self) -> &VmProof {
370 &self.vm
371 }
372
373 pub const fn precompile(&self) -> &PrecompileStatus {
377 &self.precompile
378 }
379
380 pub fn into_parts(self) -> (ExecutionProofCompatibility, VmProof, PrecompileStatus) {
382 (self.compatibility, self.vm, self.precompile)
383 }
384
385 pub const fn has_precompiles(&self) -> bool {
387 !matches!(self.precompile, PrecompileStatus::Empty)
388 }
389
390 pub fn complete(mut self, precompile: PrecompileProof) -> Result<Self, ExecutionProofError> {
395 if !matches!(self.precompile, PrecompileStatus::Deferred(_)) {
396 return Err(ExecutionProofError::AlreadyComplete);
397 }
398 self.precompile = PrecompileStatus::Proven(precompile);
399 Ok(self)
400 }
401
402 pub fn to_bytes(&self) -> Vec<u8> {
404 Serializable::to_bytes(self)
405 }
406
407 pub fn read_from_bytes(bytes: &[u8]) -> Result<Self, DeserializationError> {
409 let mut reader = BudgetedReader::new(SliceReader::new(bytes), bytes.len());
410 let proof = <Self as Deserializable>::read_from(&mut reader)?;
411
412 if reader.has_more_bytes() {
413 return Err(DeserializationError::InvalidValue(
414 "extra bytes after versioned proof payload".into(),
415 ));
416 }
417 if proof.to_bytes() != bytes {
418 return Err(DeserializationError::InvalidValue(
419 "versioned proof bytes are not canonically encoded".into(),
420 ));
421 }
422
423 Ok(proof)
424 }
425}
426
427impl Serializable for ExecutionProof {
428 fn write_into<W: ByteWriter>(&self, target: &mut W) {
429 target.write_u8(self.compatibility.format);
430 self.compatibility.vm_verifier_roots.write_into(target);
431 self.compatibility.pvm_verifier_roots.write_into(target);
432 self.write_into_v2(target);
433 }
434}
435
436impl Deserializable for ExecutionProof {
437 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
438 let format = source.read_u8()?;
439 if format != ExecutionProofCompatibility::FORMAT_V2 {
440 return Err(DeserializationError::InvalidValue(format!(
441 "unsupported execution proof format {format}"
442 )));
443 }
444
445 let vm_verifier_roots = Vec::<Word>::read_from(source)?;
446 let pvm_verifier_roots = Vec::<Word>::read_from(source)?;
447 let compatibility = ExecutionProofCompatibility::new(vm_verifier_roots, pvm_verifier_roots)
448 .map_err(|error| DeserializationError::InvalidValue(error.to_string()))?;
449
450 Self::read_from_v2(source, compatibility)
451 }
452
453 fn min_serialized_size() -> usize {
454 u8::min_serialized_size()
455 + Vec::<Word>::min_serialized_size()
456 + Vec::<Word>::min_serialized_size()
457 + ExecutionProof::min_serialized_size_v2()
458 }
459}
460
461impl ExecutionProof {
462 fn write_into_v2<W: ByteWriter>(&self, target: &mut W) {
463 match &self.precompile {
464 PrecompileStatus::Deferred(precompile) => {
465 target.write_u8(DEFERRED_PROOF_DISCRIMINANT);
466 self.vm.write_into(target);
467 precompile.write_into(target);
468 },
469 PrecompileStatus::Empty => {
470 target.write_u8(COMPLETE_PROOF_DISCRIMINANT);
471 self.vm.write_into(target);
472 Option::<PrecompileProof>::None.write_into(target);
473 },
474 PrecompileStatus::Proven(precompile) => {
475 target.write_u8(COMPLETE_PROOF_DISCRIMINANT);
476 self.vm.write_into(target);
477 Some(precompile).write_into(target);
478 },
479 }
480 }
481
482 fn read_from_v2<R: ByteReader>(
483 source: &mut R,
484 compatibility: ExecutionProofCompatibility,
485 ) -> Result<Self, DeserializationError> {
486 let discriminant = source.read_u8()?;
487 if !matches!(discriminant, DEFERRED_PROOF_DISCRIMINANT | COMPLETE_PROOF_DISCRIMINANT) {
488 return Err(DeserializationError::InvalidValue(format!(
489 "invalid execution proof discriminant {discriminant}"
490 )));
491 }
492
493 let vm = VmProof::read_from(source)?;
494 let precompile = match discriminant {
495 DEFERRED_PROOF_DISCRIMINANT => {
496 PrecompileStatus::Deferred(PrecompileWitness::read_from(source)?)
497 },
498 COMPLETE_PROOF_DISCRIMINANT => match Option::<PrecompileProof>::read_from(source)? {
499 Some(precompile) => PrecompileStatus::Proven(precompile),
500 None => PrecompileStatus::Empty,
501 },
502 _ => unreachable!("execution proof discriminant was checked before decoding"),
503 };
504
505 Ok(Self { compatibility, vm, precompile })
506 }
507
508 fn min_serialized_size_v2() -> usize {
509 u8::min_serialized_size()
510 + VmProof::min_serialized_size()
511 + Option::<PrecompileProof>::min_serialized_size()
512 }
513}
514
515#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
517pub enum ExecutionProofError {
518 #[error("the execution proof is already complete")]
520 AlreadyComplete,
521}
522
523#[derive(Debug, Clone, PartialEq, Eq)]
528#[cfg_attr(
529 all(feature = "arbitrary", test),
530 miden_test_serialization_macros::serialization_test
531)]
532pub struct StarkProof {
533 bytes: Vec<u8>,
534 hash_fn: HashFunction,
535}
536
537impl StarkProof {
538 pub const fn new(bytes: Vec<u8>, hash_fn: HashFunction) -> Self {
540 Self { bytes, hash_fn }
541 }
542
543 pub fn bytes(&self) -> &[u8] {
545 &self.bytes
546 }
547
548 pub const fn hash_fn(&self) -> HashFunction {
550 self.hash_fn
551 }
552
553 pub fn into_parts(self) -> (Vec<u8>, HashFunction) {
555 (self.bytes, self.hash_fn)
556 }
557}
558
559impl Serializable for StarkProof {
560 fn write_into<W: ByteWriter>(&self, target: &mut W) {
561 self.bytes.write_into(target);
562 self.hash_fn.write_into(target);
563 }
564}
565
566impl Deserializable for StarkProof {
567 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
568 let byte_count = source.read_usize()?;
569 if byte_count > MAX_STARK_PROOF_BYTES {
570 return Err(DeserializationError::InvalidValue(format!(
571 "STARK proof contains too many bytes: found {byte_count}, maximum is {MAX_STARK_PROOF_BYTES}"
572 )));
573 }
574 let bytes = source.read_many_iter::<u8>(byte_count)?.collect::<Result<Vec<_>, _>>()?;
575 let hash_fn = HashFunction::read_from(source)?;
576 Ok(Self::new(bytes, hash_fn))
577 }
578
579 fn read_from_bytes(bytes: &[u8]) -> Result<Self, DeserializationError> {
580 let mut reader = BudgetedReader::new(SliceReader::new(bytes), bytes.len());
581 Self::read_from(&mut reader)
582 }
583
584 fn min_serialized_size() -> usize {
585 Vec::<u8>::min_serialized_size() + HashFunction::min_serialized_size()
586 }
587}
588
589fn has_duplicate(roots: &[Word]) -> bool {
593 let mut unique = BTreeSet::new();
594 roots.iter().any(|root| !unique.insert(*root))
595}
596
597#[cfg(test)]
601mod tests {
602 use super::*;
603 use crate::{
604 Felt,
605 deferred::{DEFERRED_AND_FRAME, Node, PrecompileWitnessEntry, TRUE_DIGEST},
606 serde::ByteWriter,
607 };
608
609 fn dummy_stark_proof(bytes: &[u8]) -> StarkProof {
610 StarkProof::new(bytes.to_vec(), HashFunction::Eidos)
611 }
612
613 #[test]
614 fn hash_function_wire_tags_are_pinned_and_retired_tags_are_rejected() {
615 for (hash_fn, tag, name) in [
616 (HashFunction::Blake3_256, 0x01, "blake3-256"),
617 (HashFunction::Keccak, 0x05, "keccak"),
618 (HashFunction::Eidos, 0x06, "eidos"),
619 ] {
620 assert_eq!(hash_fn.to_bytes(), [tag]);
621 assert_eq!(HashFunction::read_from_bytes(&[tag]).unwrap(), hash_fn);
622 assert_eq!(HashFunction::try_from(name).unwrap(), hash_fn);
623 }
624 for (tag, name) in [(0x02, "rpo"), (0x03, "rpx"), (0x04, "poseidon2")] {
625 assert!(HashFunction::read_from_bytes(&[tag]).is_err());
626 assert!(HashFunction::try_from(name).is_err());
627 }
628 }
629
630 fn root(value: u64) -> DeferredRoot {
631 [Felt::new(value).unwrap(), Felt::ZERO, Felt::ZERO, Felt::ZERO].into()
632 }
633
634 fn vm_proof(precompile_root: DeferredRoot) -> VmProof {
635 VmProof {
636 proof: dummy_stark_proof(&[1]),
637 precompile_root,
638 }
639 }
640
641 fn precompile_proof(roots: &[DeferredRoot]) -> PrecompileProof {
642 PrecompileProof {
643 proof: dummy_stark_proof(&[2]),
644 roots: roots.to_vec(),
645 }
646 }
647
648 #[test]
649 fn aggregate_root_preserves_order_grouping_and_duplicates() {
650 let a = root(1);
651 let b = root(2);
652 let c = root(3);
653 let and = |lhs, rhs| Node::and(lhs, rhs).digest();
654
655 assert_eq!(precompile_proof(&[]).aggregate_root(), None);
656 assert_eq!(precompile_proof(&[a]).aggregate_root(), Some(a));
657 assert_eq!(precompile_proof(&[a, b, c]).aggregate_root(), Some(and(and(a, b), c)));
658 assert_ne!(precompile_proof(&[a, b, c]).aggregate_root(), Some(and(a, and(b, c))));
659 assert_ne!(
660 precompile_proof(&[a, b]).aggregate_root(),
661 precompile_proof(&[b, a]).aggregate_root()
662 );
663 assert_eq!(precompile_proof(&[a, b, a]).aggregate_root(), Some(and(and(a, b), a)));
664 }
665
666 fn wire() -> (PrecompileWitness, DeferredRoot) {
667 let witness = PrecompileWitness::from_entries(vec![PrecompileWitnessEntry::Join {
668 frame: DEFERRED_AND_FRAME,
669 lhs: 0,
670 rhs: 0,
671 }])
672 .unwrap();
673 let root = Node::and(TRUE_DIGEST, TRUE_DIGEST).digest();
674 (witness, root)
675 }
676
677 fn versioned(vm: VmProof, precompile: PrecompileStatus) -> ExecutionProof {
678 ExecutionProof::from_parts(
679 ExecutionProofCompatibility::new(vec![root(11), root(12)], vec![root(21)]).unwrap(),
680 vm,
681 precompile,
682 )
683 }
684
685 fn version_prefix() -> Vec<u8> {
686 let compatibility =
687 ExecutionProofCompatibility::new(vec![root(11), root(12)], vec![root(21)]).unwrap();
688 let mut bytes = vec![compatibility.format()];
689 compatibility.vm_verifier_roots().to_vec().write_into(&mut bytes);
690 compatibility.pvm_verifier_roots().to_vec().write_into(&mut bytes);
691 bytes
692 }
693
694 #[test]
695 fn execution_proof_reports_precompile_state() {
696 let (precompile_wire, wire_root) = wire();
697 let deferred = versioned(vm_proof(wire_root), PrecompileStatus::Deferred(precompile_wire));
698 assert!(deferred.has_precompiles());
699
700 let complete_without_precompile = versioned(vm_proof(TRUE_DIGEST), PrecompileStatus::Empty);
701 assert!(!complete_without_precompile.has_precompiles());
702
703 let complete_with_precompile =
704 versioned(vm_proof(root(1)), PrecompileStatus::Proven(precompile_proof(&[root(1)])));
705 assert!(complete_with_precompile.has_precompiles());
706 }
707
708 #[test]
709 fn versioned_proof_round_trips_with_format_first() {
710 let proof = versioned(vm_proof(TRUE_DIGEST), PrecompileStatus::Empty);
711
712 let bytes = proof.to_bytes();
713 let decoded = ExecutionProof::read_from_bytes(&bytes).unwrap();
714
715 assert_eq!(bytes[0], ExecutionProofCompatibility::FORMAT_V2);
716 assert_eq!(decoded, proof);
717 assert_eq!(decoded.compatibility().format(), ExecutionProofCompatibility::FORMAT_V2);
718 assert_eq!(decoded.compatibility().vm_verifier_roots(), &[root(11), root(12)]);
719 assert_eq!(decoded.compatibility().pvm_verifier_roots(), &[root(21)]);
720 }
721
722 #[test]
723 fn versioned_proof_decoder_rejects_unknown_format_before_body() {
724 for version in [0, 1, ExecutionProofCompatibility::FORMAT_V2 + 1] {
725 let error = ExecutionProof::read_from_bytes(&[version]).unwrap_err();
726 assert!(matches!(error, DeserializationError::InvalidValue(message)
727 if message.contains("unsupported execution proof format")));
728 }
729 }
730
731 #[test]
732 fn versioned_proof_decoder_rejects_trailing_and_noncanonical_bytes() {
733 let proof = versioned(vm_proof(TRUE_DIGEST), PrecompileStatus::Empty);
734 let mut trailing = proof.to_bytes();
735 trailing.push(0);
736 assert!(ExecutionProof::read_from_bytes(&trailing).is_err());
737
738 let canonical = proof.to_bytes();
739 assert_eq!(canonical[1], 5, "two VM roots use a one-byte vint encoding");
740 let mut noncanonical = alloc::vec![canonical[0], 0];
741 noncanonical.extend_from_slice(&2u64.to_le_bytes());
742 noncanonical.extend_from_slice(&canonical[2..]);
743 let error = ExecutionProof::read_from_bytes(&noncanonical).unwrap_err();
744 assert!(
745 matches!(error, DeserializationError::InvalidValue(message) if message.contains("not canonically encoded"))
746 );
747 }
748
749 #[test]
750 fn versioned_proof_decoder_applies_the_input_budget_to_root_lists() {
751 let mut bytes = vec![ExecutionProofCompatibility::FORMAT_V2];
752 bytes.write_usize(usize::MAX);
753
754 let error = ExecutionProof::read_from_bytes(&bytes).unwrap_err();
755 assert!(matches!(error, DeserializationError::InvalidValue(_)));
756 }
757
758 #[test]
759 fn compatibility_constructor_rejects_duplicate_roots() {
760 assert_eq!(
761 ExecutionProofCompatibility::new(vec![root(1), root(1)], vec![]),
762 Err(ExecutionProofCompatibilityError::DuplicateVmVerifierRoot)
763 );
764 assert_eq!(
765 ExecutionProofCompatibility::new(vec![], vec![root(2), root(2)]),
766 Err(ExecutionProofCompatibilityError::DuplicatePvmVerifierRoot)
767 );
768 }
769
770 #[test]
771 fn versioned_proof_decoder_rejects_duplicate_roots() {
772 let proof = versioned(vm_proof(TRUE_DIGEST), PrecompileStatus::Empty);
773 let proof_bytes = proof.to_bytes();
774 let body = &proof_bytes[version_prefix().len()..];
775
776 let mut duplicate_vm = vec![ExecutionProofCompatibility::FORMAT_V2];
777 vec![root(1), root(1)].write_into(&mut duplicate_vm);
778 Vec::<Word>::new().write_into(&mut duplicate_vm);
779 duplicate_vm.extend_from_slice(body);
780 let error = ExecutionProof::read_from_bytes(&duplicate_vm).unwrap_err();
781 assert!(
782 matches!(error, DeserializationError::InvalidValue(message) if message.contains("VM verifier roots must not contain duplicates"))
783 );
784
785 let mut duplicate_pvm = vec![ExecutionProofCompatibility::FORMAT_V2];
786 Vec::<Word>::new().write_into(&mut duplicate_pvm);
787 vec![root(2), root(2)].write_into(&mut duplicate_pvm);
788 duplicate_pvm.extend_from_slice(body);
789 let error = ExecutionProof::read_from_bytes(&duplicate_pvm).unwrap_err();
790 assert!(
791 matches!(error, DeserializationError::InvalidValue(message) if message.contains("PVM verifier roots must not contain duplicates"))
792 );
793 }
794
795 #[test]
796 fn proof_minimum_serialized_sizes_match_shortest_canonical_encodings() {
797 let stark = StarkProof::new(Vec::new(), HashFunction::Eidos);
798 assert_eq!(StarkProof::min_serialized_size(), stark.to_bytes().len());
799 assert_eq!(StarkProof::min_serialized_size(), 2);
800
801 let vm = VmProof {
802 proof: stark,
803 precompile_root: TRUE_DIGEST,
804 };
805 assert_eq!(VmProof::min_serialized_size(), vm.to_bytes().len());
806 assert_eq!(VmProof::min_serialized_size(), 34);
807
808 let empty = PrecompileProof {
809 proof: StarkProof::new(Vec::new(), HashFunction::Eidos),
810 roots: Vec::new(),
811 };
812 assert_eq!(PrecompileProof::min_serialized_size(), empty.to_bytes().len());
813 assert_eq!(PrecompileProof::min_serialized_size(), 3);
814
815 let singleton = PrecompileProof {
816 proof: StarkProof::new(Vec::new(), HashFunction::Eidos),
817 roots: alloc::vec![root(1)],
818 };
819 assert_eq!(singleton.to_bytes().len(), 35);
820
821 let proofs = alloc::vec![singleton.clone(), singleton];
822 let bytes = proofs.to_bytes();
823 assert_eq!(bytes.len(), 71);
824 let decoded = Vec::<PrecompileProof>::read_from_bytes_with_budget(&bytes, 71).unwrap();
825 assert_eq!(decoded.to_bytes(), bytes);
826 }
827
828 #[test]
829 fn stark_proof_decoder_rejects_oversized_length_before_payload() {
830 let mut bytes = Vec::new();
831 bytes.write_usize(MAX_STARK_PROOF_BYTES + 1);
832
833 let error = StarkProof::read_from_bytes(&bytes).unwrap_err();
834 let DeserializationError::InvalidValue(message) = error else {
835 panic!("expected excessive STARK proof length to be rejected")
836 };
837 assert!(message.contains("STARK proof contains too many bytes"));
838 }
839
840 #[test]
841 fn precompile_proof_decoder_rejects_oversized_root_count_before_payload() {
842 let at_limit = precompile_proof(&[root(3); MAX_PRECOMPILE_ROOTS]);
843 assert_eq!(PrecompileProof::read_from_bytes(&at_limit.to_bytes()).unwrap(), at_limit);
844 let mut bytes = dummy_stark_proof(&[2]).to_bytes();
845 bytes.write_usize(MAX_PRECOMPILE_ROOTS + 1);
846
847 let error = PrecompileProof::read_from_bytes(&bytes).unwrap_err();
848 let DeserializationError::InvalidValue(message) = error else {
849 panic!("expected excessive root count to be rejected")
850 };
851 assert!(message.contains("precompile proof contains too many roots"));
852 }
853
854 #[test]
855 fn standalone_proof_decoders_reject_trailing_bytes() {
856 let mut vm_bytes = vm_proof(root(3)).to_bytes();
857 vm_bytes.push(0);
858 assert!(VmProof::read_from_bytes(&vm_bytes).is_err());
859
860 let mut precompile_bytes = precompile_proof(&[root(3)]).to_bytes();
861 precompile_bytes.push(0);
862 assert!(PrecompileProof::read_from_bytes(&precompile_bytes).is_err());
863 }
864
865 #[test]
866 fn proof_artifacts_round_trip_canonically() {
867 let stark = dummy_stark_proof(&[1, 2, 3]);
868 let stark_bytes = stark.to_bytes();
869 let decoded_stark = StarkProof::read_from_bytes(&stark_bytes).unwrap();
870 assert_eq!(decoded_stark.to_bytes(), stark_bytes);
871
872 let vm = vm_proof(root(3));
873 let vm_bytes = vm.to_bytes();
874 let decoded_vm = VmProof::read_from_bytes(&vm_bytes).unwrap();
875 assert_eq!(decoded_vm.to_bytes(), vm_bytes);
876
877 let precompile = precompile_proof(&[]);
878 let precompile_bytes = precompile.to_bytes();
879 let decoded_precompile = PrecompileProof::read_from_bytes(&precompile_bytes).unwrap();
880 assert_eq!(decoded_precompile.to_bytes(), precompile_bytes);
881
882 let (precompile_wire, wire_root) = wire();
883 let proofs = [
884 versioned(vm_proof(wire_root), PrecompileStatus::Deferred(precompile_wire)),
885 versioned(vm_proof(TRUE_DIGEST), PrecompileStatus::Empty),
886 versioned(
887 vm_proof(TRUE_DIGEST),
888 PrecompileStatus::Proven(precompile_proof(&[TRUE_DIGEST])),
889 ),
890 ];
891 for proof in proofs {
892 let bytes = proof.to_bytes();
893 assert_eq!(ExecutionProof::read_from_bytes(&bytes).unwrap(), proof);
894 }
895 }
896
897 #[test]
898 fn complete_transitions_deferred_proof_without_validating_artifact_shape() {
899 let vm = vm_proof(TRUE_DIGEST);
900 let precompile = precompile_proof(&[]);
901 let deferred = versioned(vm.clone(), PrecompileStatus::Deferred(wire().0));
902
903 let completed = deferred.complete(precompile.clone()).unwrap();
904
905 let (completed_compatibility, completed_vm, completed_precompile) = completed.into_parts();
906 let PrecompileStatus::Proven(completed_precompile) = completed_precompile else {
907 panic!("deferred proof should transition to complete")
908 };
909 assert_eq!(
910 completed_compatibility,
911 ExecutionProofCompatibility::new(vec![root(11), root(12)], vec![root(21)]).unwrap()
912 );
913 assert_eq!(completed_vm.to_bytes(), vm.to_bytes());
914 assert_eq!(completed_precompile.to_bytes(), precompile.to_bytes());
915 }
916
917 #[test]
918 fn complete_rejects_an_already_complete_proof() {
919 let complete = versioned(vm_proof(TRUE_DIGEST), PrecompileStatus::Empty);
920
921 assert!(matches!(
922 complete.complete(precompile_proof(&[])),
923 Err(ExecutionProofError::AlreadyComplete)
924 ));
925 }
926
927 #[test]
928 fn versioned_proof_transport_rejects_bad_discriminants_and_stark_bounds() {
929 let mut bad_discriminant = version_prefix();
930 bad_discriminant.write_u8(9);
931 assert!(ExecutionProof::read_from_bytes(&bad_discriminant).is_err());
932
933 let canonical = versioned(vm_proof(TRUE_DIGEST), PrecompileStatus::Empty).to_bytes();
934 let prefix_len = version_prefix().len();
935 assert_eq!(canonical[prefix_len + 1], 3, "one STARK byte uses a one-byte vint encoding");
936 let mut noncanonical = canonical[..prefix_len + 1].to_vec();
937 noncanonical.push(0);
938 noncanonical.extend_from_slice(&1u64.to_le_bytes());
939 noncanonical.extend_from_slice(&canonical[prefix_len + 2..]);
940 let error = ExecutionProof::read_from_bytes(&noncanonical).unwrap_err();
941 assert!(
942 matches!(error, DeserializationError::InvalidValue(message) if message.contains("not canonically encoded"))
943 );
944
945 let mut oversized_proof = version_prefix();
946 oversized_proof.write_u8(COMPLETE_PROOF_DISCRIMINANT);
947 oversized_proof.write_usize(MAX_STARK_PROOF_BYTES + 1);
948 let error = ExecutionProof::read_from_bytes(&oversized_proof).unwrap_err();
949 assert!(
950 matches!(error, DeserializationError::InvalidValue(message) if message.contains("STARK proof contains too many bytes"))
951 );
952 }
953}
954
955#[cfg(any(test, feature = "arbitrary"))]
959mod proof_arbitrary {
960 use proptest::prelude::*;
961
962 use super::{
963 ExecutionProof, ExecutionProofCompatibility, HashFunction, PrecompileProof,
964 PrecompileStatus, PrecompileWitness, StarkProof, VmProof,
965 };
966 use crate::{Felt, Word};
967
968 impl Arbitrary for StarkProof {
969 type Parameters = ();
970 type Strategy = BoxedStrategy<Self>;
971
972 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
973 (
976 proptest::collection::vec(any::<u8>(), 0..=64),
977 prop_oneof![
978 Just(HashFunction::Blake3_256),
979 Just(HashFunction::Keccak),
980 Just(HashFunction::Eidos),
981 ],
982 )
983 .prop_map(|(bytes, hash_fn)| StarkProof::new(bytes, hash_fn))
984 .boxed()
985 }
986 }
987
988 impl Arbitrary for VmProof {
989 type Parameters = ();
990 type Strategy = BoxedStrategy<Self>;
991
992 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
993 (any::<StarkProof>(), any::<Word>())
995 .prop_map(|(proof, precompile_root)| Self { proof, precompile_root })
996 .boxed()
997 }
998 }
999
1000 impl Arbitrary for PrecompileProof {
1001 type Parameters = ();
1002 type Strategy = BoxedStrategy<Self>;
1003
1004 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
1005 (any::<StarkProof>(), proptest::collection::vec(any::<Word>(), 0..=4))
1007 .prop_map(|(proof, roots)| Self { proof, roots })
1008 .boxed()
1009 }
1010 }
1011
1012 impl Arbitrary for PrecompileStatus {
1013 type Parameters = ();
1014 type Strategy = BoxedStrategy<Self>;
1015
1016 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
1017 prop_oneof![
1018 Just(Self::Empty),
1019 any::<PrecompileWitness>().prop_map(Self::Deferred).boxed(),
1020 any::<PrecompileProof>().prop_map(Self::Proven).boxed(),
1021 ]
1022 .boxed()
1023 }
1024 }
1025
1026 impl Arbitrary for ExecutionProof {
1027 type Parameters = ();
1028 type Strategy = BoxedStrategy<Self>;
1029
1030 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
1031 (0usize..=3, 0usize..=3, any::<VmProof>(), any::<PrecompileStatus>())
1034 .prop_map(|(vm_count, pvm_count, vm, precompile)| {
1035 let compatibility = ExecutionProofCompatibility::new(
1036 (0..vm_count).map(|i| [Felt::from(i as u32); 4].into()).collect(),
1037 (0..pvm_count).map(|i| [Felt::from((i as u32) + 1000); 4].into()).collect(),
1038 )
1039 .expect("index-derived roots are distinct");
1040 Self::from_parts(compatibility, vm, precompile)
1041 })
1042 .boxed()
1043 }
1044 }
1045}