1use alloc::{collections::VecDeque, vec::Vec};
23use core::{fmt, ops::Deref};
24
25use anyhow::{anyhow, bail, ensure};
26use borsh::{BorshDeserialize, BorshSerialize};
27use derive_more::Debug;
28use risc0_binfmt::{
29 read_sha_halfs, tagged_list, tagged_list_cons, tagged_struct, write_sha_halfs, Digestible,
30 ExitCode, InvalidExitCodeError,
31};
32use risc0_circuit_rv32im::{HighLowU16, Rv32imV2Claim};
33use risc0_zkp::core::digest::Digest;
34use risc0_zkvm_platform::syscall::halt;
35use serde::{Deserialize, Serialize};
36
37use crate::{
38 sha::{self, Sha256},
39 SystemState,
40};
41
42#[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
53#[cfg_attr(test, derive(PartialEq))]
54pub struct ReceiptClaim {
55 pub pre: MaybePruned<SystemState>,
57
58 pub post: MaybePruned<SystemState>,
60
61 pub exit_code: ExitCode,
63
64 pub input: MaybePruned<Option<Input>>,
66
67 pub output: MaybePruned<Option<Output>>,
69}
70
71impl ReceiptClaim {
72 pub fn ok(
75 image_id: impl Into<Digest>,
76 journal: impl Into<MaybePruned<Vec<u8>>>,
77 ) -> ReceiptClaim {
78 Self {
79 pre: MaybePruned::Pruned(image_id.into()),
80 post: MaybePruned::Value(SystemState {
81 pc: 0,
82 merkle_root: Digest::ZERO,
83 }),
84 exit_code: ExitCode::Halted(0),
85 input: None.into(),
86 output: Some(Output {
87 journal: journal.into(),
88 assumptions: MaybePruned::Pruned(Digest::ZERO),
89 })
90 .into(),
91 }
92 }
93
94 pub fn paused(
97 image_id: impl Into<Digest>,
98 journal: impl Into<MaybePruned<Vec<u8>>>,
99 ) -> ReceiptClaim {
100 Self {
101 pre: MaybePruned::Pruned(image_id.into()),
102 post: MaybePruned::Value(SystemState {
103 pc: 0,
104 merkle_root: Digest::ZERO,
105 }),
106 exit_code: ExitCode::Paused(0),
107 input: None.into(),
108 output: Some(Output {
109 journal: journal.into(),
110 assumptions: MaybePruned::Pruned(Digest::ZERO),
111 })
112 .into(),
113 }
114 }
115
116 pub fn decode(flat: &mut VecDeque<u32>) -> Result<Self, DecodeError> {
118 let input = read_sha_halfs(flat)?;
119 let pre = SystemState::decode(flat)?;
120 let post = SystemState::decode(flat)?;
121 let sys_exit = flat
122 .pop_front()
123 .ok_or(risc0_binfmt::DecodeError::EndOfStream)?;
124 let user_exit = flat
125 .pop_front()
126 .ok_or(risc0_binfmt::DecodeError::EndOfStream)?;
127 let exit_code = ExitCode::from_pair(sys_exit, user_exit)?;
128 let output = read_sha_halfs(flat)?;
129
130 Ok(Self {
131 input: MaybePruned::Pruned(input),
132 pre: pre.into(),
133 post: post.into(),
134 exit_code,
135 output: MaybePruned::Pruned(output),
136 })
137 }
138
139 pub fn encode(&self, flat: &mut Vec<u32>) -> Result<(), PrunedValueError> {
141 write_sha_halfs(flat, &self.input.digest::<sha::Impl>());
142 self.pre.as_value()?.encode(flat);
143 self.post.as_value()?.encode(flat);
144 let (sys_exit, user_exit) = self.exit_code.into_pair();
145 flat.push(sys_exit);
146 flat.push(user_exit);
147 write_sha_halfs(flat, &self.output.digest::<sha::Impl>());
148 Ok(())
149 }
150
151 pub(crate) fn decode_from_seal_v2(
152 seal: &[u32],
153 _po2: Option<u32>,
154 ) -> anyhow::Result<ReceiptClaim> {
155 let claim = Rv32imV2Claim::decode(seal)?;
156 tracing::debug!("claim: {claim:#?}");
157
158 let exit_code = exit_code_from_rv32im_v2_claim(&claim)?;
165 let post_state = match exit_code {
166 ExitCode::Halted(_) => Digest::ZERO,
167 _ => claim.post_state,
168 };
169
170 Ok(ReceiptClaim {
171 pre: MaybePruned::Value(SystemState {
172 pc: 0,
173 merkle_root: claim.pre_state,
174 }),
175 post: MaybePruned::Value(SystemState {
176 pc: 0,
177 merkle_root: post_state,
178 }),
179 exit_code,
180 input: MaybePruned::Pruned(claim.input),
181 output: MaybePruned::Pruned(claim.output.unwrap_or_default()),
182 })
183 }
184}
185
186pub(crate) fn exit_code_from_rv32im_v2_claim(claim: &Rv32imV2Claim) -> anyhow::Result<ExitCode> {
187 let exit_code = if let Some(term) = claim.terminate_state {
188 let HighLowU16(user_exit, halt_type) = term.a0;
189 match halt_type as u32 {
190 halt::TERMINATE => ExitCode::Halted(user_exit as u32),
191 halt::PAUSE => ExitCode::Paused(user_exit as u32),
192 _ => bail!("Illegal halt type: {halt_type}"),
193 }
194 } else {
195 ExitCode::SystemSplit
196 };
197 Ok(exit_code)
198}
199
200impl Digestible for ReceiptClaim {
201 fn digest<S: Sha256>(&self) -> Digest {
203 let (sys_exit, user_exit) = self.exit_code.into_pair();
204 tagged_struct::<S>(
205 "risc0.ReceiptClaim",
206 &[
207 self.input.digest::<S>(),
208 self.pre.digest::<S>(),
209 self.post.digest::<S>(),
210 self.output.digest::<S>(),
211 ],
212 &[sys_exit, user_exit],
213 )
214 }
215}
216
217#[derive(Debug, Copy, Clone)]
219pub enum DecodeError {
220 InvalidExitCode(InvalidExitCodeError),
222 Decode(risc0_binfmt::DecodeError),
224}
225
226impl fmt::Display for DecodeError {
227 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
228 match self {
229 Self::InvalidExitCode(e) => write!(f, "failed to decode receipt claim: {e}"),
230 Self::Decode(e) => write!(f, "failed to decode receipt claim: {e}"),
231 }
232 }
233}
234
235impl From<risc0_binfmt::DecodeError> for DecodeError {
236 fn from(e: risc0_binfmt::DecodeError) -> Self {
237 Self::Decode(e)
238 }
239}
240
241impl From<InvalidExitCodeError> for DecodeError {
242 fn from(e: InvalidExitCodeError) -> Self {
243 Self::InvalidExitCode(e)
244 }
245}
246
247#[cfg(feature = "std")]
248impl std::error::Error for DecodeError {}
249
250#[derive(Clone, Debug, Serialize, Deserialize)]
261#[cfg_attr(test, derive(PartialEq))]
262pub enum Unknown {}
263
264impl Digestible for Unknown {
265 fn digest<S: Sha256>(&self) -> Digest {
266 match *self { }
267 }
268}
269
270impl BorshSerialize for Unknown {
271 fn serialize<W>(&self, _: &mut W) -> core::result::Result<(), borsh::io::Error> {
272 unreachable!("unreachable")
273 }
274}
275
276impl BorshDeserialize for Unknown {
277 fn deserialize_reader<R>(_: &mut R) -> core::result::Result<Self, borsh::io::Error> {
278 unreachable!("unreachable")
279 }
280}
281
282#[derive(Debug, Clone, Serialize, Deserialize)]
285pub struct UnionClaim {
286 pub left: Digest,
290 pub right: Digest,
292}
293
294impl Digestible for UnionClaim {
295 fn digest<S: Sha256>(&self) -> Digest {
296 tagged_struct::<S>("risc0.UnionClaim", &[self.left, self.right], &[])
297 }
298}
299
300#[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
305#[cfg_attr(test, derive(PartialEq))]
306pub struct Input {
307 pub(crate) x: Unknown,
311}
312
313impl Digestible for Input {
314 fn digest<S: Sha256>(&self) -> Digest {
316 match self.x { }
317 }
318}
319
320#[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
322#[cfg_attr(test, derive(PartialEq))]
323pub struct Output {
324 #[debug("{}", fmt_debug_journal(journal))]
326 pub journal: MaybePruned<Vec<u8>>,
327
328 pub assumptions: MaybePruned<Assumptions>,
337}
338
339#[allow(dead_code)]
340fn fmt_debug_journal(journal: &MaybePruned<Vec<u8>>) -> alloc::string::String {
341 match journal {
342 MaybePruned::Value(bytes) => alloc::format!("{} bytes", bytes.len()),
343 MaybePruned::Pruned(_) => alloc::format!("{journal:?}"),
344 }
345}
346
347impl Digestible for Output {
348 fn digest<S: Sha256>(&self) -> Digest {
350 tagged_struct::<S>(
351 "risc0.Output",
352 &[self.journal.digest::<S>(), self.assumptions.digest::<S>()],
353 &[],
354 )
355 }
356}
357
358#[derive(
366 Clone, Debug, Serialize, Deserialize, Eq, Hash, PartialEq, BorshSerialize, BorshDeserialize,
367)]
368pub struct Assumption {
369 pub claim: Digest,
372
373 pub control_root: Digest,
386}
387
388impl Digestible for Assumption {
389 fn digest<S: Sha256>(&self) -> Digest {
391 tagged_struct::<S>("risc0.Assumption", &[self.claim, self.control_root], &[])
392 }
393}
394
395#[derive(Clone, Default, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
397#[cfg_attr(test, derive(PartialEq))]
398pub struct Assumptions(pub Vec<MaybePruned<Assumption>>);
399
400impl Assumptions {
401 pub fn add(&mut self, assumption: MaybePruned<Assumption>) {
403 self.0.insert(0, assumption);
404 }
405
406 pub fn resolve(&mut self, resolved: &Digest) -> anyhow::Result<()> {
410 let head = self
411 .0
412 .first()
413 .ok_or_else(|| anyhow!("cannot resolve assumption from empty list"))?;
414
415 ensure!(
416 &head.digest::<sha::Impl>() == resolved,
417 "resolved assumption is not equal to the head of the list: {} != {}",
418 resolved,
419 head.digest::<sha::Impl>()
420 );
421
422 self.0 = self.0.split_off(1);
424 Ok(())
425 }
426}
427
428impl Deref for Assumptions {
429 type Target = [MaybePruned<Assumption>];
430
431 fn deref(&self) -> &Self::Target {
432 &self.0
433 }
434}
435
436impl Digestible for Assumptions {
437 fn digest<S: Sha256>(&self) -> Digest {
439 tagged_list::<S>(
440 "risc0.Assumptions",
441 &self.0.iter().map(|a| a.digest::<S>()).collect::<Vec<_>>(),
442 )
443 }
444}
445
446impl MaybePruned<Assumptions> {
447 pub fn is_empty(&self) -> bool {
449 match self {
450 MaybePruned::Value(list) => list.is_empty(),
451 MaybePruned::Pruned(digest) => digest == &Digest::ZERO,
452 }
453 }
454
455 pub fn add(&mut self, assumption: MaybePruned<Assumption>) {
459 match self {
460 MaybePruned::Value(list) => list.add(assumption),
461 MaybePruned::Pruned(list_digest) => {
462 *list_digest = tagged_list_cons::<sha::Impl>(
463 "risc0.Assumptions",
464 &assumption.digest::<sha::Impl>(),
465 &*list_digest,
466 );
467 }
468 }
469 }
470
471 pub fn resolve(&mut self, resolved: &Digest, tail: &Digest) -> anyhow::Result<()> {
478 match self {
479 MaybePruned::Value(list) => list.resolve(resolved),
480 MaybePruned::Pruned(list_digest) => {
481 let reconstructed =
482 tagged_list_cons::<sha::Impl>("risc0.Assumptions", resolved, tail);
483 ensure!(
484 &reconstructed == list_digest,
485 "reconstructed list digest does not match; expected {}, reconstructed {}",
486 list_digest,
487 reconstructed
488 );
489
490 *list_digest = *tail;
492 Ok(())
493 }
494 }
495 }
496}
497
498impl From<Vec<MaybePruned<Assumption>>> for Assumptions {
499 fn from(value: Vec<MaybePruned<Assumption>>) -> Self {
500 Self(value)
501 }
502}
503
504impl From<Vec<Assumption>> for Assumptions {
505 fn from(value: Vec<Assumption>) -> Self {
506 Self(value.into_iter().map(Into::into).collect())
507 }
508}
509
510impl From<Vec<Assumption>> for MaybePruned<Assumptions> {
511 fn from(value: Vec<Assumption>) -> Self {
512 Self::Value(value.into())
513 }
514}
515
516#[derive(Clone, Deserialize, Serialize, BorshSerialize, BorshDeserialize)]
525pub enum MaybePruned<T>
526where
527 T: Clone + Serialize,
528{
529 Value(T),
531
532 Pruned(Digest),
534}
535
536impl<T> MaybePruned<T>
537where
538 T: Clone + Serialize,
539{
540 pub fn value(self) -> Result<T, PrunedValueError> {
542 match self {
543 MaybePruned::Value(value) => Ok(value),
544 MaybePruned::Pruned(digest) => Err(PrunedValueError(digest)),
545 }
546 }
547
548 pub fn as_value(&self) -> Result<&T, PrunedValueError> {
550 match self {
551 MaybePruned::Value(ref value) => Ok(value),
552 MaybePruned::Pruned(ref digest) => Err(PrunedValueError(*digest)),
553 }
554 }
555
556 pub fn as_value_mut(&mut self) -> Result<&mut T, PrunedValueError> {
558 match self {
559 MaybePruned::Value(ref mut value) => Ok(value),
560 MaybePruned::Pruned(ref digest) => Err(PrunedValueError(*digest)),
561 }
562 }
563}
564
565impl<T> From<T> for MaybePruned<T>
566where
567 T: Clone + Serialize,
568{
569 fn from(value: T) -> Self {
570 Self::Value(value)
571 }
572}
573
574impl<T> Digestible for MaybePruned<T>
575where
576 T: Digestible + Clone + Serialize,
577{
578 fn digest<S: Sha256>(&self) -> Digest {
579 match self {
580 MaybePruned::Value(ref val) => val.digest::<S>(),
581 MaybePruned::Pruned(digest) => *digest,
582 }
583 }
584}
585
586impl<T> Default for MaybePruned<T>
587where
588 T: Digestible + Default + Clone + Serialize,
589{
590 fn default() -> Self {
591 MaybePruned::Value(Default::default())
592 }
593}
594
595impl<T> MaybePruned<Option<T>>
596where
597 T: Clone + Serialize,
598{
599 pub fn is_none(&self) -> bool {
602 match self {
603 MaybePruned::Value(Some(_)) => false,
604 MaybePruned::Value(None) => true,
605 MaybePruned::Pruned(digest) => digest == &Digest::ZERO,
606 }
607 }
608
609 pub fn is_some(&self) -> bool {
612 !self.is_none()
613 }
614}
615
616#[cfg(test)]
617impl<T> PartialEq for MaybePruned<T>
618where
619 T: Clone + Serialize + PartialEq,
620{
621 fn eq(&self, other: &Self) -> bool {
622 match (self, other) {
623 (Self::Value(a), Self::Value(b)) => a == b,
624 (Self::Pruned(a), Self::Pruned(b)) => a == b,
625 _ => false,
626 }
627 }
628}
629
630impl<T> fmt::Debug for MaybePruned<T>
631where
632 T: Clone + Serialize + Digestible + fmt::Debug,
633{
634 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
638 let mut builder = fmt.debug_struct("MaybePruned");
639 if let MaybePruned::Value(value) = self {
640 builder.field("value", value);
641 }
642 builder
643 .field("digest", &self.digest::<sha::Impl>())
644 .finish()
645 }
646}
647
648#[derive(Debug, Clone)]
650pub struct PrunedValueError(pub Digest);
651
652impl fmt::Display for PrunedValueError {
653 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
654 write!(f, "value is pruned: {}", &self.0)
655 }
656}
657
658#[cfg(feature = "std")]
659impl std::error::Error for PrunedValueError {}
660
661#[cfg(feature = "prove")]
667pub(crate) trait Merge: Digestible + Sized {
668 fn merge(&self, other: &Self) -> Result<Self, MergeInequalityError>;
670
671 fn merge_with(&mut self, other: &Self) -> Result<(), MergeInequalityError> {
673 *self = self.merge(other)?;
675 Ok(())
676 }
677}
678
679#[cfg(feature = "prove")]
681#[derive(Debug, Clone)]
682pub(crate) struct MergeInequalityError(pub Digest, pub Digest);
683
684#[cfg(feature = "prove")]
685impl fmt::Display for MergeInequalityError {
686 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
687 write!(
688 f,
689 "cannot merge values; left and right are not digest equal: left {}, right {}",
690 hex::encode(self.0),
691 hex::encode(self.1)
692 )
693 }
694}
695
696#[cfg(all(feature = "std", feature = "prove"))]
697impl std::error::Error for MergeInequalityError {}
698
699#[cfg(feature = "prove")]
701trait MergeLeaf: Digestible + PartialEq + Clone + Sized {}
702
703#[cfg(feature = "prove")]
704impl MergeLeaf for SystemState {}
705#[cfg(feature = "prove")]
706impl MergeLeaf for Assumption {}
707#[cfg(feature = "prove")]
708impl MergeLeaf for Vec<u8> {}
709
710#[cfg(feature = "prove")]
711impl<T: MergeLeaf> Merge for T {
712 fn merge(&self, other: &Self) -> Result<Self, MergeInequalityError> {
713 if self != other {
714 return Err(MergeInequalityError(
715 self.digest::<sha::Impl>(),
716 other.digest::<sha::Impl>(),
717 ));
718 }
719
720 Ok(self.clone())
721 }
722}
723
724#[cfg(feature = "prove")]
725impl<T> Merge for MaybePruned<T>
726where
727 T: Merge + Serialize + Clone,
728{
729 fn merge(&self, other: &Self) -> Result<Self, MergeInequalityError> {
730 let check_eq = || {
731 if self.digest::<sha::Impl>() != other.digest::<sha::Impl>() {
732 Err(MergeInequalityError(
733 self.digest::<sha::Impl>(),
734 other.digest::<sha::Impl>(),
735 ))
736 } else {
737 Ok(())
738 }
739 };
740
741 Ok(match (self, other) {
742 (MaybePruned::Value(left), MaybePruned::Value(right)) => {
743 MaybePruned::Value(left.merge(right)?)
744 }
745 (MaybePruned::Value(_), MaybePruned::Pruned(_)) => {
746 check_eq()?;
747 self.clone()
748 }
749 (MaybePruned::Pruned(_), MaybePruned::Value(_)) => {
750 check_eq()?;
751 other.clone()
752 }
753 (MaybePruned::Pruned(_), MaybePruned::Pruned(_)) => {
754 check_eq()?;
755 self.clone()
756 }
757 })
758 }
759}
760
761#[cfg(feature = "prove")]
762impl<T: Merge> Merge for Option<T> {
763 fn merge(&self, other: &Self) -> Result<Self, MergeInequalityError> {
764 match (self, other) {
765 (Some(left), Some(right)) => Some(left.merge(right)).transpose(),
766 (None, None) => Ok(None),
767 _ => Err(MergeInequalityError(
768 self.digest::<sha::Impl>(),
769 other.digest::<sha::Impl>(),
770 )),
771 }
772 }
773}
774
775#[cfg(feature = "prove")]
776impl Merge for Assumptions {
777 fn merge(&self, other: &Self) -> Result<Self, MergeInequalityError> {
778 if self.0.len() != other.0.len() {
779 return Err(MergeInequalityError(
780 self.digest::<sha::Impl>(),
781 other.digest::<sha::Impl>(),
782 ));
783 }
784 Ok(Assumptions(
785 self.0
786 .iter()
787 .zip(other.0.iter())
788 .map(|(left, right)| left.merge(right))
789 .collect::<Result<Vec<_>, _>>()?,
790 ))
791 }
792}
793
794#[cfg(feature = "prove")]
795impl Merge for Input {
796 fn merge(&self, _other: &Self) -> Result<Self, MergeInequalityError> {
797 match self.x { }
798 }
799}
800
801#[cfg(feature = "prove")]
802impl Merge for Output {
803 fn merge(&self, other: &Self) -> Result<Self, MergeInequalityError> {
804 Ok(Self {
805 journal: self.journal.merge(&other.journal)?,
806 assumptions: self.assumptions.merge(&other.assumptions)?,
807 })
808 }
809}
810
811#[cfg(feature = "prove")]
812impl Merge for ReceiptClaim {
813 fn merge(&self, other: &Self) -> Result<Self, MergeInequalityError> {
814 if self.exit_code != other.exit_code {
815 return Err(MergeInequalityError(
816 self.digest::<sha::Impl>(),
817 other.digest::<sha::Impl>(),
818 ));
819 }
820 Ok(Self {
821 pre: self.pre.merge(&other.pre)?,
822 post: self.post.merge(&other.post)?,
823 exit_code: self.exit_code,
824 input: self.input.merge(&other.input)?,
825 output: self.output.merge(&other.output)?,
826 })
827 }
828}
829
830#[cfg(feature = "prove")]
831#[cfg(test)]
832mod tests {
833 use hex::FromHex;
834
835 use super::{Assumptions, ExitCode, MaybePruned, Merge, Output, ReceiptClaim, SystemState};
836 use crate::sha::{Digest, Digestible};
837
838 trait RandPrune {
840 fn rand_prune(&self) -> Self;
841 }
842
843 impl RandPrune for MaybePruned<ReceiptClaim> {
844 fn rand_prune(&self) -> Self {
845 match (self, rand::random::<bool>()) {
846 (Self::Value(x), true) => Self::Pruned(x.digest()),
847 (Self::Value(x), false) => ReceiptClaim {
848 pre: x.pre.rand_prune(),
849 post: x.post.rand_prune(),
850 exit_code: x.exit_code,
851 input: x.input.clone(),
852 output: x.output.rand_prune(),
853 }
854 .into(),
855 (Self::Pruned(x), _) => Self::Pruned(*x),
856 }
857 }
858 }
859
860 impl RandPrune for MaybePruned<SystemState> {
861 fn rand_prune(&self) -> Self {
862 match (self, rand::random::<bool>()) {
863 (Self::Value(x), true) => Self::Pruned(x.digest()),
864 (Self::Value(x), false) => SystemState {
865 pc: x.pc,
866 merkle_root: x.merkle_root,
867 }
868 .into(),
869 (Self::Pruned(x), _) => Self::Pruned(*x),
870 }
871 }
872 }
873
874 impl RandPrune for MaybePruned<Option<Output>> {
875 fn rand_prune(&self) -> Self {
876 match (self, rand::random::<bool>()) {
877 (Self::Value(x), true) => Self::Pruned(x.digest()),
878 (Self::Value(x), false) => x
879 .as_ref()
880 .map(|o| Output {
881 journal: o.journal.rand_prune(),
882 assumptions: o.assumptions.rand_prune(),
883 })
884 .into(),
885 (Self::Pruned(x), _) => Self::Pruned(*x),
886 }
887 }
888 }
889
890 impl RandPrune for MaybePruned<Vec<u8>> {
891 fn rand_prune(&self) -> Self {
892 match (self, rand::random::<bool>()) {
893 (Self::Value(x), true) => Self::Pruned(x.digest()),
894 (Self::Value(x), false) => x.clone().into(),
895 (Self::Pruned(x), _) => Self::Pruned(*x),
896 }
897 }
898 }
899
900 impl RandPrune for MaybePruned<Assumptions> {
901 fn rand_prune(&self) -> Self {
902 match (self, rand::random::<bool>()) {
903 (Self::Value(x), true) => Self::Pruned(x.digest()),
904 (Self::Value(x), false) => x.clone().into(),
905 (Self::Pruned(x), _) => Self::Pruned(*x),
906 }
907 }
908 }
909
910 #[test]
911 fn merge_receipt_claim() {
912 let claim = MaybePruned::Value(ReceiptClaim {
913 pre: SystemState {
914 pc: 2100484,
915 merkle_root: Digest::from_hex(
916 "9095da07d84ccc170c5113e3dafdf0531700f0b3f0c627acc9f0329440d984fa",
917 )
918 .unwrap(),
919 }
920 .into(),
921 post: SystemState {
922 pc: 2297164,
923 merkle_root: Digest::from_hex(
924 "223651656250c0cf2f1c3f8923ef3d2c8624a361830492ffec6450e1930fb07d",
925 )
926 .unwrap(),
927 }
928 .into(),
929 exit_code: ExitCode::Halted(0),
930 input: None.into(),
931 output: MaybePruned::Value(Some(Output {
932 journal: MaybePruned::Value(b"hello world".to_vec()),
933 assumptions: MaybePruned::Value(Assumptions(vec![
934 MaybePruned::Pruned(Digest::ZERO),
935 MaybePruned::Pruned(Digest::ZERO),
936 ])),
937 })),
938 });
939
940 for _ in 0..10000 {
942 let left = claim.rand_prune();
943 let right = claim.rand_prune();
944
945 assert_eq!(left.merge(&right).unwrap().digest(), claim.digest());
946 }
947 }
948}