risc0_zkvm/
receipt_claim.rs

1// Copyright 2024 RISC Zero, Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! [ReceiptClaim] and associated types and functions.
16//!
17//! A [ReceiptClaim] struct contains the public claims (i.e. public outputs) of a zkVM guest
18//! execution, such as the journal committed to by the guest. It also includes important
19//! information such as the exit code and the starting and ending system state (i.e. the state of
20//! memory).
21
22use alloc::{collections::VecDeque, vec::Vec};
23use core::{fmt, ops::Deref};
24
25use anyhow::{anyhow, ensure};
26use borsh::{BorshDeserialize, BorshSerialize};
27use risc0_binfmt::{
28    read_sha_halfs, tagged_list, tagged_list_cons, tagged_struct, write_sha_halfs, Digestible,
29    ExitCode, InvalidExitCodeError,
30};
31use risc0_zkp::core::digest::Digest;
32use serde::{Deserialize, Serialize};
33
34use crate::{
35    sha::{self, Sha256},
36    SystemState,
37};
38
39// TODO(victor): Add functions to handle the `ReceiptClaim` transformations conducted as part of
40// join, resolve, and eventually resume calls. This will allow these to be used for recursion, as
41// well as dev mode recursion, and composite receipts.
42
43/// Public claims about a zkVM guest execution, such as the journal committed to by the guest.
44///
45/// Also includes important information such as the exit code and the starting and ending system
46/// state (i.e. the state of memory). [ReceiptClaim] is a "Merkle-ized struct" supporting
47/// partial openings of the underlying fields from a hash commitment to the full structure. Also
48/// see [MaybePruned].
49#[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
50#[cfg_attr(test, derive(PartialEq))]
51pub struct ReceiptClaim {
52    /// The [SystemState] just before execution has begun.
53    pub pre: MaybePruned<SystemState>,
54
55    /// The [SystemState] just after execution has completed.
56    pub post: MaybePruned<SystemState>,
57
58    /// The exit code for the execution.
59    pub exit_code: ExitCode,
60
61    /// Input to the guest.
62    pub input: MaybePruned<Option<Input>>,
63
64    /// [Output] of the guest, including the journal and assumptions set during execution.
65    pub output: MaybePruned<Option<Output>>,
66}
67
68impl ReceiptClaim {
69    /// Construct a [ReceiptClaim] representing a zkVM execution that ended normally (i.e.
70    /// Halted(0)) with the given image ID and journal.
71    pub fn ok(
72        image_id: impl Into<Digest>,
73        journal: impl Into<MaybePruned<Vec<u8>>>,
74    ) -> ReceiptClaim {
75        Self {
76            pre: MaybePruned::Pruned(image_id.into()),
77            post: MaybePruned::Value(SystemState {
78                pc: 0,
79                merkle_root: Digest::ZERO,
80            }),
81            exit_code: ExitCode::Halted(0),
82            input: None.into(),
83            output: Some(Output {
84                journal: journal.into(),
85                assumptions: MaybePruned::Pruned(Digest::ZERO),
86            })
87            .into(),
88        }
89    }
90
91    /// Construct a [ReceiptClaim] representing a zkVM execution that ended in a normal paused
92    /// state (i.e. Paused(0)) with the given image ID and journal.
93    pub fn paused(
94        image_id: impl Into<Digest>,
95        journal: impl Into<MaybePruned<Vec<u8>>>,
96    ) -> ReceiptClaim {
97        Self {
98            pre: MaybePruned::Pruned(image_id.into()),
99            post: MaybePruned::Value(SystemState {
100                pc: 0,
101                merkle_root: Digest::ZERO,
102            }),
103            exit_code: ExitCode::Paused(0),
104            input: None.into(),
105            output: Some(Output {
106                journal: journal.into(),
107                assumptions: MaybePruned::Pruned(Digest::ZERO),
108            })
109            .into(),
110        }
111    }
112
113    /// Decode a [ReceiptClaim] from a list of [u32]'s
114    pub fn decode(flat: &mut VecDeque<u32>) -> Result<Self, DecodeError> {
115        let input = read_sha_halfs(flat)?;
116        let pre = SystemState::decode(flat)?;
117        let post = SystemState::decode(flat)?;
118        let sys_exit = flat
119            .pop_front()
120            .ok_or(risc0_binfmt::DecodeError::EndOfStream)?;
121        let user_exit = flat
122            .pop_front()
123            .ok_or(risc0_binfmt::DecodeError::EndOfStream)?;
124        let exit_code = ExitCode::from_pair(sys_exit, user_exit)?;
125        let output = read_sha_halfs(flat)?;
126
127        Ok(Self {
128            input: MaybePruned::Pruned(input),
129            pre: pre.into(),
130            post: post.into(),
131            exit_code,
132            output: MaybePruned::Pruned(output),
133        })
134    }
135
136    /// Encode a [ReceiptClaim] to a list of [u32]'s
137    pub fn encode(&self, flat: &mut Vec<u32>) -> Result<(), PrunedValueError> {
138        write_sha_halfs(flat, &self.input.digest::<sha::Impl>());
139        self.pre.as_value()?.encode(flat);
140        self.post.as_value()?.encode(flat);
141        let (sys_exit, user_exit) = self.exit_code.into_pair();
142        flat.push(sys_exit);
143        flat.push(user_exit);
144        write_sha_halfs(flat, &self.output.digest::<sha::Impl>());
145        Ok(())
146    }
147}
148
149impl Digestible for ReceiptClaim {
150    /// Hash the [ReceiptClaim] to get a digest of the struct.
151    fn digest<S: Sha256>(&self) -> Digest {
152        let (sys_exit, user_exit) = self.exit_code.into_pair();
153        tagged_struct::<S>(
154            "risc0.ReceiptClaim",
155            &[
156                self.input.digest::<S>(),
157                self.pre.digest::<S>(),
158                self.post.digest::<S>(),
159                self.output.digest::<S>(),
160            ],
161            &[sys_exit, user_exit],
162        )
163    }
164}
165
166/// Error returned when decoding [ReceiptClaim] fails.
167#[derive(Debug, Copy, Clone)]
168pub enum DecodeError {
169    /// Decoding failure due to an invalid exit code.
170    InvalidExitCode(InvalidExitCodeError),
171    /// Decoding failure due to an inner decoding failure.
172    Decode(risc0_binfmt::DecodeError),
173}
174
175impl fmt::Display for DecodeError {
176    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
177        match self {
178            Self::InvalidExitCode(e) => write!(f, "failed to decode receipt claim: {e}"),
179            Self::Decode(e) => write!(f, "failed to decode receipt claim: {e}"),
180        }
181    }
182}
183
184impl From<risc0_binfmt::DecodeError> for DecodeError {
185    fn from(e: risc0_binfmt::DecodeError) -> Self {
186        Self::Decode(e)
187    }
188}
189
190impl From<InvalidExitCodeError> for DecodeError {
191    fn from(e: InvalidExitCodeError) -> Self {
192        Self::InvalidExitCode(e)
193    }
194}
195
196#[cfg(feature = "std")]
197impl std::error::Error for DecodeError {}
198
199/// A type representing an unknown claim type.
200///
201/// A receipt (e.g. [SuccinctReceipt][crate::SuccinctReceipt]) may have an unknown claim type when
202/// only the digest of the claim is needed, and the full claim value cannot be determined by the
203/// compiler. This allows for a collection of receipts to be created even when the underlying
204/// claims are of heterogeneous types (e.g. `Vec<SuccinctReceipt<Unknown>>`).
205///
206/// Note that this is an uninhabited type, similar to the [never type].
207///
208/// [never type]: https://doc.rust-lang.org/std/primitive.never.html
209#[derive(Clone, Debug, Serialize, Deserialize)]
210#[cfg_attr(test, derive(PartialEq))]
211pub enum Unknown {}
212
213impl Digestible for Unknown {
214    fn digest<S: Sha256>(&self) -> Digest {
215        match *self { /* unreachable  */ }
216    }
217}
218
219impl BorshSerialize for Unknown {
220    fn serialize<W>(&self, _: &mut W) -> core::result::Result<(), borsh::io::Error> {
221        unreachable!("unreachable")
222    }
223}
224
225impl BorshDeserialize for Unknown {
226    fn deserialize_reader<R>(_: &mut R) -> core::result::Result<Self, borsh::io::Error> {
227        unreachable!("unreachable")
228    }
229}
230
231/// Input field in the [ReceiptClaim], committing to a public value accessible to the guest.
232///
233/// NOTE: This type is currently uninhabited (i.e. it cannot be constructed), and only its digest
234/// is accessible. It may become inhabited in a future release.
235#[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
236#[cfg_attr(test, derive(PartialEq))]
237pub struct Input {
238    // Private field to ensure this type cannot be constructed.
239    // By making this type uninhabited, it can be populated later without breaking backwards
240    // compatibility.
241    pub(crate) x: Unknown,
242}
243
244impl Digestible for Input {
245    /// Hash the [Input] to get a digest of the struct.
246    fn digest<S: Sha256>(&self) -> Digest {
247        match self.x { /* unreachable  */ }
248    }
249}
250
251/// Output field in the [ReceiptClaim], committing to a claimed journal and assumptions list.
252#[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
253#[cfg_attr(test, derive(PartialEq))]
254pub struct Output {
255    /// The journal committed to by the guest execution.
256    pub journal: MaybePruned<Vec<u8>>,
257
258    /// An ordered list of [ReceiptClaim] digests corresponding to the
259    /// calls to `env::verify` and `env::verify_integrity`.
260    ///
261    /// Verifying the integrity of a [crate::Receipt] corresponding to a [ReceiptClaim] with a
262    /// non-empty assumptions list does not guarantee unconditionally any of the claims over the
263    /// guest execution (i.e. if the assumptions list is non-empty, then the journal digest cannot
264    /// be trusted to correspond to a genuine execution). The claims can be checked by additional
265    /// verifying a [crate::Receipt] for every digest in the assumptions list.
266    pub assumptions: MaybePruned<Assumptions>,
267}
268
269impl Digestible for Output {
270    /// Hash the [Output] to get a digest of the struct.
271    fn digest<S: Sha256>(&self) -> Digest {
272        tagged_struct::<S>(
273            "risc0.Output",
274            &[self.journal.digest::<S>(), self.assumptions.digest::<S>()],
275            &[],
276        )
277    }
278}
279
280/// An [assumption] made in the course of proving program execution.
281///
282/// Assumptions are generated when the guest makes a recursive verification call. Each assumption
283/// commits the statement, such that only a receipt proving that statement can be used to resolve
284/// and remove the assumption.
285///
286/// [assumption]: https://dev.risczero.com/terminology#assumption
287#[derive(
288    Clone, Debug, Serialize, Deserialize, Eq, Hash, PartialEq, BorshSerialize, BorshDeserialize,
289)]
290pub struct Assumption {
291    /// Commitment to the assumption claim. It may be the digest of a [ReceiptClaim], or it could
292    /// be the digest of the claim for a different circuit such as an accelerator.
293    pub claim: Digest,
294
295    /// Commitment to the set of [recursion programs] that can be used to resolve this assumption.
296    ///
297    /// Binding the set of recursion programs also binds the circuits, and creates an assumption
298    /// resolved by independent set of circuits (e.g. keccak or Groth16 verify). Proofs of these
299    /// external claims are verified by a "lift" program implemented for the recursion VM which
300    /// brings the claim into the recursion system. This lift program is committed to in the
301    /// control root.
302    ///
303    /// A special value of all zeroes indicates "self-composition", where the control root used to
304    /// verify this claim is also used to verify the assumption.
305    ///
306    /// [recursion programs]: https://dev.risczero.com/terminology#recursion-program
307    pub control_root: Digest,
308}
309
310impl Digestible for Assumption {
311    /// Hash the [Assumption] to get a digest of the struct.
312    fn digest<S: Sha256>(&self) -> Digest {
313        tagged_struct::<S>("risc0.Assumption", &[self.claim, self.control_root], &[])
314    }
315}
316
317/// A list of assumptions, each a [Digest] or populated value of an [Assumption].
318#[derive(Clone, Default, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
319#[cfg_attr(test, derive(PartialEq))]
320pub struct Assumptions(pub Vec<MaybePruned<Assumption>>);
321
322impl Assumptions {
323    /// Add an assumption to the head of the assumptions list.
324    pub fn add(&mut self, assumption: MaybePruned<Assumption>) {
325        self.0.insert(0, assumption);
326    }
327
328    /// Mark an assumption as resolved and remove it from the list.
329    ///
330    /// Assumptions can only be removed from the head of the list.
331    pub fn resolve(&mut self, resolved: &Digest) -> anyhow::Result<()> {
332        let head = self
333            .0
334            .first()
335            .ok_or_else(|| anyhow!("cannot resolve assumption from empty list"))?;
336
337        ensure!(
338            &head.digest::<sha::Impl>() == resolved,
339            "resolved assumption is not equal to the head of the list: {} != {}",
340            resolved,
341            head.digest::<sha::Impl>()
342        );
343
344        // Drop the head of the assumptions list.
345        self.0 = self.0.split_off(1);
346        Ok(())
347    }
348}
349
350impl Deref for Assumptions {
351    type Target = [MaybePruned<Assumption>];
352
353    fn deref(&self) -> &Self::Target {
354        &self.0
355    }
356}
357
358impl Digestible for Assumptions {
359    /// Hash the [Assumptions] to get a digest of the struct.
360    fn digest<S: Sha256>(&self) -> Digest {
361        tagged_list::<S>(
362            "risc0.Assumptions",
363            &self.0.iter().map(|a| a.digest::<S>()).collect::<Vec<_>>(),
364        )
365    }
366}
367
368impl MaybePruned<Assumptions> {
369    /// Check if the (possibly pruned) assumptions list is empty.
370    pub fn is_empty(&self) -> bool {
371        match self {
372            MaybePruned::Value(list) => list.is_empty(),
373            MaybePruned::Pruned(digest) => digest == &Digest::ZERO,
374        }
375    }
376
377    /// Add an assumption to the head of the assumptions list.
378    ///
379    /// If this value is pruned, then the result will also be a pruned value.
380    pub fn add(&mut self, assumption: MaybePruned<Assumption>) {
381        match self {
382            MaybePruned::Value(list) => list.add(assumption),
383            MaybePruned::Pruned(list_digest) => {
384                *list_digest = tagged_list_cons::<sha::Impl>(
385                    "risc0.Assumptions",
386                    &assumption.digest::<sha::Impl>(),
387                    &*list_digest,
388                );
389            }
390        }
391    }
392
393    /// Mark an assumption as resolved and remove it from the list.
394    ///
395    /// Assumptions can only be removed from the head of the list. If this value
396    /// is pruned, then the result will also be a pruned value. The `tail`
397    /// parameter should be equal to the digest of the list after the
398    /// resolved assumption is removed.
399    pub fn resolve(&mut self, resolved: &Digest, tail: &Digest) -> anyhow::Result<()> {
400        match self {
401            MaybePruned::Value(list) => list.resolve(resolved),
402            MaybePruned::Pruned(list_digest) => {
403                let reconstructed =
404                    tagged_list_cons::<sha::Impl>("risc0.Assumptions", resolved, tail);
405                ensure!(
406                    &reconstructed == list_digest,
407                    "reconstructed list digest does not match; expected {}, reconstructed {}",
408                    list_digest,
409                    reconstructed
410                );
411
412                // Set the pruned digest value to be equal to the rest parameter.
413                *list_digest = *tail;
414                Ok(())
415            }
416        }
417    }
418}
419
420impl From<Vec<MaybePruned<Assumption>>> for Assumptions {
421    fn from(value: Vec<MaybePruned<Assumption>>) -> Self {
422        Self(value)
423    }
424}
425
426impl From<Vec<Assumption>> for Assumptions {
427    fn from(value: Vec<Assumption>) -> Self {
428        Self(value.into_iter().map(Into::into).collect())
429    }
430}
431
432impl From<Vec<Assumption>> for MaybePruned<Assumptions> {
433    fn from(value: Vec<Assumption>) -> Self {
434        Self::Value(value.into())
435    }
436}
437
438/// Either a source value or a hash [Digest] of the source value.
439///
440/// This type supports creating "Merkle-ized structs". Each field of a Merkle-ized struct can have
441/// either the full value, or it can be "pruned" and replaced with a digest committing to that
442/// value. One way to think of this is as a special Merkle tree of a predefined shape. Each field
443/// is a child node. Any field/node in the tree can be opened by providing the Merkle inclusion
444/// proof. When a subtree is pruned, the digest commits to the value of all contained fields.
445/// [ReceiptClaim] is the motivating example of this type of Merkle-ized struct.
446#[derive(Clone, Deserialize, Serialize, BorshSerialize, BorshDeserialize)]
447pub enum MaybePruned<T>
448where
449    T: Clone + Serialize,
450{
451    /// Unpruned value.
452    Value(T),
453
454    /// Pruned value, which is a hash [Digest] of the value.
455    Pruned(Digest),
456}
457
458impl<T> MaybePruned<T>
459where
460    T: Clone + Serialize,
461{
462    /// Unwrap the value, or return an error.
463    pub fn value(self) -> Result<T, PrunedValueError> {
464        match self {
465            MaybePruned::Value(value) => Ok(value),
466            MaybePruned::Pruned(digest) => Err(PrunedValueError(digest)),
467        }
468    }
469
470    /// Unwrap the value as a reference, or return an error.
471    pub fn as_value(&self) -> Result<&T, PrunedValueError> {
472        match self {
473            MaybePruned::Value(ref value) => Ok(value),
474            MaybePruned::Pruned(ref digest) => Err(PrunedValueError(*digest)),
475        }
476    }
477
478    /// Unwrap the value as a mutable reference, or return an error.
479    pub fn as_value_mut(&mut self) -> Result<&mut T, PrunedValueError> {
480        match self {
481            MaybePruned::Value(ref mut value) => Ok(value),
482            MaybePruned::Pruned(ref digest) => Err(PrunedValueError(*digest)),
483        }
484    }
485}
486
487impl<T> From<T> for MaybePruned<T>
488where
489    T: Clone + Serialize,
490{
491    fn from(value: T) -> Self {
492        Self::Value(value)
493    }
494}
495
496impl<T> Digestible for MaybePruned<T>
497where
498    T: Digestible + Clone + Serialize,
499{
500    fn digest<S: Sha256>(&self) -> Digest {
501        match self {
502            MaybePruned::Value(ref val) => val.digest::<S>(),
503            MaybePruned::Pruned(digest) => *digest,
504        }
505    }
506}
507
508impl<T> Default for MaybePruned<T>
509where
510    T: Digestible + Default + Clone + Serialize,
511{
512    fn default() -> Self {
513        MaybePruned::Value(Default::default())
514    }
515}
516
517impl<T> MaybePruned<Option<T>>
518where
519    T: Clone + Serialize,
520{
521    /// Returns true is the value is None, or the value is pruned as the zero
522    /// digest.
523    pub fn is_none(&self) -> bool {
524        match self {
525            MaybePruned::Value(Some(_)) => false,
526            MaybePruned::Value(None) => true,
527            MaybePruned::Pruned(digest) => digest == &Digest::ZERO,
528        }
529    }
530
531    /// Returns true is the value is Some(_), or the value is pruned as a
532    /// non-zero digest.
533    pub fn is_some(&self) -> bool {
534        !self.is_none()
535    }
536}
537
538#[cfg(test)]
539impl<T> PartialEq for MaybePruned<T>
540where
541    T: Clone + Serialize + PartialEq,
542{
543    fn eq(&self, other: &Self) -> bool {
544        match (self, other) {
545            (Self::Value(a), Self::Value(b)) => a == b,
546            (Self::Pruned(a), Self::Pruned(b)) => a == b,
547            _ => false,
548        }
549    }
550}
551
552impl<T> fmt::Debug for MaybePruned<T>
553where
554    T: Clone + Serialize + Digestible + fmt::Debug,
555{
556    /// Format [MaybePruned] values are if they were a struct with value and
557    /// digest fields. Digest field is always provided so that divergent
558    /// trees of [MaybePruned] values can be compared.
559    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
560        let mut builder = fmt.debug_struct("MaybePruned");
561        if let MaybePruned::Value(value) = self {
562            builder.field("value", value);
563        }
564        builder
565            .field("digest", &self.digest::<sha::Impl>())
566            .finish()
567    }
568}
569
570/// Error returned when the source value was pruned, and is not available.
571#[derive(Debug, Clone)]
572pub struct PrunedValueError(pub Digest);
573
574impl fmt::Display for PrunedValueError {
575    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
576        write!(f, "value is pruned: {}", &self.0)
577    }
578}
579
580#[cfg(feature = "std")]
581impl std::error::Error for PrunedValueError {}
582
583/// Merge two structures containing [MaybePruned] fields to produce a resulting structure with
584/// populated fields equal to the union of the two.
585///
586/// Viewing the two structs as Merkle trees, in which subtrees may be pruned, the result of this
587/// operation is a tree with a set of nodes equal to the union of the set of nodes for each input.
588#[cfg(feature = "prove")]
589pub(crate) trait Merge: Digestible + Sized {
590    /// Merge two structs to produce an output with a union of the fields populated in the inputs.
591    fn merge(&self, other: &Self) -> Result<Self, MergeInequalityError>;
592
593    /// Merge two structs to assigning self as the union of the fields populated in the two inputs.
594    fn merge_with(&mut self, other: &Self) -> Result<(), MergeInequalityError> {
595        // Not a very efficient implementation.
596        *self = self.merge(other)?;
597        Ok(())
598    }
599}
600
601/// Error returned when a merge it attempted with two values with unequal digests.
602#[cfg(feature = "prove")]
603#[derive(Debug, Clone)]
604pub(crate) struct MergeInequalityError(pub Digest, pub Digest);
605
606#[cfg(feature = "prove")]
607impl fmt::Display for MergeInequalityError {
608    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
609        write!(
610            f,
611            "cannot merge values; left and right are not digest equal: left {}, right {}",
612            hex::encode(self.0),
613            hex::encode(self.1)
614        )
615    }
616}
617
618#[cfg(all(feature = "std", feature = "prove"))]
619impl std::error::Error for MergeInequalityError {}
620
621/// Private marker trait providing an implementation of merge to values which implement PartialEq and clone and do not contain Merge fields.
622#[cfg(feature = "prove")]
623trait MergeLeaf: Digestible + PartialEq + Clone + Sized {}
624
625#[cfg(feature = "prove")]
626impl MergeLeaf for SystemState {}
627#[cfg(feature = "prove")]
628impl MergeLeaf for Assumption {}
629#[cfg(feature = "prove")]
630impl MergeLeaf for Vec<u8> {}
631
632#[cfg(feature = "prove")]
633impl<T: MergeLeaf> Merge for T {
634    fn merge(&self, other: &Self) -> Result<Self, MergeInequalityError> {
635        if self != other {
636            return Err(MergeInequalityError(
637                self.digest::<sha::Impl>(),
638                other.digest::<sha::Impl>(),
639            ));
640        }
641
642        Ok(self.clone())
643    }
644}
645
646#[cfg(feature = "prove")]
647impl<T> Merge for MaybePruned<T>
648where
649    T: Merge + Serialize + Clone,
650{
651    fn merge(&self, other: &Self) -> Result<Self, MergeInequalityError> {
652        let check_eq = || {
653            if self.digest::<sha::Impl>() != other.digest::<sha::Impl>() {
654                Err(MergeInequalityError(
655                    self.digest::<sha::Impl>(),
656                    other.digest::<sha::Impl>(),
657                ))
658            } else {
659                Ok(())
660            }
661        };
662
663        Ok(match (self, other) {
664            (MaybePruned::Value(left), MaybePruned::Value(right)) => {
665                MaybePruned::Value(left.merge(right)?)
666            }
667            (MaybePruned::Value(_), MaybePruned::Pruned(_)) => {
668                check_eq()?;
669                self.clone()
670            }
671            (MaybePruned::Pruned(_), MaybePruned::Value(_)) => {
672                check_eq()?;
673                other.clone()
674            }
675            (MaybePruned::Pruned(_), MaybePruned::Pruned(_)) => {
676                check_eq()?;
677                self.clone()
678            }
679        })
680    }
681}
682
683#[cfg(feature = "prove")]
684impl<T: Merge> Merge for Option<T> {
685    fn merge(&self, other: &Self) -> Result<Self, MergeInequalityError> {
686        match (self, other) {
687            (Some(left), Some(right)) => Some(left.merge(right)).transpose(),
688            (None, None) => Ok(None),
689            _ => Err(MergeInequalityError(
690                self.digest::<sha::Impl>(),
691                other.digest::<sha::Impl>(),
692            )),
693        }
694    }
695}
696
697#[cfg(feature = "prove")]
698impl Merge for Assumptions {
699    fn merge(&self, other: &Self) -> Result<Self, MergeInequalityError> {
700        if self.0.len() != other.0.len() {
701            return Err(MergeInequalityError(
702                self.digest::<sha::Impl>(),
703                other.digest::<sha::Impl>(),
704            ));
705        }
706        Ok(Assumptions(
707            self.0
708                .iter()
709                .zip(other.0.iter())
710                .map(|(left, right)| left.merge(right))
711                .collect::<Result<Vec<_>, _>>()?,
712        ))
713    }
714}
715
716#[cfg(feature = "prove")]
717impl Merge for Input {
718    fn merge(&self, _other: &Self) -> Result<Self, MergeInequalityError> {
719        match self.x { /* unreachable  */ }
720    }
721}
722
723#[cfg(feature = "prove")]
724impl Merge for Output {
725    fn merge(&self, other: &Self) -> Result<Self, MergeInequalityError> {
726        Ok(Self {
727            journal: self.journal.merge(&other.journal)?,
728            assumptions: self.assumptions.merge(&other.assumptions)?,
729        })
730    }
731}
732
733#[cfg(feature = "prove")]
734impl Merge for ReceiptClaim {
735    fn merge(&self, other: &Self) -> Result<Self, MergeInequalityError> {
736        if self.exit_code != other.exit_code {
737            return Err(MergeInequalityError(
738                self.digest::<sha::Impl>(),
739                other.digest::<sha::Impl>(),
740            ));
741        }
742        Ok(Self {
743            pre: self.pre.merge(&other.pre)?,
744            post: self.post.merge(&other.post)?,
745            exit_code: self.exit_code,
746            input: self.input.merge(&other.input)?,
747            output: self.output.merge(&other.output)?,
748        })
749    }
750}
751
752#[cfg(feature = "prove")]
753#[cfg(test)]
754mod tests {
755    use hex::FromHex;
756
757    use super::{Assumptions, ExitCode, MaybePruned, Merge, Output, ReceiptClaim, SystemState};
758    use crate::sha::{Digest, Digestible};
759
760    /// Testing utility for randomly pruning structs.
761    trait RandPrune {
762        fn rand_prune(&self) -> Self;
763    }
764
765    impl RandPrune for MaybePruned<ReceiptClaim> {
766        fn rand_prune(&self) -> Self {
767            match (self, rand::random::<bool>()) {
768                (Self::Value(x), true) => Self::Pruned(x.digest()),
769                (Self::Value(x), false) => ReceiptClaim {
770                    pre: x.pre.rand_prune(),
771                    post: x.post.rand_prune(),
772                    exit_code: x.exit_code,
773                    input: x.input.clone(),
774                    output: x.output.rand_prune(),
775                }
776                .into(),
777                (Self::Pruned(x), _) => Self::Pruned(*x),
778            }
779        }
780    }
781
782    impl RandPrune for MaybePruned<SystemState> {
783        fn rand_prune(&self) -> Self {
784            match (self, rand::random::<bool>()) {
785                (Self::Value(x), true) => Self::Pruned(x.digest()),
786                (Self::Value(x), false) => SystemState {
787                    pc: x.pc,
788                    merkle_root: x.merkle_root,
789                }
790                .into(),
791                (Self::Pruned(x), _) => Self::Pruned(*x),
792            }
793        }
794    }
795
796    impl RandPrune for MaybePruned<Option<Output>> {
797        fn rand_prune(&self) -> Self {
798            match (self, rand::random::<bool>()) {
799                (Self::Value(x), true) => Self::Pruned(x.digest()),
800                (Self::Value(x), false) => x
801                    .as_ref()
802                    .map(|o| Output {
803                        journal: o.journal.rand_prune(),
804                        assumptions: o.assumptions.rand_prune(),
805                    })
806                    .into(),
807                (Self::Pruned(x), _) => Self::Pruned(*x),
808            }
809        }
810    }
811
812    impl RandPrune for MaybePruned<Vec<u8>> {
813        fn rand_prune(&self) -> Self {
814            match (self, rand::random::<bool>()) {
815                (Self::Value(x), true) => Self::Pruned(x.digest()),
816                (Self::Value(x), false) => x.clone().into(),
817                (Self::Pruned(x), _) => Self::Pruned(*x),
818            }
819        }
820    }
821
822    impl RandPrune for MaybePruned<Assumptions> {
823        fn rand_prune(&self) -> Self {
824            match (self, rand::random::<bool>()) {
825                (Self::Value(x), true) => Self::Pruned(x.digest()),
826                (Self::Value(x), false) => x.clone().into(),
827                (Self::Pruned(x), _) => Self::Pruned(*x),
828            }
829        }
830    }
831
832    #[test]
833    fn merge_receipt_claim() {
834        let claim = MaybePruned::Value(ReceiptClaim {
835            pre: SystemState {
836                pc: 2100484,
837                merkle_root: Digest::from_hex(
838                    "9095da07d84ccc170c5113e3dafdf0531700f0b3f0c627acc9f0329440d984fa",
839                )
840                .unwrap(),
841            }
842            .into(),
843            post: SystemState {
844                pc: 2297164,
845                merkle_root: Digest::from_hex(
846                    "223651656250c0cf2f1c3f8923ef3d2c8624a361830492ffec6450e1930fb07d",
847                )
848                .unwrap(),
849            }
850            .into(),
851            exit_code: ExitCode::Halted(0),
852            input: None.into(),
853            output: MaybePruned::Value(Some(Output {
854                journal: MaybePruned::Value(b"hello world".to_vec()),
855                assumptions: MaybePruned::Value(Assumptions(vec![
856                    MaybePruned::Pruned(Digest::ZERO),
857                    MaybePruned::Pruned(Digest::ZERO),
858                ])),
859            })),
860        });
861
862        // Run the test to 10k times to reach every combination with high probability.
863        for _ in 0..10000 {
864            let left = claim.rand_prune();
865            let right = claim.rand_prune();
866
867            assert_eq!(left.merge(&right).unwrap().digest(), claim.digest());
868        }
869    }
870}