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, Poseidon2, Rpo256, Rpx256},
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(7434262842308815738),
32 crate::Felt::new_unchecked(8308347286077348452),
33 crate::Felt::new_unchecked(5536370215252113983),
34 crate::Felt::new_unchecked(12836609874872806107),
35]);
36pub const CURRENT_PVM_VERIFIER_ROOT: Word = Word::new([
38 crate::Felt::new_unchecked(12831523712082380442),
39 crate::Felt::new_unchecked(17828351797951166499),
40 crate::Felt::new_unchecked(7688574826409945056),
41 crate::Felt::new_unchecked(3055905288509180742),
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 Rpo256 = 0x02,
59 Rpx256 = 0x03,
61 Poseidon2 = 0x04,
63 Keccak = 0x05,
65}
66
67impl HashFunction {
68 pub const fn collision_resistance(&self) -> u32 {
70 match self {
71 HashFunction::Blake3_256 => Blake3_256::COLLISION_RESISTANCE,
72 HashFunction::Rpo256 => Rpo256::COLLISION_RESISTANCE,
73 HashFunction::Rpx256 => Rpx256::COLLISION_RESISTANCE,
74 HashFunction::Poseidon2 => Poseidon2::COLLISION_RESISTANCE,
75 HashFunction::Keccak => 128,
76 }
77 }
78}
79
80#[derive(Debug, thiserror::Error)]
82#[error(
83 "invalid hash function '{hash_function}'. Valid options are: blake3-256, rpo, rpx, poseidon2, keccak"
84)]
85pub struct InvalidHashFunctionError {
86 pub hash_function: String,
87}
88
89impl TryFrom<u8> for HashFunction {
90 type Error = DeserializationError;
91
92 fn try_from(repr: u8) -> Result<Self, Self::Error> {
93 match repr {
94 0x01 => Ok(Self::Blake3_256),
95 0x02 => Ok(Self::Rpo256),
96 0x03 => Ok(Self::Rpx256),
97 0x04 => Ok(Self::Poseidon2),
98 0x05 => Ok(Self::Keccak),
99 _ => Err(DeserializationError::InvalidValue(format!(
100 "the hash function representation {repr} is not valid!"
101 ))),
102 }
103 }
104}
105
106impl TryFrom<&str> for HashFunction {
107 type Error = InvalidHashFunctionError;
108
109 fn try_from(hash_fn_str: &str) -> Result<Self, Self::Error> {
110 match hash_fn_str {
111 "blake3-256" => Ok(Self::Blake3_256),
112 "rpo" => Ok(Self::Rpo256),
113 "rpx" => Ok(Self::Rpx256),
114 "poseidon2" => Ok(Self::Poseidon2),
115 "keccak" => Ok(Self::Keccak),
116 _ => Err(InvalidHashFunctionError { hash_function: hash_fn_str.to_string() }),
117 }
118 }
119}
120
121#[cfg(feature = "arbitrary")]
122impl Arbitrary for HashFunction {
123 type Parameters = ();
124 type Strategy = BoxedStrategy<Self>;
125
126 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
127 any::<u8>()
128 .prop_map(|tag| match tag % 5 {
129 0 => Self::Blake3_256,
130 1 => Self::Rpo256,
131 2 => Self::Rpx256,
132 3 => Self::Poseidon2,
133 _ => Self::Keccak,
134 })
135 .boxed()
136 }
137}
138
139impl Serializable for HashFunction {
140 fn write_into<W: ByteWriter>(&self, target: &mut W) {
141 target.write_u8(*self as u8);
142 }
143}
144
145impl Deserializable for HashFunction {
146 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
147 source.read_u8()?.try_into()
148 }
149}
150
151#[derive(Debug, Clone, PartialEq, Eq)]
156pub struct VmProof {
157 pub proof: StarkProof,
158 pub precompile_root: DeferredRoot,
159}
160
161impl Serializable for VmProof {
162 fn write_into<W: ByteWriter>(&self, target: &mut W) {
163 self.proof.write_into(target);
164 self.precompile_root.write_into(target);
165 }
166}
167
168impl Deserializable for VmProof {
169 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
170 let proof = StarkProof::read_from(source)?;
171 let precompile_root = DeferredRoot::read_from(source)?;
172 Ok(Self { proof, precompile_root })
173 }
174
175 fn read_from_bytes(bytes: &[u8]) -> Result<Self, DeserializationError> {
176 let mut reader = BudgetedReader::new(SliceReader::new(bytes), bytes.len());
177 let proof = Self::read_from(&mut reader)?;
178 if reader.has_more_bytes() {
179 return Err(DeserializationError::InvalidValue(
180 "extra bytes after VM proof payload".into(),
181 ));
182 }
183 Ok(proof)
184 }
185
186 fn min_serialized_size() -> usize {
187 StarkProof::min_serialized_size() + DeferredRoot::min_serialized_size()
188 }
189}
190
191#[derive(Debug, Clone, PartialEq, Eq)]
196pub struct PrecompileProof {
197 pub proof: StarkProof,
198 pub roots: Vec<DeferredRoot>,
199}
200
201impl PrecompileProof {
202 pub fn aggregate_root(&self) -> Option<DeferredRoot> {
208 self.roots.iter().copied().reduce(fold_deferred_root)
209 }
210}
211
212impl Serializable for PrecompileProof {
213 fn write_into<W: ByteWriter>(&self, target: &mut W) {
214 self.proof.write_into(target);
215 self.roots.write_into(target);
216 }
217}
218
219impl Deserializable for PrecompileProof {
220 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
221 let proof = StarkProof::read_from(source)?;
222 let root_count = source.read_usize()?;
223 if root_count > MAX_PRECOMPILE_ROOTS {
224 return Err(DeserializationError::InvalidValue(format!(
225 "precompile proof contains too many roots: found {root_count}, maximum is {MAX_PRECOMPILE_ROOTS}"
226 )));
227 }
228 let roots = source
229 .read_many_iter::<DeferredRoot>(root_count)?
230 .collect::<Result<Vec<_>, _>>()?;
231 Ok(Self { proof, roots })
232 }
233
234 fn read_from_bytes(bytes: &[u8]) -> Result<Self, DeserializationError> {
235 let mut reader = BudgetedReader::new(SliceReader::new(bytes), bytes.len());
236 let proof = Self::read_from(&mut reader)?;
237 if reader.has_more_bytes() {
238 return Err(DeserializationError::InvalidValue(
239 "extra bytes after precompile proof payload".into(),
240 ));
241 }
242 Ok(proof)
243 }
244
245 fn min_serialized_size() -> usize {
246 StarkProof::min_serialized_size() + usize::min_serialized_size()
247 }
248}
249
250#[derive(Debug, Clone, PartialEq, Eq)]
255pub struct ExecutionProofCompatibility {
256 format: u8,
257 vm_verifier_roots: Vec<Word>,
258 pvm_verifier_roots: Vec<Word>,
259}
260
261impl ExecutionProofCompatibility {
262 pub const FORMAT_V2: u8 = 2;
264
265 pub fn new(
271 vm_verifier_roots: Vec<Word>,
272 pvm_verifier_roots: Vec<Word>,
273 ) -> Result<Self, ExecutionProofCompatibilityError> {
274 if has_duplicate(&vm_verifier_roots) {
275 return Err(ExecutionProofCompatibilityError::DuplicateVmVerifierRoot);
276 }
277 if has_duplicate(&pvm_verifier_roots) {
278 return Err(ExecutionProofCompatibilityError::DuplicatePvmVerifierRoot);
279 }
280
281 Ok(Self {
282 format: Self::FORMAT_V2,
283 vm_verifier_roots,
284 pvm_verifier_roots,
285 })
286 }
287
288 pub fn current() -> Self {
290 Self::new(alloc::vec![CURRENT_VM_VERIFIER_ROOT], alloc::vec![CURRENT_PVM_VERIFIER_ROOT])
291 .expect("current execution proof compatibility must not contain duplicate roots")
292 }
293
294 pub const fn format(&self) -> u8 {
296 self.format
297 }
298
299 pub fn vm_verifier_roots(&self) -> &[Word] {
304 &self.vm_verifier_roots
305 }
306
307 pub fn pvm_verifier_roots(&self) -> &[Word] {
313 &self.pvm_verifier_roots
314 }
315}
316
317#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
319pub enum ExecutionProofCompatibilityError {
320 #[error("VM verifier roots must not contain duplicates")]
322 DuplicateVmVerifierRoot,
323 #[error("PVM verifier roots must not contain duplicates")]
325 DuplicatePvmVerifierRoot,
326}
327
328#[derive(Debug, Clone, PartialEq, Eq)]
330pub enum PrecompileStatus {
331 Empty,
333 Deferred(PrecompileWitness),
335 Proven(PrecompileProof),
337}
338
339#[derive(Debug, Clone, PartialEq, Eq)]
343pub struct ExecutionProof {
344 compatibility: ExecutionProofCompatibility,
345 vm: VmProof,
346 precompile: PrecompileStatus,
347}
348
349impl ExecutionProof {
350 pub fn new(vm: VmProof, precompile: PrecompileStatus) -> Self {
352 Self::from_parts(ExecutionProofCompatibility::current(), vm, precompile)
353 }
354
355 pub const fn from_parts(
357 compatibility: ExecutionProofCompatibility,
358 vm: VmProof,
359 precompile: PrecompileStatus,
360 ) -> Self {
361 Self { compatibility, vm, precompile }
362 }
363
364 pub const fn compatibility(&self) -> &ExecutionProofCompatibility {
366 &self.compatibility
367 }
368
369 pub const fn vm(&self) -> &VmProof {
371 &self.vm
372 }
373
374 pub const fn precompile(&self) -> &PrecompileStatus {
378 &self.precompile
379 }
380
381 pub fn into_parts(self) -> (ExecutionProofCompatibility, VmProof, PrecompileStatus) {
383 (self.compatibility, self.vm, self.precompile)
384 }
385
386 pub const fn has_precompiles(&self) -> bool {
388 !matches!(self.precompile, PrecompileStatus::Empty)
389 }
390
391 pub fn complete(mut self, precompile: PrecompileProof) -> Result<Self, ExecutionProofError> {
396 if !matches!(self.precompile, PrecompileStatus::Deferred(_)) {
397 return Err(ExecutionProofError::AlreadyComplete);
398 }
399 self.precompile = PrecompileStatus::Proven(precompile);
400 Ok(self)
401 }
402
403 pub fn to_bytes(&self) -> Vec<u8> {
405 Serializable::to_bytes(self)
406 }
407
408 pub fn read_from_bytes(bytes: &[u8]) -> Result<Self, DeserializationError> {
410 let mut reader = BudgetedReader::new(SliceReader::new(bytes), bytes.len());
411 let proof = <Self as Deserializable>::read_from(&mut reader)?;
412
413 if reader.has_more_bytes() {
414 return Err(DeserializationError::InvalidValue(
415 "extra bytes after versioned proof payload".into(),
416 ));
417 }
418 if proof.to_bytes() != bytes {
419 return Err(DeserializationError::InvalidValue(
420 "versioned proof bytes are not canonically encoded".into(),
421 ));
422 }
423
424 Ok(proof)
425 }
426}
427
428impl Serializable for ExecutionProof {
429 fn write_into<W: ByteWriter>(&self, target: &mut W) {
430 target.write_u8(self.compatibility.format);
431 self.compatibility.vm_verifier_roots.write_into(target);
432 self.compatibility.pvm_verifier_roots.write_into(target);
433 self.write_into_v2(target);
434 }
435}
436
437impl Deserializable for ExecutionProof {
438 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
439 let format = source.read_u8()?;
440 if format != ExecutionProofCompatibility::FORMAT_V2 {
441 return Err(DeserializationError::InvalidValue(format!(
442 "unsupported execution proof format {format}"
443 )));
444 }
445
446 let vm_verifier_roots = Vec::<Word>::read_from(source)?;
447 let pvm_verifier_roots = Vec::<Word>::read_from(source)?;
448 let compatibility = ExecutionProofCompatibility::new(vm_verifier_roots, pvm_verifier_roots)
449 .map_err(|error| DeserializationError::InvalidValue(error.to_string()))?;
450
451 Self::read_from_v2(source, compatibility)
452 }
453
454 fn min_serialized_size() -> usize {
455 u8::min_serialized_size()
456 + Vec::<Word>::min_serialized_size()
457 + Vec::<Word>::min_serialized_size()
458 + ExecutionProof::min_serialized_size_v2()
459 }
460}
461
462impl ExecutionProof {
463 fn write_into_v2<W: ByteWriter>(&self, target: &mut W) {
464 match &self.precompile {
465 PrecompileStatus::Deferred(precompile) => {
466 target.write_u8(DEFERRED_PROOF_DISCRIMINANT);
467 self.vm.write_into(target);
468 precompile.write_into(target);
469 },
470 PrecompileStatus::Empty => {
471 target.write_u8(COMPLETE_PROOF_DISCRIMINANT);
472 self.vm.write_into(target);
473 Option::<PrecompileProof>::None.write_into(target);
474 },
475 PrecompileStatus::Proven(precompile) => {
476 target.write_u8(COMPLETE_PROOF_DISCRIMINANT);
477 self.vm.write_into(target);
478 Some(precompile).write_into(target);
479 },
480 }
481 }
482
483 fn read_from_v2<R: ByteReader>(
484 source: &mut R,
485 compatibility: ExecutionProofCompatibility,
486 ) -> Result<Self, DeserializationError> {
487 let discriminant = source.read_u8()?;
488 if !matches!(discriminant, DEFERRED_PROOF_DISCRIMINANT | COMPLETE_PROOF_DISCRIMINANT) {
489 return Err(DeserializationError::InvalidValue(format!(
490 "invalid execution proof discriminant {discriminant}"
491 )));
492 }
493
494 let vm = VmProof::read_from(source)?;
495 let precompile = match discriminant {
496 DEFERRED_PROOF_DISCRIMINANT => {
497 PrecompileStatus::Deferred(PrecompileWitness::read_from(source)?)
498 },
499 COMPLETE_PROOF_DISCRIMINANT => match Option::<PrecompileProof>::read_from(source)? {
500 Some(precompile) => PrecompileStatus::Proven(precompile),
501 None => PrecompileStatus::Empty,
502 },
503 _ => unreachable!("execution proof discriminant was checked before decoding"),
504 };
505
506 Ok(Self { compatibility, vm, precompile })
507 }
508
509 fn min_serialized_size_v2() -> usize {
510 u8::min_serialized_size()
511 + VmProof::min_serialized_size()
512 + Option::<PrecompileProof>::min_serialized_size()
513 }
514}
515
516#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
518pub enum ExecutionProofError {
519 #[error("the execution proof is already complete")]
521 AlreadyComplete,
522}
523
524#[derive(Debug, Clone, PartialEq, Eq)]
529pub struct StarkProof {
530 bytes: Vec<u8>,
531 hash_fn: HashFunction,
532}
533
534impl StarkProof {
535 pub const fn new(bytes: Vec<u8>, hash_fn: HashFunction) -> Self {
537 Self { bytes, hash_fn }
538 }
539
540 pub fn bytes(&self) -> &[u8] {
542 &self.bytes
543 }
544
545 pub const fn hash_fn(&self) -> HashFunction {
547 self.hash_fn
548 }
549
550 pub fn into_parts(self) -> (Vec<u8>, HashFunction) {
552 (self.bytes, self.hash_fn)
553 }
554}
555
556impl Serializable for StarkProof {
557 fn write_into<W: ByteWriter>(&self, target: &mut W) {
558 self.bytes.write_into(target);
559 self.hash_fn.write_into(target);
560 }
561}
562
563impl Deserializable for StarkProof {
564 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
565 let byte_count = source.read_usize()?;
566 if byte_count > MAX_STARK_PROOF_BYTES {
567 return Err(DeserializationError::InvalidValue(format!(
568 "STARK proof contains too many bytes: found {byte_count}, maximum is {MAX_STARK_PROOF_BYTES}"
569 )));
570 }
571 let bytes = source.read_many_iter::<u8>(byte_count)?.collect::<Result<Vec<_>, _>>()?;
572 let hash_fn = HashFunction::read_from(source)?;
573 Ok(Self::new(bytes, hash_fn))
574 }
575
576 fn read_from_bytes(bytes: &[u8]) -> Result<Self, DeserializationError> {
577 let mut reader = BudgetedReader::new(SliceReader::new(bytes), bytes.len());
578 Self::read_from(&mut reader)
579 }
580
581 fn min_serialized_size() -> usize {
582 Vec::<u8>::min_serialized_size() + HashFunction::min_serialized_size()
583 }
584}
585
586fn has_duplicate(roots: &[Word]) -> bool {
590 let mut unique = BTreeSet::new();
591 roots.iter().any(|root| !unique.insert(*root))
592}
593
594#[cfg(test)]
598mod tests {
599 use super::*;
600 use crate::{
601 Felt,
602 deferred::{Node, PrecompileWitnessEntry, TRUE_DIGEST, Tag},
603 serde::ByteWriter,
604 };
605
606 fn dummy_stark_proof(bytes: &[u8]) -> StarkProof {
607 StarkProof::new(bytes.to_vec(), HashFunction::Blake3_256)
608 }
609
610 fn root(value: u64) -> DeferredRoot {
611 [Felt::new(value).unwrap(), Felt::ZERO, Felt::ZERO, Felt::ZERO].into()
612 }
613
614 fn vm_proof(precompile_root: DeferredRoot) -> VmProof {
615 VmProof {
616 proof: dummy_stark_proof(&[1]),
617 precompile_root,
618 }
619 }
620
621 fn precompile_proof(roots: &[DeferredRoot]) -> PrecompileProof {
622 PrecompileProof {
623 proof: dummy_stark_proof(&[2]),
624 roots: roots.to_vec(),
625 }
626 }
627
628 #[test]
629 fn aggregate_root_preserves_order_grouping_and_duplicates() {
630 let a = root(1);
631 let b = root(2);
632 let c = root(3);
633 let and = |lhs, rhs| Node::and(lhs, rhs).digest();
634
635 assert_eq!(precompile_proof(&[]).aggregate_root(), None);
636 assert_eq!(precompile_proof(&[a]).aggregate_root(), Some(a));
637 assert_eq!(precompile_proof(&[a, b, c]).aggregate_root(), Some(and(and(a, b), c)));
638 assert_ne!(precompile_proof(&[a, b, c]).aggregate_root(), Some(and(a, and(b, c))));
639 assert_ne!(
640 precompile_proof(&[a, b]).aggregate_root(),
641 precompile_proof(&[b, a]).aggregate_root()
642 );
643 assert_eq!(precompile_proof(&[a, b, a]).aggregate_root(), Some(and(and(a, b), a)));
644 }
645
646 fn wire() -> (PrecompileWitness, DeferredRoot) {
647 let witness = PrecompileWitness::from_entries(vec![PrecompileWitnessEntry::Join {
648 tag: Tag::AND,
649 lhs: 0,
650 rhs: 0,
651 }])
652 .unwrap();
653 let root = witness.root_unchecked();
654 (witness, root)
655 }
656
657 fn versioned(vm: VmProof, precompile: PrecompileStatus) -> ExecutionProof {
658 ExecutionProof::from_parts(
659 ExecutionProofCompatibility::new(vec![root(11), root(12)], vec![root(21)]).unwrap(),
660 vm,
661 precompile,
662 )
663 }
664
665 fn version_prefix() -> Vec<u8> {
666 let compatibility =
667 ExecutionProofCompatibility::new(vec![root(11), root(12)], vec![root(21)]).unwrap();
668 let mut bytes = vec![compatibility.format()];
669 compatibility.vm_verifier_roots().to_vec().write_into(&mut bytes);
670 compatibility.pvm_verifier_roots().to_vec().write_into(&mut bytes);
671 bytes
672 }
673
674 #[test]
675 fn execution_proof_reports_precompile_state() {
676 let (precompile_wire, wire_root) = wire();
677 let deferred = versioned(vm_proof(wire_root), PrecompileStatus::Deferred(precompile_wire));
678 assert!(deferred.has_precompiles());
679
680 let complete_without_precompile = versioned(vm_proof(TRUE_DIGEST), PrecompileStatus::Empty);
681 assert!(!complete_without_precompile.has_precompiles());
682
683 let complete_with_precompile =
684 versioned(vm_proof(root(1)), PrecompileStatus::Proven(precompile_proof(&[root(1)])));
685 assert!(complete_with_precompile.has_precompiles());
686 }
687
688 #[test]
689 fn versioned_proof_round_trips_with_format_first() {
690 let proof = versioned(vm_proof(TRUE_DIGEST), PrecompileStatus::Empty);
691
692 let bytes = proof.to_bytes();
693 let decoded = ExecutionProof::read_from_bytes(&bytes).unwrap();
694
695 assert_eq!(bytes[0], ExecutionProofCompatibility::FORMAT_V2);
696 assert_eq!(decoded, proof);
697 assert_eq!(decoded.compatibility().format(), ExecutionProofCompatibility::FORMAT_V2);
698 assert_eq!(decoded.compatibility().vm_verifier_roots(), &[root(11), root(12)]);
699 assert_eq!(decoded.compatibility().pvm_verifier_roots(), &[root(21)]);
700 }
701
702 #[test]
703 fn versioned_proof_decoder_rejects_unknown_format_before_body() {
704 for version in [0, 1, ExecutionProofCompatibility::FORMAT_V2 + 1] {
705 let error = ExecutionProof::read_from_bytes(&[version]).unwrap_err();
706 assert!(matches!(error, DeserializationError::InvalidValue(message)
707 if message.contains("unsupported execution proof format")));
708 }
709 }
710
711 #[test]
712 fn versioned_proof_decoder_rejects_trailing_and_noncanonical_bytes() {
713 let proof = versioned(vm_proof(TRUE_DIGEST), PrecompileStatus::Empty);
714 let mut trailing = proof.to_bytes();
715 trailing.push(0);
716 assert!(ExecutionProof::read_from_bytes(&trailing).is_err());
717
718 let canonical = proof.to_bytes();
719 assert_eq!(canonical[1], 5, "two VM roots use a one-byte vint encoding");
720 let mut noncanonical = alloc::vec![canonical[0], 0];
721 noncanonical.extend_from_slice(&2u64.to_le_bytes());
722 noncanonical.extend_from_slice(&canonical[2..]);
723 let error = ExecutionProof::read_from_bytes(&noncanonical).unwrap_err();
724 assert!(
725 matches!(error, DeserializationError::InvalidValue(message) if message.contains("not canonically encoded"))
726 );
727 }
728
729 #[test]
730 fn versioned_proof_decoder_applies_the_input_budget_to_root_lists() {
731 let mut bytes = vec![ExecutionProofCompatibility::FORMAT_V2];
732 bytes.write_usize(usize::MAX);
733
734 let error = ExecutionProof::read_from_bytes(&bytes).unwrap_err();
735 assert!(matches!(error, DeserializationError::InvalidValue(_)));
736 }
737
738 #[test]
739 fn compatibility_constructor_rejects_duplicate_roots() {
740 assert_eq!(
741 ExecutionProofCompatibility::new(vec![root(1), root(1)], vec![]),
742 Err(ExecutionProofCompatibilityError::DuplicateVmVerifierRoot)
743 );
744 assert_eq!(
745 ExecutionProofCompatibility::new(vec![], vec![root(2), root(2)]),
746 Err(ExecutionProofCompatibilityError::DuplicatePvmVerifierRoot)
747 );
748 }
749
750 #[test]
751 fn versioned_proof_decoder_rejects_duplicate_roots() {
752 let proof = versioned(vm_proof(TRUE_DIGEST), PrecompileStatus::Empty);
753 let proof_bytes = proof.to_bytes();
754 let body = &proof_bytes[version_prefix().len()..];
755
756 let mut duplicate_vm = vec![ExecutionProofCompatibility::FORMAT_V2];
757 vec![root(1), root(1)].write_into(&mut duplicate_vm);
758 Vec::<Word>::new().write_into(&mut duplicate_vm);
759 duplicate_vm.extend_from_slice(body);
760 let error = ExecutionProof::read_from_bytes(&duplicate_vm).unwrap_err();
761 assert!(
762 matches!(error, DeserializationError::InvalidValue(message) if message.contains("VM verifier roots must not contain duplicates"))
763 );
764
765 let mut duplicate_pvm = vec![ExecutionProofCompatibility::FORMAT_V2];
766 Vec::<Word>::new().write_into(&mut duplicate_pvm);
767 vec![root(2), root(2)].write_into(&mut duplicate_pvm);
768 duplicate_pvm.extend_from_slice(body);
769 let error = ExecutionProof::read_from_bytes(&duplicate_pvm).unwrap_err();
770 assert!(
771 matches!(error, DeserializationError::InvalidValue(message) if message.contains("PVM verifier roots must not contain duplicates"))
772 );
773 }
774
775 #[test]
776 fn proof_minimum_serialized_sizes_match_shortest_canonical_encodings() {
777 let stark = StarkProof::new(Vec::new(), HashFunction::Blake3_256);
778 assert_eq!(StarkProof::min_serialized_size(), stark.to_bytes().len());
779 assert_eq!(StarkProof::min_serialized_size(), 2);
780
781 let vm = VmProof {
782 proof: stark,
783 precompile_root: TRUE_DIGEST,
784 };
785 assert_eq!(VmProof::min_serialized_size(), vm.to_bytes().len());
786 assert_eq!(VmProof::min_serialized_size(), 34);
787
788 let empty = PrecompileProof {
789 proof: StarkProof::new(Vec::new(), HashFunction::Blake3_256),
790 roots: Vec::new(),
791 };
792 assert_eq!(PrecompileProof::min_serialized_size(), empty.to_bytes().len());
793 assert_eq!(PrecompileProof::min_serialized_size(), 3);
794
795 let singleton = PrecompileProof {
796 proof: StarkProof::new(Vec::new(), HashFunction::Blake3_256),
797 roots: alloc::vec![root(1)],
798 };
799 assert_eq!(singleton.to_bytes().len(), 35);
800
801 let proofs = alloc::vec![singleton.clone(), singleton];
802 let bytes = proofs.to_bytes();
803 assert_eq!(bytes.len(), 71);
804 let decoded = Vec::<PrecompileProof>::read_from_bytes_with_budget(&bytes, 71).unwrap();
805 assert_eq!(decoded.to_bytes(), bytes);
806 }
807
808 #[test]
809 fn stark_proof_decoder_rejects_oversized_length_before_payload() {
810 let mut bytes = Vec::new();
811 bytes.write_usize(MAX_STARK_PROOF_BYTES + 1);
812
813 let error = StarkProof::read_from_bytes(&bytes).unwrap_err();
814 let DeserializationError::InvalidValue(message) = error else {
815 panic!("expected excessive STARK proof length to be rejected")
816 };
817 assert!(message.contains("STARK proof contains too many bytes"));
818 }
819
820 #[test]
821 fn precompile_proof_decoder_rejects_oversized_root_count_before_payload() {
822 let mut bytes = dummy_stark_proof(&[2]).to_bytes();
823 bytes.write_usize(MAX_PRECOMPILE_ROOTS + 1);
824
825 let error = PrecompileProof::read_from_bytes(&bytes).unwrap_err();
826 let DeserializationError::InvalidValue(message) = error else {
827 panic!("expected excessive root count to be rejected")
828 };
829 assert!(message.contains("precompile proof contains too many roots"));
830 }
831
832 #[test]
833 fn standalone_proof_decoders_reject_trailing_bytes() {
834 let mut vm_bytes = vm_proof(root(3)).to_bytes();
835 vm_bytes.push(0);
836 assert!(VmProof::read_from_bytes(&vm_bytes).is_err());
837
838 let mut precompile_bytes = precompile_proof(&[root(3)]).to_bytes();
839 precompile_bytes.push(0);
840 assert!(PrecompileProof::read_from_bytes(&precompile_bytes).is_err());
841 }
842
843 #[test]
844 fn proof_artifacts_round_trip_canonically() {
845 let stark = dummy_stark_proof(&[1, 2, 3]);
846 let stark_bytes = stark.to_bytes();
847 let decoded_stark = StarkProof::read_from_bytes(&stark_bytes).unwrap();
848 assert_eq!(decoded_stark.to_bytes(), stark_bytes);
849
850 let vm = vm_proof(root(3));
851 let vm_bytes = vm.to_bytes();
852 let decoded_vm = VmProof::read_from_bytes(&vm_bytes).unwrap();
853 assert_eq!(decoded_vm.to_bytes(), vm_bytes);
854
855 let precompile = precompile_proof(&[]);
856 let precompile_bytes = precompile.to_bytes();
857 let decoded_precompile = PrecompileProof::read_from_bytes(&precompile_bytes).unwrap();
858 assert_eq!(decoded_precompile.to_bytes(), precompile_bytes);
859
860 let (precompile_wire, wire_root) = wire();
861 let proofs = [
862 versioned(vm_proof(wire_root), PrecompileStatus::Deferred(precompile_wire)),
863 versioned(vm_proof(TRUE_DIGEST), PrecompileStatus::Empty),
864 versioned(
865 vm_proof(TRUE_DIGEST),
866 PrecompileStatus::Proven(precompile_proof(&[TRUE_DIGEST])),
867 ),
868 ];
869 for proof in proofs {
870 let bytes = proof.to_bytes();
871 assert_eq!(ExecutionProof::read_from_bytes(&bytes).unwrap(), proof);
872 }
873 }
874
875 #[test]
876 fn complete_transitions_deferred_proof_without_validating_artifact_shape() {
877 let vm = vm_proof(TRUE_DIGEST);
878 let precompile = precompile_proof(&[]);
879 let deferred = versioned(vm.clone(), PrecompileStatus::Deferred(wire().0));
880
881 let completed = deferred.complete(precompile.clone()).unwrap();
882
883 let (completed_compatibility, completed_vm, completed_precompile) = completed.into_parts();
884 let PrecompileStatus::Proven(completed_precompile) = completed_precompile else {
885 panic!("deferred proof should transition to complete")
886 };
887 assert_eq!(
888 completed_compatibility,
889 ExecutionProofCompatibility::new(vec![root(11), root(12)], vec![root(21)]).unwrap()
890 );
891 assert_eq!(completed_vm.to_bytes(), vm.to_bytes());
892 assert_eq!(completed_precompile.to_bytes(), precompile.to_bytes());
893 }
894
895 #[test]
896 fn complete_rejects_an_already_complete_proof() {
897 let complete = versioned(vm_proof(TRUE_DIGEST), PrecompileStatus::Empty);
898
899 assert!(matches!(
900 complete.complete(precompile_proof(&[])),
901 Err(ExecutionProofError::AlreadyComplete)
902 ));
903 }
904
905 #[test]
906 fn versioned_proof_transport_rejects_bad_discriminants_and_stark_bounds() {
907 let mut bad_discriminant = version_prefix();
908 bad_discriminant.write_u8(9);
909 assert!(ExecutionProof::read_from_bytes(&bad_discriminant).is_err());
910
911 let canonical = versioned(vm_proof(TRUE_DIGEST), PrecompileStatus::Empty).to_bytes();
912 let prefix_len = version_prefix().len();
913 assert_eq!(canonical[prefix_len + 1], 3, "one STARK byte uses a one-byte vint encoding");
914 let mut noncanonical = canonical[..prefix_len + 1].to_vec();
915 noncanonical.push(0);
916 noncanonical.extend_from_slice(&1u64.to_le_bytes());
917 noncanonical.extend_from_slice(&canonical[prefix_len + 2..]);
918 let error = ExecutionProof::read_from_bytes(&noncanonical).unwrap_err();
919 assert!(
920 matches!(error, DeserializationError::InvalidValue(message) if message.contains("not canonically encoded"))
921 );
922
923 let mut oversized_proof = version_prefix();
924 oversized_proof.write_u8(COMPLETE_PROOF_DISCRIMINANT);
925 oversized_proof.write_usize(MAX_STARK_PROOF_BYTES + 1);
926 let error = ExecutionProof::read_from_bytes(&oversized_proof).unwrap_err();
927 assert!(
928 matches!(error, DeserializationError::InvalidValue(message) if message.contains("STARK proof contains too many bytes"))
929 );
930 }
931}