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, DeferredStateWire, MAX_PRECOMPILE_ROOTS},
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(3472736072004736895),
32 crate::Felt::new_unchecked(9258997376938263475),
33 crate::Felt::new_unchecked(2749074330562194466),
34 crate::Felt::new_unchecked(16770332914073895013),
35]);
36pub const CURRENT_PVM_VERIFIER_ROOT: Word = Word::new([
38 crate::Felt::new_unchecked(9567976034529193007),
39 crate::Felt::new_unchecked(16098679426107462596),
40 crate::Felt::new_unchecked(270852360285345822),
41 crate::Felt::new_unchecked(12812280715059172950),
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 Serializable for PrecompileProof {
202 fn write_into<W: ByteWriter>(&self, target: &mut W) {
203 self.proof.write_into(target);
204 self.roots.write_into(target);
205 }
206}
207
208impl Deserializable for PrecompileProof {
209 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
210 let proof = StarkProof::read_from(source)?;
211 let root_count = source.read_usize()?;
212 if root_count > MAX_PRECOMPILE_ROOTS {
213 return Err(DeserializationError::InvalidValue(format!(
214 "precompile proof contains too many roots: found {root_count}, maximum is {MAX_PRECOMPILE_ROOTS}"
215 )));
216 }
217 let roots = source
218 .read_many_iter::<DeferredRoot>(root_count)?
219 .collect::<Result<Vec<_>, _>>()?;
220 Ok(Self { proof, roots })
221 }
222
223 fn read_from_bytes(bytes: &[u8]) -> Result<Self, DeserializationError> {
224 let mut reader = BudgetedReader::new(SliceReader::new(bytes), bytes.len());
225 let proof = Self::read_from(&mut reader)?;
226 if reader.has_more_bytes() {
227 return Err(DeserializationError::InvalidValue(
228 "extra bytes after precompile proof payload".into(),
229 ));
230 }
231 Ok(proof)
232 }
233
234 fn min_serialized_size() -> usize {
235 StarkProof::min_serialized_size() + usize::min_serialized_size()
236 }
237}
238
239#[derive(Debug, Clone, PartialEq, Eq)]
244pub struct ExecutionProofCompatibility {
245 format: u8,
246 vm_verifier_roots: Vec<Word>,
247 pvm_verifier_roots: Vec<Word>,
248}
249
250impl ExecutionProofCompatibility {
251 pub const FORMAT_V1: u8 = 1;
253
254 pub fn new(
260 vm_verifier_roots: Vec<Word>,
261 pvm_verifier_roots: Vec<Word>,
262 ) -> Result<Self, ExecutionProofCompatibilityError> {
263 if has_duplicate(&vm_verifier_roots) {
264 return Err(ExecutionProofCompatibilityError::DuplicateVmVerifierRoot);
265 }
266 if has_duplicate(&pvm_verifier_roots) {
267 return Err(ExecutionProofCompatibilityError::DuplicatePvmVerifierRoot);
268 }
269
270 Ok(Self {
271 format: Self::FORMAT_V1,
272 vm_verifier_roots,
273 pvm_verifier_roots,
274 })
275 }
276
277 pub fn current() -> Self {
279 Self::new(alloc::vec![CURRENT_VM_VERIFIER_ROOT], alloc::vec![CURRENT_PVM_VERIFIER_ROOT])
280 .expect("current execution proof compatibility must not contain duplicate roots")
281 }
282
283 pub const fn format(&self) -> u8 {
285 self.format
286 }
287
288 pub fn vm_verifier_roots(&self) -> &[Word] {
293 &self.vm_verifier_roots
294 }
295
296 pub fn pvm_verifier_roots(&self) -> &[Word] {
302 &self.pvm_verifier_roots
303 }
304}
305
306#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
308pub enum ExecutionProofCompatibilityError {
309 #[error("VM verifier roots must not contain duplicates")]
311 DuplicateVmVerifierRoot,
312 #[error("PVM verifier roots must not contain duplicates")]
314 DuplicatePvmVerifierRoot,
315}
316
317#[derive(Debug, Clone, PartialEq, Eq)]
319pub enum PrecompileStatus {
320 Empty,
322 Deferred(DeferredStateWire),
324 Proven(PrecompileProof),
326}
327
328#[derive(Debug, Clone, PartialEq, Eq)]
332pub struct ExecutionProof {
333 compatibility: ExecutionProofCompatibility,
334 vm: VmProof,
335 precompile: PrecompileStatus,
336}
337
338impl ExecutionProof {
339 pub fn new(vm: VmProof, precompile: PrecompileStatus) -> Self {
341 Self::from_parts(ExecutionProofCompatibility::current(), vm, precompile)
342 }
343
344 pub const fn from_parts(
346 compatibility: ExecutionProofCompatibility,
347 vm: VmProof,
348 precompile: PrecompileStatus,
349 ) -> Self {
350 Self { compatibility, vm, precompile }
351 }
352
353 pub const fn compatibility(&self) -> &ExecutionProofCompatibility {
355 &self.compatibility
356 }
357
358 pub const fn vm(&self) -> &VmProof {
360 &self.vm
361 }
362
363 pub const fn precompile(&self) -> &PrecompileStatus {
365 &self.precompile
366 }
367
368 pub fn into_parts(self) -> (ExecutionProofCompatibility, VmProof, PrecompileStatus) {
370 (self.compatibility, self.vm, self.precompile)
371 }
372
373 pub const fn is_complete(&self) -> bool {
375 !matches!(self.precompile, PrecompileStatus::Deferred(_))
376 }
377
378 pub const fn has_precompiles(&self) -> bool {
380 !matches!(self.precompile, PrecompileStatus::Empty)
381 }
382
383 pub fn complete(mut self, precompile: PrecompileProof) -> Result<Self, ExecutionProofError> {
388 if !matches!(self.precompile, PrecompileStatus::Deferred(_)) {
389 return Err(ExecutionProofError::AlreadyComplete);
390 }
391 self.precompile = PrecompileStatus::Proven(precompile);
392 Ok(self)
393 }
394
395 pub fn to_bytes(&self) -> Vec<u8> {
397 Serializable::to_bytes(self)
398 }
399
400 pub fn read_from_bytes(bytes: &[u8]) -> Result<Self, DeserializationError> {
402 let mut reader = BudgetedReader::new(SliceReader::new(bytes), bytes.len());
403 let proof = <Self as Deserializable>::read_from(&mut reader)?;
404
405 if reader.has_more_bytes() {
406 return Err(DeserializationError::InvalidValue(
407 "extra bytes after versioned proof payload".into(),
408 ));
409 }
410 if proof.to_bytes() != bytes {
411 return Err(DeserializationError::InvalidValue(
412 "versioned proof bytes are not canonically encoded".into(),
413 ));
414 }
415
416 Ok(proof)
417 }
418}
419
420impl Serializable for ExecutionProof {
421 fn write_into<W: ByteWriter>(&self, target: &mut W) {
422 target.write_u8(self.compatibility.format);
423 self.compatibility.vm_verifier_roots.write_into(target);
424 self.compatibility.pvm_verifier_roots.write_into(target);
425 self.write_into_v1(target);
426 }
427}
428
429impl Deserializable for ExecutionProof {
430 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
431 let format = source.read_u8()?;
432 if format != ExecutionProofCompatibility::FORMAT_V1 {
433 return Err(DeserializationError::InvalidValue(format!(
434 "unsupported execution proof format {format}"
435 )));
436 }
437
438 let vm_verifier_roots = Vec::<Word>::read_from(source)?;
439 let pvm_verifier_roots = Vec::<Word>::read_from(source)?;
440 let compatibility = ExecutionProofCompatibility::new(vm_verifier_roots, pvm_verifier_roots)
441 .map_err(|error| DeserializationError::InvalidValue(error.to_string()))?;
442
443 Self::read_from_v1(source, compatibility)
444 }
445
446 fn min_serialized_size() -> usize {
447 u8::min_serialized_size()
448 + Vec::<Word>::min_serialized_size()
449 + Vec::<Word>::min_serialized_size()
450 + ExecutionProof::min_serialized_size_v1()
451 }
452}
453
454impl ExecutionProof {
455 fn write_into_v1<W: ByteWriter>(&self, target: &mut W) {
456 match &self.precompile {
457 PrecompileStatus::Deferred(precompile) => {
458 target.write_u8(DEFERRED_PROOF_DISCRIMINANT);
459 self.vm.write_into(target);
460 precompile.write_into(target);
461 },
462 PrecompileStatus::Empty => {
463 target.write_u8(COMPLETE_PROOF_DISCRIMINANT);
464 self.vm.write_into(target);
465 Option::<PrecompileProof>::None.write_into(target);
466 },
467 PrecompileStatus::Proven(precompile) => {
468 target.write_u8(COMPLETE_PROOF_DISCRIMINANT);
469 self.vm.write_into(target);
470 Some(precompile).write_into(target);
471 },
472 }
473 }
474
475 fn read_from_v1<R: ByteReader>(
476 source: &mut R,
477 compatibility: ExecutionProofCompatibility,
478 ) -> Result<Self, DeserializationError> {
479 let discriminant = source.read_u8()?;
480 if !matches!(discriminant, DEFERRED_PROOF_DISCRIMINANT | COMPLETE_PROOF_DISCRIMINANT) {
481 return Err(DeserializationError::InvalidValue(format!(
482 "invalid execution proof discriminant {discriminant}"
483 )));
484 }
485
486 let vm = VmProof::read_from(source)?;
487 let precompile = match discriminant {
488 DEFERRED_PROOF_DISCRIMINANT => {
489 PrecompileStatus::Deferred(DeferredStateWire::read_from(source)?)
490 },
491 COMPLETE_PROOF_DISCRIMINANT => match Option::<PrecompileProof>::read_from(source)? {
492 Some(precompile) => PrecompileStatus::Proven(precompile),
493 None => PrecompileStatus::Empty,
494 },
495 _ => unreachable!("execution proof discriminant was checked before decoding"),
496 };
497
498 Ok(Self { compatibility, vm, precompile })
499 }
500
501 fn min_serialized_size_v1() -> usize {
502 u8::min_serialized_size()
503 + VmProof::min_serialized_size()
504 + Option::<PrecompileProof>::min_serialized_size()
505 }
506}
507
508#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
510pub enum ExecutionProofError {
511 #[error("the execution proof is already complete")]
513 AlreadyComplete,
514}
515
516#[derive(Debug, Clone, PartialEq, Eq)]
521pub struct StarkProof {
522 bytes: Vec<u8>,
523 hash_fn: HashFunction,
524}
525
526impl StarkProof {
527 pub const fn new(bytes: Vec<u8>, hash_fn: HashFunction) -> Self {
529 Self { bytes, hash_fn }
530 }
531
532 pub fn bytes(&self) -> &[u8] {
534 &self.bytes
535 }
536
537 pub const fn hash_fn(&self) -> HashFunction {
539 self.hash_fn
540 }
541
542 pub fn into_parts(self) -> (Vec<u8>, HashFunction) {
544 (self.bytes, self.hash_fn)
545 }
546}
547
548impl Serializable for StarkProof {
549 fn write_into<W: ByteWriter>(&self, target: &mut W) {
550 self.bytes.write_into(target);
551 self.hash_fn.write_into(target);
552 }
553}
554
555impl Deserializable for StarkProof {
556 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
557 let byte_count = source.read_usize()?;
558 if byte_count > MAX_STARK_PROOF_BYTES {
559 return Err(DeserializationError::InvalidValue(format!(
560 "STARK proof contains too many bytes: found {byte_count}, maximum is {MAX_STARK_PROOF_BYTES}"
561 )));
562 }
563 let bytes = source.read_many_iter::<u8>(byte_count)?.collect::<Result<Vec<_>, _>>()?;
564 let hash_fn = HashFunction::read_from(source)?;
565 Ok(Self::new(bytes, hash_fn))
566 }
567
568 fn read_from_bytes(bytes: &[u8]) -> Result<Self, DeserializationError> {
569 let mut reader = BudgetedReader::new(SliceReader::new(bytes), bytes.len());
570 Self::read_from(&mut reader)
571 }
572
573 fn min_serialized_size() -> usize {
574 Vec::<u8>::min_serialized_size() + HashFunction::min_serialized_size()
575 }
576}
577
578fn has_duplicate(roots: &[Word]) -> bool {
582 let mut unique = BTreeSet::new();
583 roots.iter().any(|root| !unique.insert(*root))
584}
585
586#[cfg(test)]
590mod tests {
591 use super::*;
592 use crate::{
593 Felt,
594 deferred::{DeferredState, Node, PrecompileWitness, TRUE_DIGEST},
595 serde::ByteWriter,
596 };
597
598 fn dummy_stark_proof(bytes: &[u8]) -> StarkProof {
599 StarkProof::new(bytes.to_vec(), HashFunction::Blake3_256)
600 }
601
602 fn root(value: u64) -> DeferredRoot {
603 [Felt::new(value).unwrap(), Felt::ZERO, Felt::ZERO, Felt::ZERO].into()
604 }
605
606 fn vm_proof(precompile_root: DeferredRoot) -> VmProof {
607 VmProof {
608 proof: dummy_stark_proof(&[1]),
609 precompile_root,
610 }
611 }
612
613 fn precompile_proof(roots: &[DeferredRoot]) -> PrecompileProof {
614 PrecompileProof {
615 proof: dummy_stark_proof(&[2]),
616 roots: roots.to_vec(),
617 }
618 }
619
620 fn wire() -> (DeferredStateWire, DeferredRoot) {
621 let mut state = DeferredState::default();
622 let statement = state.register(Node::and(TRUE_DIGEST, TRUE_DIGEST)).unwrap();
623 state.log_statement(statement).unwrap();
624 let witness = PrecompileWitness::new(state).unwrap();
625 (witness.state().to_wire().unwrap(), witness.roots()[0])
626 }
627
628 fn versioned(vm: VmProof, precompile: PrecompileStatus) -> ExecutionProof {
629 ExecutionProof::from_parts(
630 ExecutionProofCompatibility::new(vec![root(11), root(12)], vec![root(21)]).unwrap(),
631 vm,
632 precompile,
633 )
634 }
635
636 fn version_prefix() -> Vec<u8> {
637 let compatibility =
638 ExecutionProofCompatibility::new(vec![root(11), root(12)], vec![root(21)]).unwrap();
639 let mut bytes = vec![compatibility.format()];
640 compatibility.vm_verifier_roots().to_vec().write_into(&mut bytes);
641 compatibility.pvm_verifier_roots().to_vec().write_into(&mut bytes);
642 bytes
643 }
644
645 #[test]
646 fn execution_proof_reports_precompile_state() {
647 let (precompile_wire, wire_root) = wire();
648 let deferred = versioned(vm_proof(wire_root), PrecompileStatus::Deferred(precompile_wire));
649 assert!(deferred.has_precompiles());
650
651 let complete_without_precompile = versioned(vm_proof(TRUE_DIGEST), PrecompileStatus::Empty);
652 assert!(!complete_without_precompile.has_precompiles());
653
654 let complete_with_precompile =
655 versioned(vm_proof(root(1)), PrecompileStatus::Proven(precompile_proof(&[root(1)])));
656 assert!(complete_with_precompile.has_precompiles());
657 }
658
659 #[test]
660 fn versioned_proof_round_trips_with_format_first() {
661 let proof = versioned(vm_proof(TRUE_DIGEST), PrecompileStatus::Empty);
662
663 let bytes = proof.to_bytes();
664 let decoded = ExecutionProof::read_from_bytes(&bytes).unwrap();
665
666 assert_eq!(bytes[0], ExecutionProofCompatibility::FORMAT_V1);
667 assert_eq!(decoded, proof);
668 assert_eq!(decoded.compatibility().format(), ExecutionProofCompatibility::FORMAT_V1);
669 assert_eq!(decoded.compatibility().vm_verifier_roots(), &[root(11), root(12)]);
670 assert_eq!(decoded.compatibility().pvm_verifier_roots(), &[root(21)]);
671 }
672
673 #[test]
674 fn versioned_proof_decoder_rejects_unknown_format_before_body() {
675 let error = ExecutionProof::read_from_bytes(&[ExecutionProofCompatibility::FORMAT_V1 + 1])
676 .unwrap_err();
677
678 assert!(
679 matches!(error, DeserializationError::InvalidValue(message) if message.contains("unsupported execution proof format 2"))
680 );
681 }
682
683 #[test]
684 fn versioned_proof_decoder_rejects_trailing_and_noncanonical_bytes() {
685 let proof = versioned(vm_proof(TRUE_DIGEST), PrecompileStatus::Empty);
686 let mut trailing = proof.to_bytes();
687 trailing.push(0);
688 assert!(ExecutionProof::read_from_bytes(&trailing).is_err());
689
690 let canonical = proof.to_bytes();
691 assert_eq!(canonical[1], 5, "two VM roots use a one-byte vint encoding");
692 let mut noncanonical = alloc::vec![canonical[0], 0];
693 noncanonical.extend_from_slice(&2u64.to_le_bytes());
694 noncanonical.extend_from_slice(&canonical[2..]);
695 let error = ExecutionProof::read_from_bytes(&noncanonical).unwrap_err();
696 assert!(
697 matches!(error, DeserializationError::InvalidValue(message) if message.contains("not canonically encoded"))
698 );
699 }
700
701 #[test]
702 fn versioned_proof_decoder_applies_the_input_budget_to_root_lists() {
703 let mut bytes = vec![ExecutionProofCompatibility::FORMAT_V1];
704 bytes.write_usize(usize::MAX);
705
706 let error = ExecutionProof::read_from_bytes(&bytes).unwrap_err();
707 assert!(matches!(error, DeserializationError::InvalidValue(_)));
708 }
709
710 #[test]
711 fn compatibility_constructor_rejects_duplicate_roots() {
712 assert_eq!(
713 ExecutionProofCompatibility::new(vec![root(1), root(1)], vec![]),
714 Err(ExecutionProofCompatibilityError::DuplicateVmVerifierRoot)
715 );
716 assert_eq!(
717 ExecutionProofCompatibility::new(vec![], vec![root(2), root(2)]),
718 Err(ExecutionProofCompatibilityError::DuplicatePvmVerifierRoot)
719 );
720 }
721
722 #[test]
723 fn versioned_proof_decoder_rejects_duplicate_roots() {
724 let proof = versioned(vm_proof(TRUE_DIGEST), PrecompileStatus::Empty);
725 let proof_bytes = proof.to_bytes();
726 let body = &proof_bytes[version_prefix().len()..];
727
728 let mut duplicate_vm = vec![ExecutionProofCompatibility::FORMAT_V1];
729 vec![root(1), root(1)].write_into(&mut duplicate_vm);
730 Vec::<Word>::new().write_into(&mut duplicate_vm);
731 duplicate_vm.extend_from_slice(body);
732 let error = ExecutionProof::read_from_bytes(&duplicate_vm).unwrap_err();
733 assert!(
734 matches!(error, DeserializationError::InvalidValue(message) if message.contains("VM verifier roots must not contain duplicates"))
735 );
736
737 let mut duplicate_pvm = vec![ExecutionProofCompatibility::FORMAT_V1];
738 Vec::<Word>::new().write_into(&mut duplicate_pvm);
739 vec![root(2), root(2)].write_into(&mut duplicate_pvm);
740 duplicate_pvm.extend_from_slice(body);
741 let error = ExecutionProof::read_from_bytes(&duplicate_pvm).unwrap_err();
742 assert!(
743 matches!(error, DeserializationError::InvalidValue(message) if message.contains("PVM verifier roots must not contain duplicates"))
744 );
745 }
746
747 #[test]
748 fn proof_minimum_serialized_sizes_match_shortest_canonical_encodings() {
749 let stark = StarkProof::new(Vec::new(), HashFunction::Blake3_256);
750 assert_eq!(StarkProof::min_serialized_size(), stark.to_bytes().len());
751 assert_eq!(StarkProof::min_serialized_size(), 2);
752
753 let vm = VmProof {
754 proof: stark,
755 precompile_root: TRUE_DIGEST,
756 };
757 assert_eq!(VmProof::min_serialized_size(), vm.to_bytes().len());
758 assert_eq!(VmProof::min_serialized_size(), 34);
759
760 let empty = PrecompileProof {
761 proof: StarkProof::new(Vec::new(), HashFunction::Blake3_256),
762 roots: Vec::new(),
763 };
764 assert_eq!(PrecompileProof::min_serialized_size(), empty.to_bytes().len());
765 assert_eq!(PrecompileProof::min_serialized_size(), 3);
766
767 let singleton = PrecompileProof {
768 proof: StarkProof::new(Vec::new(), HashFunction::Blake3_256),
769 roots: alloc::vec![root(1)],
770 };
771 assert_eq!(singleton.to_bytes().len(), 35);
772
773 let proofs = alloc::vec![singleton.clone(), singleton];
774 let bytes = proofs.to_bytes();
775 assert_eq!(bytes.len(), 71);
776 let decoded = Vec::<PrecompileProof>::read_from_bytes_with_budget(&bytes, 71).unwrap();
777 assert_eq!(decoded.to_bytes(), bytes);
778 }
779
780 #[test]
781 fn stark_proof_decoder_rejects_oversized_length_before_payload() {
782 let mut bytes = Vec::new();
783 bytes.write_usize(MAX_STARK_PROOF_BYTES + 1);
784
785 let error = StarkProof::read_from_bytes(&bytes).unwrap_err();
786 let DeserializationError::InvalidValue(message) = error else {
787 panic!("expected excessive STARK proof length to be rejected")
788 };
789 assert!(message.contains("STARK proof contains too many bytes"));
790 }
791
792 #[test]
793 fn precompile_proof_decoder_rejects_oversized_root_count_before_payload() {
794 let mut bytes = dummy_stark_proof(&[2]).to_bytes();
795 bytes.write_usize(MAX_PRECOMPILE_ROOTS + 1);
796
797 let error = PrecompileProof::read_from_bytes(&bytes).unwrap_err();
798 let DeserializationError::InvalidValue(message) = error else {
799 panic!("expected excessive root count to be rejected")
800 };
801 assert!(message.contains("precompile proof contains too many roots"));
802 }
803
804 #[test]
805 fn standalone_proof_decoders_reject_trailing_bytes() {
806 let mut vm_bytes = vm_proof(root(3)).to_bytes();
807 vm_bytes.push(0);
808 assert!(VmProof::read_from_bytes(&vm_bytes).is_err());
809
810 let mut precompile_bytes = precompile_proof(&[root(3)]).to_bytes();
811 precompile_bytes.push(0);
812 assert!(PrecompileProof::read_from_bytes(&precompile_bytes).is_err());
813 }
814
815 #[test]
816 fn proof_artifacts_round_trip_canonically() {
817 let stark = dummy_stark_proof(&[1, 2, 3]);
818 let stark_bytes = stark.to_bytes();
819 let decoded_stark = StarkProof::read_from_bytes(&stark_bytes).unwrap();
820 assert_eq!(decoded_stark.to_bytes(), stark_bytes);
821
822 let vm = vm_proof(root(3));
823 let vm_bytes = vm.to_bytes();
824 let decoded_vm = VmProof::read_from_bytes(&vm_bytes).unwrap();
825 assert_eq!(decoded_vm.to_bytes(), vm_bytes);
826
827 let precompile = precompile_proof(&[]);
828 let precompile_bytes = precompile.to_bytes();
829 let decoded_precompile = PrecompileProof::read_from_bytes(&precompile_bytes).unwrap();
830 assert_eq!(decoded_precompile.to_bytes(), precompile_bytes);
831
832 let (precompile_wire, wire_root) = wire();
833 let proofs = [
834 versioned(vm_proof(wire_root), PrecompileStatus::Deferred(precompile_wire)),
835 versioned(vm_proof(TRUE_DIGEST), PrecompileStatus::Empty),
836 versioned(
837 vm_proof(TRUE_DIGEST),
838 PrecompileStatus::Proven(precompile_proof(&[TRUE_DIGEST])),
839 ),
840 ];
841 for proof in proofs {
842 let bytes = proof.to_bytes();
843 assert_eq!(ExecutionProof::read_from_bytes(&bytes).unwrap(), proof);
844 }
845 }
846
847 #[test]
848 fn complete_transitions_deferred_proof_without_validating_artifact_shape() {
849 let vm = vm_proof(TRUE_DIGEST);
850 let precompile = precompile_proof(&[]);
851 let deferred =
852 versioned(vm.clone(), PrecompileStatus::Deferred(DeferredStateWire::default()));
853
854 let completed = deferred.complete(precompile.clone()).unwrap();
855
856 let (completed_compatibility, completed_vm, completed_precompile) = completed.into_parts();
857 let PrecompileStatus::Proven(completed_precompile) = completed_precompile else {
858 panic!("deferred proof should transition to complete")
859 };
860 assert_eq!(
861 completed_compatibility,
862 ExecutionProofCompatibility::new(vec![root(11), root(12)], vec![root(21)]).unwrap()
863 );
864 assert_eq!(completed_vm.to_bytes(), vm.to_bytes());
865 assert_eq!(completed_precompile.to_bytes(), precompile.to_bytes());
866 }
867
868 #[test]
869 fn complete_rejects_an_already_complete_proof() {
870 let complete = versioned(vm_proof(TRUE_DIGEST), PrecompileStatus::Empty);
871
872 assert!(matches!(
873 complete.complete(precompile_proof(&[])),
874 Err(ExecutionProofError::AlreadyComplete)
875 ));
876 }
877
878 #[test]
879 fn versioned_proof_transport_rejects_bad_discriminants_and_stark_bounds() {
880 let mut bad_discriminant = version_prefix();
881 bad_discriminant.write_u8(9);
882 assert!(ExecutionProof::read_from_bytes(&bad_discriminant).is_err());
883
884 let canonical = versioned(vm_proof(TRUE_DIGEST), PrecompileStatus::Empty).to_bytes();
885 let prefix_len = version_prefix().len();
886 assert_eq!(canonical[prefix_len + 1], 3, "one STARK byte uses a one-byte vint encoding");
887 let mut noncanonical = canonical[..prefix_len + 1].to_vec();
888 noncanonical.push(0);
889 noncanonical.extend_from_slice(&1u64.to_le_bytes());
890 noncanonical.extend_from_slice(&canonical[prefix_len + 2..]);
891 let error = ExecutionProof::read_from_bytes(&noncanonical).unwrap_err();
892 assert!(
893 matches!(error, DeserializationError::InvalidValue(message) if message.contains("not canonically encoded"))
894 );
895
896 let mut oversized_proof = version_prefix();
897 oversized_proof.write_u8(COMPLETE_PROOF_DISCRIMINANT);
898 oversized_proof.write_usize(MAX_STARK_PROOF_BYTES + 1);
899 let error = ExecutionProof::read_from_bytes(&oversized_proof).unwrap_err();
900 assert!(
901 matches!(error, DeserializationError::InvalidValue(message) if message.contains("STARK proof contains too many bytes"))
902 );
903 }
904}