Skip to main content

miden_core/
proof.rs

1use alloc::{
2    string::{String, ToString},
3    vec::Vec,
4};
5
6#[cfg(feature = "arbitrary")]
7use proptest::prelude::*;
8
9use crate::{
10    crypto::hash::{Blake3_256, Poseidon2, Rpo256, Rpx256},
11    deferred::{DeferredRoot, DeferredStateWire, MAX_PRECOMPILE_ROOTS},
12    serde::{
13        BudgetedReader, ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable,
14        SliceReader,
15    },
16};
17
18// HASH FUNCTION
19// ================================================================================================
20
21/// A hash function used during STARK proof generation.
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
23#[cfg_attr(
24    all(feature = "arbitrary", test),
25    miden_test_serialization_macros::serialization_test
26)]
27#[repr(u8)]
28pub enum HashFunction {
29    /// BLAKE3 hash function with 256-bit output.
30    Blake3_256 = 0x01,
31    /// RPO hash function with 256-bit output.
32    Rpo256 = 0x02,
33    /// RPX hash function with 256-bit output.
34    Rpx256 = 0x03,
35    /// Poseidon2 hash function with 256-bit output.
36    Poseidon2 = 0x04,
37    /// Keccak hash function with 256-bit output.
38    Keccak = 0x05,
39}
40
41impl HashFunction {
42    /// Returns the collision resistance level (in bits) of this hash function.
43    pub const fn collision_resistance(&self) -> u32 {
44        match self {
45            HashFunction::Blake3_256 => Blake3_256::COLLISION_RESISTANCE,
46            HashFunction::Rpo256 => Rpo256::COLLISION_RESISTANCE,
47            HashFunction::Rpx256 => Rpx256::COLLISION_RESISTANCE,
48            HashFunction::Poseidon2 => Poseidon2::COLLISION_RESISTANCE,
49            HashFunction::Keccak => 128,
50        }
51    }
52}
53
54/// Error type for invalid hash function strings.
55#[derive(Debug, thiserror::Error)]
56#[error(
57    "invalid hash function '{hash_function}'. Valid options are: blake3-256, rpo, rpx, poseidon2, keccak"
58)]
59pub struct InvalidHashFunctionError {
60    pub hash_function: String,
61}
62
63impl TryFrom<u8> for HashFunction {
64    type Error = DeserializationError;
65
66    fn try_from(repr: u8) -> Result<Self, Self::Error> {
67        match repr {
68            0x01 => Ok(Self::Blake3_256),
69            0x02 => Ok(Self::Rpo256),
70            0x03 => Ok(Self::Rpx256),
71            0x04 => Ok(Self::Poseidon2),
72            0x05 => Ok(Self::Keccak),
73            _ => Err(DeserializationError::InvalidValue(format!(
74                "the hash function representation {repr} is not valid!"
75            ))),
76        }
77    }
78}
79
80impl TryFrom<&str> for HashFunction {
81    type Error = InvalidHashFunctionError;
82
83    fn try_from(hash_fn_str: &str) -> Result<Self, Self::Error> {
84        match hash_fn_str {
85            "blake3-256" => Ok(Self::Blake3_256),
86            "rpo" => Ok(Self::Rpo256),
87            "rpx" => Ok(Self::Rpx256),
88            "poseidon2" => Ok(Self::Poseidon2),
89            "keccak" => Ok(Self::Keccak),
90            _ => Err(InvalidHashFunctionError { hash_function: hash_fn_str.to_string() }),
91        }
92    }
93}
94
95#[cfg(feature = "arbitrary")]
96impl Arbitrary for HashFunction {
97    type Parameters = ();
98    type Strategy = BoxedStrategy<Self>;
99
100    fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
101        any::<u8>()
102            .prop_map(|tag| match tag % 5 {
103                0 => Self::Blake3_256,
104                1 => Self::Rpo256,
105                2 => Self::Rpx256,
106                3 => Self::Poseidon2,
107                _ => Self::Keccak,
108            })
109            .boxed()
110    }
111}
112
113impl Serializable for HashFunction {
114    fn write_into<W: ByteWriter>(&self, target: &mut W) {
115        target.write_u8(*self as u8);
116    }
117}
118
119impl Deserializable for HashFunction {
120    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
121        source.read_u8()?.try_into()
122    }
123}
124
125// PROOF ARTIFACTS
126// ================================================================================================
127
128/// Hard encoded-size and per-allocation safety ceiling for every STARK proof.
129pub const MAX_STARK_PROOF_BYTES: usize = 64 * 1024 * 1024;
130
131/// A Miden VM STARK proof together with its authenticated precompile obligation.
132#[derive(Debug, Clone, PartialEq, Eq)]
133pub struct VmProof {
134    pub proof: StarkProof,
135    pub precompile_root: DeferredRoot,
136}
137
138impl Serializable for VmProof {
139    fn write_into<W: ByteWriter>(&self, target: &mut W) {
140        self.proof.write_into(target);
141        self.precompile_root.write_into(target);
142    }
143}
144
145impl Deserializable for VmProof {
146    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
147        let proof = StarkProof::read_from(source)?;
148        let precompile_root = DeferredRoot::read_from(source)?;
149        Ok(Self { proof, precompile_root })
150    }
151
152    fn read_from_bytes(bytes: &[u8]) -> Result<Self, DeserializationError> {
153        let mut reader = BudgetedReader::new(SliceReader::new(bytes), bytes.len());
154        let proof = Self::read_from(&mut reader)?;
155        if reader.has_more_bytes() {
156            return Err(DeserializationError::InvalidValue(
157                "extra bytes after VM proof payload".into(),
158            ));
159        }
160        Ok(proof)
161    }
162
163    fn min_serialized_size() -> usize {
164        StarkProof::min_serialized_size() + DeferredRoot::min_serialized_size()
165    }
166}
167
168/// A precompile STARK proof with its ordered constituent roots.
169///
170/// Binary decoding enforces the fixed root ceiling before reserving root storage, but otherwise
171/// preserves the encoded artifact shape.
172#[derive(Debug, Clone, PartialEq, Eq)]
173pub struct PrecompileProof {
174    pub proof: StarkProof,
175    pub roots: Vec<DeferredRoot>,
176}
177
178impl Serializable for PrecompileProof {
179    fn write_into<W: ByteWriter>(&self, target: &mut W) {
180        self.proof.write_into(target);
181        self.roots.write_into(target);
182    }
183}
184
185impl Deserializable for PrecompileProof {
186    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
187        let proof = StarkProof::read_from(source)?;
188        let root_count = source.read_usize()?;
189        if root_count > MAX_PRECOMPILE_ROOTS {
190            return Err(DeserializationError::InvalidValue(format!(
191                "precompile proof contains too many roots: found {root_count}, maximum is {MAX_PRECOMPILE_ROOTS}"
192            )));
193        }
194        let roots = source
195            .read_many_iter::<DeferredRoot>(root_count)?
196            .collect::<Result<Vec<_>, _>>()?;
197        Ok(Self { proof, roots })
198    }
199
200    fn read_from_bytes(bytes: &[u8]) -> Result<Self, DeserializationError> {
201        let mut reader = BudgetedReader::new(SliceReader::new(bytes), bytes.len());
202        let proof = Self::read_from(&mut reader)?;
203        if reader.has_more_bytes() {
204            return Err(DeserializationError::InvalidValue(
205                "extra bytes after precompile proof payload".into(),
206            ));
207        }
208        Ok(proof)
209    }
210
211    fn min_serialized_size() -> usize {
212        StarkProof::min_serialized_size() + usize::min_serialized_size()
213    }
214}
215
216const DEFERRED_PROOF_DISCRIMINANT: u8 = 0;
217const COMPLETE_PROOF_DISCRIMINANT: u8 = 1;
218
219/// A Miden VM execution proof, either awaiting precompile proving or complete.
220///
221/// This type preserves proof artifacts without establishing their validity.
222#[derive(Debug, Clone, PartialEq, Eq)]
223pub enum ExecutionProof {
224    /// The VM STARK and deferred precompile wire are available.
225    Deferred {
226        vm: VmProof,
227        precompile: DeferredStateWire,
228    },
229    /// The proof lifecycle is complete, with an optional precompile proof.
230    Complete {
231        vm: VmProof,
232        precompile: Option<PrecompileProof>,
233    },
234}
235
236impl ExecutionProof {
237    /// Returns whether this proof has completed its lifecycle transition.
238    pub const fn is_complete(&self) -> bool {
239        matches!(self, Self::Complete { .. })
240    }
241
242    /// Transitions a deferred proof to complete by attaching a precompile proof.
243    pub fn complete(self, precompile: PrecompileProof) -> Result<Self, ExecutionProofError> {
244        let Self::Deferred { vm, .. } = self else {
245            return Err(ExecutionProofError::AlreadyComplete);
246        };
247        Ok(Self::Complete { vm, precompile: Some(precompile) })
248    }
249
250    /// Encodes either state canonically.
251    ///
252    /// Encoding preserves the public enum representation and does not establish proof validity.
253    pub fn to_bytes(&self) -> Vec<u8> {
254        Serializable::to_bytes(self)
255    }
256
257    /// Decodes an execution proof without hydrating passive deferred wire.
258    ///
259    /// Decoding establishes bounded canonical transport syntax, not proof validity.
260    pub fn read_from_bytes(bytes: &[u8]) -> Result<Self, DeserializationError> {
261        let mut reader = BudgetedReader::new(SliceReader::new(bytes), bytes.len());
262        let proof = <Self as Deserializable>::read_from(&mut reader)?;
263
264        if reader.has_more_bytes() {
265            return Err(DeserializationError::InvalidValue(
266                "extra bytes after execution proof payload".into(),
267            ));
268        }
269        if proof.to_bytes() != bytes {
270            return Err(DeserializationError::InvalidValue(
271                "execution proof bytes are not canonically encoded".into(),
272            ));
273        }
274
275        Ok(proof)
276    }
277}
278
279impl Serializable for ExecutionProof {
280    fn write_into<W: ByteWriter>(&self, target: &mut W) {
281        match self {
282            Self::Deferred { vm, precompile } => {
283                target.write_u8(DEFERRED_PROOF_DISCRIMINANT);
284                vm.write_into(target);
285                precompile.write_into(target);
286            },
287            Self::Complete { vm, precompile } => {
288                target.write_u8(COMPLETE_PROOF_DISCRIMINANT);
289                vm.write_into(target);
290                precompile.write_into(target);
291            },
292        }
293    }
294}
295
296impl Deserializable for ExecutionProof {
297    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
298        let discriminant = source.read_u8()?;
299        if !matches!(discriminant, DEFERRED_PROOF_DISCRIMINANT | COMPLETE_PROOF_DISCRIMINANT) {
300            return Err(DeserializationError::InvalidValue(format!(
301                "invalid execution proof discriminant {discriminant}"
302            )));
303        }
304
305        let vm = VmProof::read_from(source)?;
306        match discriminant {
307            DEFERRED_PROOF_DISCRIMINANT => {
308                let precompile = DeferredStateWire::read_from(source)?;
309                Ok(Self::Deferred { vm, precompile })
310            },
311            COMPLETE_PROOF_DISCRIMINANT => {
312                let precompile = Option::<PrecompileProof>::read_from(source)?;
313                Ok(Self::Complete { vm, precompile })
314            },
315            _ => unreachable!("execution proof discriminant was checked before decoding"),
316        }
317    }
318
319    fn min_serialized_size() -> usize {
320        u8::min_serialized_size()
321            + VmProof::min_serialized_size()
322            + Option::<PrecompileProof>::min_serialized_size()
323    }
324}
325
326/// Lifecycle errors returned while transitioning an execution proof.
327#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
328pub enum ExecutionProofError {
329    /// Only a deferred execution proof can be completed.
330    #[error("the execution proof is already complete")]
331    AlreadyComplete,
332}
333
334// STARK PROOF
335// ================================================================================================
336
337/// A serialized STARK proof and the hash function used during proof generation.
338#[derive(Debug, Clone, PartialEq, Eq)]
339pub struct StarkProof {
340    bytes: Vec<u8>,
341    hash_fn: HashFunction,
342}
343
344impl StarkProof {
345    /// Creates a new instance of [StarkProof] from proof bytes and hash function.
346    pub const fn new(bytes: Vec<u8>, hash_fn: HashFunction) -> Self {
347        Self { bytes, hash_fn }
348    }
349
350    /// Returns the serialized STARK proof bytes.
351    pub fn bytes(&self) -> &[u8] {
352        &self.bytes
353    }
354
355    /// Returns the hash function used during proof generation process.
356    pub const fn hash_fn(&self) -> HashFunction {
357        self.hash_fn
358    }
359
360    /// Returns the serialized STARK proof bytes and hash function.
361    pub fn into_parts(self) -> (Vec<u8>, HashFunction) {
362        (self.bytes, self.hash_fn)
363    }
364}
365
366impl Serializable for StarkProof {
367    fn write_into<W: ByteWriter>(&self, target: &mut W) {
368        self.bytes.write_into(target);
369        self.hash_fn.write_into(target);
370    }
371}
372
373impl Deserializable for StarkProof {
374    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
375        let byte_count = source.read_usize()?;
376        if byte_count > MAX_STARK_PROOF_BYTES {
377            return Err(DeserializationError::InvalidValue(format!(
378                "STARK proof contains too many bytes: found {byte_count}, maximum is {MAX_STARK_PROOF_BYTES}"
379            )));
380        }
381        let bytes = source.read_many_iter::<u8>(byte_count)?.collect::<Result<Vec<_>, _>>()?;
382        let hash_fn = HashFunction::read_from(source)?;
383        Ok(Self::new(bytes, hash_fn))
384    }
385
386    fn read_from_bytes(bytes: &[u8]) -> Result<Self, DeserializationError> {
387        let mut reader = BudgetedReader::new(SliceReader::new(bytes), bytes.len());
388        Self::read_from(&mut reader)
389    }
390
391    fn min_serialized_size() -> usize {
392        Vec::<u8>::min_serialized_size() + HashFunction::min_serialized_size()
393    }
394}
395
396#[cfg(test)]
397mod tests {
398    use super::*;
399    use crate::{
400        Felt,
401        deferred::{DeferredState, Node, PrecompileWitness, TRUE_DIGEST},
402        serde::ByteWriter,
403    };
404
405    fn dummy_stark_proof(bytes: &[u8]) -> StarkProof {
406        StarkProof::new(bytes.to_vec(), HashFunction::Blake3_256)
407    }
408
409    fn root(value: u64) -> DeferredRoot {
410        [Felt::new(value).unwrap(), Felt::ZERO, Felt::ZERO, Felt::ZERO].into()
411    }
412
413    fn vm_proof(precompile_root: DeferredRoot) -> VmProof {
414        VmProof {
415            proof: dummy_stark_proof(&[1]),
416            precompile_root,
417        }
418    }
419
420    fn precompile_proof(roots: &[DeferredRoot]) -> PrecompileProof {
421        PrecompileProof {
422            proof: dummy_stark_proof(&[2]),
423            roots: roots.to_vec(),
424        }
425    }
426
427    fn wire() -> (DeferredStateWire, DeferredRoot) {
428        let mut state = DeferredState::default();
429        let statement = state.register(Node::and(TRUE_DIGEST, TRUE_DIGEST)).unwrap();
430        state.log_statement(statement).unwrap();
431        let witness = PrecompileWitness::new(state).unwrap();
432        (witness.state().to_wire().unwrap(), witness.roots()[0])
433    }
434
435    fn round_trip_execution_proof(proof: &ExecutionProof) {
436        let bytes = proof.to_bytes();
437        let decoded = ExecutionProof::read_from_bytes(&bytes).unwrap();
438        assert_eq!(&decoded, proof);
439    }
440
441    #[test]
442    fn execution_proof_repository_traits_decode_one_stream_item() {
443        let (precompile_wire, wire_root) = wire();
444        let deferred = ExecutionProof::Deferred {
445            vm: vm_proof(wire_root),
446            precompile: precompile_wire,
447        };
448        let complete = ExecutionProof::Complete {
449            vm: vm_proof(TRUE_DIGEST),
450            precompile: Some(precompile_proof(&[root(1)])),
451        };
452        let mut stream = deferred.to_bytes();
453        complete.write_into(&mut stream);
454        let mut reader = SliceReader::new(&stream);
455
456        assert_eq!(ExecutionProof::read_from(&mut reader).unwrap(), deferred);
457        assert_eq!(ExecutionProof::read_from(&mut reader).unwrap(), complete);
458        assert!(!reader.has_more_bytes());
459    }
460
461    #[test]
462    fn execution_proof_containers_round_trip_representable_shapes_with_exact_budget() {
463        let complete_without_precompile = ExecutionProof::Complete {
464            vm: vm_proof(TRUE_DIGEST),
465            precompile: None,
466        };
467        let smallest =
468            alloc::vec![complete_without_precompile.clone(), complete_without_precompile.clone(),];
469        let smallest_bytes = smallest.to_bytes();
470        assert_eq!(smallest_bytes.len(), 75);
471        assert_eq!(ExecutionProof::min_serialized_size(), 36);
472        assert_eq!(
473            Vec::<ExecutionProof>::read_from_bytes_with_budget(
474                &smallest_bytes,
475                smallest_bytes.len()
476            )
477            .unwrap(),
478            smallest
479        );
480
481        let (precompile_wire, wire_root) = wire();
482        let malformed_shapes = alloc::vec![
483            ExecutionProof::Deferred {
484                vm: vm_proof(wire_root),
485                precompile: precompile_wire,
486            },
487            complete_without_precompile,
488            ExecutionProof::Complete {
489                vm: vm_proof(root(9)),
490                precompile: Some(precompile_proof(&[])),
491            },
492            ExecutionProof::Complete {
493                vm: vm_proof(root(9)),
494                precompile: Some(precompile_proof(&[root(9), root(9)])),
495            },
496            ExecutionProof::Complete {
497                vm: vm_proof(root(9)),
498                precompile: Some(precompile_proof(&[TRUE_DIGEST])),
499            },
500        ];
501        let bytes = malformed_shapes.to_bytes();
502        let decoded =
503            Vec::<ExecutionProof>::read_from_bytes_with_budget(&bytes, bytes.len()).unwrap();
504        assert_eq!(decoded, malformed_shapes);
505
506        for wrapper in [None, Some(malformed_shapes[0].clone())] {
507            let bytes = wrapper.to_bytes();
508            let decoded =
509                Option::<ExecutionProof>::read_from_bytes_with_budget(&bytes, bytes.len()).unwrap();
510            assert_eq!(decoded, wrapper);
511        }
512    }
513
514    #[test]
515    fn proof_minimum_serialized_sizes_match_shortest_canonical_encodings() {
516        let stark = StarkProof::new(Vec::new(), HashFunction::Blake3_256);
517        assert_eq!(StarkProof::min_serialized_size(), stark.to_bytes().len());
518        assert_eq!(StarkProof::min_serialized_size(), 2);
519
520        let vm = VmProof {
521            proof: stark,
522            precompile_root: TRUE_DIGEST,
523        };
524        assert_eq!(VmProof::min_serialized_size(), vm.to_bytes().len());
525        assert_eq!(VmProof::min_serialized_size(), 34);
526
527        let empty = PrecompileProof {
528            proof: StarkProof::new(Vec::new(), HashFunction::Blake3_256),
529            roots: Vec::new(),
530        };
531        assert_eq!(PrecompileProof::min_serialized_size(), empty.to_bytes().len());
532        assert_eq!(PrecompileProof::min_serialized_size(), 3);
533
534        let singleton = PrecompileProof {
535            proof: StarkProof::new(Vec::new(), HashFunction::Blake3_256),
536            roots: alloc::vec![root(1)],
537        };
538        assert_eq!(singleton.to_bytes().len(), 35);
539
540        let proofs = alloc::vec![singleton.clone(), singleton];
541        let bytes = proofs.to_bytes();
542        assert_eq!(bytes.len(), 71);
543        let decoded = Vec::<PrecompileProof>::read_from_bytes_with_budget(&bytes, 71).unwrap();
544        assert_eq!(decoded.to_bytes(), bytes);
545    }
546
547    #[test]
548    fn stark_proof_decoder_rejects_oversized_length_before_payload() {
549        let mut bytes = Vec::new();
550        bytes.write_usize(MAX_STARK_PROOF_BYTES + 1);
551
552        let error = StarkProof::read_from_bytes(&bytes).unwrap_err();
553        let DeserializationError::InvalidValue(message) = error else {
554            panic!("expected excessive STARK proof length to be rejected")
555        };
556        assert!(message.contains("STARK proof contains too many bytes"));
557    }
558
559    #[test]
560    fn precompile_proof_decoder_rejects_oversized_root_count_before_payload() {
561        let mut bytes = dummy_stark_proof(&[2]).to_bytes();
562        bytes.write_usize(MAX_PRECOMPILE_ROOTS + 1);
563
564        let error = PrecompileProof::read_from_bytes(&bytes).unwrap_err();
565        let DeserializationError::InvalidValue(message) = error else {
566            panic!("expected excessive root count to be rejected")
567        };
568        assert!(message.contains("precompile proof contains too many roots"));
569    }
570
571    #[test]
572    fn standalone_proof_decoders_reject_trailing_bytes() {
573        let mut vm_bytes = vm_proof(root(3)).to_bytes();
574        vm_bytes.push(0);
575        assert!(VmProof::read_from_bytes(&vm_bytes).is_err());
576
577        let mut precompile_bytes = precompile_proof(&[root(3)]).to_bytes();
578        precompile_bytes.push(0);
579        assert!(PrecompileProof::read_from_bytes(&precompile_bytes).is_err());
580    }
581
582    #[test]
583    fn proof_artifacts_round_trip_canonically() {
584        let stark = dummy_stark_proof(&[1, 2, 3]);
585        let stark_bytes = stark.to_bytes();
586        let decoded_stark = StarkProof::read_from_bytes(&stark_bytes).unwrap();
587        assert_eq!(decoded_stark.to_bytes(), stark_bytes);
588
589        let vm = vm_proof(root(3));
590        let vm_bytes = vm.to_bytes();
591        let decoded_vm = VmProof::read_from_bytes(&vm_bytes).unwrap();
592        assert_eq!(decoded_vm.to_bytes(), vm_bytes);
593
594        let precompile = precompile_proof(&[]);
595        let precompile_bytes = precompile.to_bytes();
596        let decoded_precompile = PrecompileProof::read_from_bytes(&precompile_bytes).unwrap();
597        assert_eq!(decoded_precompile.to_bytes(), precompile_bytes);
598
599        let (precompile_wire, wire_root) = wire();
600        let proofs = [
601            ExecutionProof::Deferred {
602                vm: vm_proof(wire_root),
603                precompile: precompile_wire,
604            },
605            ExecutionProof::Complete {
606                vm: vm_proof(TRUE_DIGEST),
607                precompile: None,
608            },
609            ExecutionProof::Complete {
610                vm: vm_proof(TRUE_DIGEST),
611                precompile: Some(precompile_proof(&[TRUE_DIGEST])),
612            },
613        ];
614        for proof in &proofs {
615            round_trip_execution_proof(proof);
616        }
617    }
618
619    #[test]
620    fn complete_transitions_deferred_proof_without_validating_artifact_shape() {
621        let vm = vm_proof(TRUE_DIGEST);
622        let precompile = precompile_proof(&[]);
623        let deferred = ExecutionProof::Deferred {
624            vm: vm.clone(),
625            precompile: DeferredStateWire::default(),
626        };
627
628        let completed = deferred.complete(precompile.clone()).unwrap();
629
630        let ExecutionProof::Complete {
631            vm: completed_vm,
632            precompile: Some(completed_precompile),
633        } = completed
634        else {
635            panic!("deferred proof should transition to complete")
636        };
637        assert_eq!(completed_vm.to_bytes(), vm.to_bytes());
638        assert_eq!(completed_precompile.to_bytes(), precompile.to_bytes());
639    }
640
641    #[test]
642    fn complete_rejects_an_already_complete_proof() {
643        let complete = ExecutionProof::Complete {
644            vm: vm_proof(TRUE_DIGEST),
645            precompile: None,
646        };
647
648        assert!(matches!(
649            complete.complete(precompile_proof(&[])),
650            Err(ExecutionProofError::AlreadyComplete)
651        ));
652    }
653
654    #[test]
655    fn execution_proof_transport_rejects_bad_discriminants_trailing_bytes_and_bounds() {
656        assert!(ExecutionProof::read_from_bytes(&[9]).is_err());
657
658        let mut trailing = ExecutionProof::Complete {
659            vm: vm_proof(TRUE_DIGEST),
660            precompile: None,
661        }
662        .to_bytes();
663        trailing.push(0);
664        assert!(ExecutionProof::read_from_bytes(&trailing).is_err());
665
666        let canonical = ExecutionProof::Complete {
667            vm: vm_proof(TRUE_DIGEST),
668            precompile: None,
669        }
670        .to_bytes();
671        assert_eq!(canonical[1], 3, "one STARK byte uses a one-byte vint encoding");
672        let mut noncanonical = alloc::vec![canonical[0], 0];
673        noncanonical.extend_from_slice(&1u64.to_le_bytes());
674        noncanonical.extend_from_slice(&canonical[2..]);
675        let error = ExecutionProof::read_from_bytes(&noncanonical).unwrap_err();
676        assert!(
677            matches!(error, DeserializationError::InvalidValue(message) if message.contains("not canonically encoded"))
678        );
679
680        let mut oversized_proof = Vec::new();
681        oversized_proof.write_u8(COMPLETE_PROOF_DISCRIMINANT);
682        oversized_proof.write_usize(MAX_STARK_PROOF_BYTES + 1);
683        let error = ExecutionProof::read_from_bytes(&oversized_proof).unwrap_err();
684        assert!(
685            matches!(error, DeserializationError::InvalidValue(message) if message.contains("STARK proof contains too many bytes"))
686        );
687    }
688}