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#[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_256 = 0x01,
31 Rpo256 = 0x02,
33 Rpx256 = 0x03,
35 Poseidon2 = 0x04,
37 Keccak = 0x05,
39}
40
41impl HashFunction {
42 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#[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
125pub const MAX_STARK_PROOF_BYTES: usize = 64 * 1024 * 1024;
130
131#[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#[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#[derive(Debug, Clone, PartialEq, Eq)]
223pub enum ExecutionProof {
224 Deferred {
226 vm: VmProof,
227 precompile: DeferredStateWire,
228 },
229 Complete {
231 vm: VmProof,
232 precompile: Option<PrecompileProof>,
233 },
234}
235
236impl ExecutionProof {
237 pub const fn is_complete(&self) -> bool {
239 matches!(self, Self::Complete { .. })
240 }
241
242 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 pub fn to_bytes(&self) -> Vec<u8> {
254 Serializable::to_bytes(self)
255 }
256
257 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#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
328pub enum ExecutionProofError {
329 #[error("the execution proof is already complete")]
331 AlreadyComplete,
332}
333
334#[derive(Debug, Clone, PartialEq, Eq)]
339pub struct StarkProof {
340 bytes: Vec<u8>,
341 hash_fn: HashFunction,
342}
343
344impl StarkProof {
345 pub const fn new(bytes: Vec<u8>, hash_fn: HashFunction) -> Self {
347 Self { bytes, hash_fn }
348 }
349
350 pub fn bytes(&self) -> &[u8] {
352 &self.bytes
353 }
354
355 pub const fn hash_fn(&self) -> HashFunction {
357 self.hash_fn
358 }
359
360 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}