risc0_zkvm/
receipt_claim.rs

1// Copyright 2025 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, 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// TODO(victor): Add functions to handle the `ReceiptClaim` transformations conducted as part of
43// join, resolve, and eventually resume calls. This will allow these to be used for recursion, as
44// well as dev mode recursion, and composite receipts.
45
46/// Public claims about a zkVM guest execution, such as the journal committed to by the guest.
47///
48/// Also includes important information such as the exit code and the starting and ending system
49/// state (i.e. the state of memory). [ReceiptClaim] is a "Merkle-ized struct" supporting
50/// partial openings of the underlying fields from a hash commitment to the full structure. Also
51/// see [MaybePruned].
52#[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
53#[cfg_attr(test, derive(PartialEq))]
54pub struct ReceiptClaim {
55    /// The [SystemState] just before execution has begun.
56    pub pre: MaybePruned<SystemState>,
57
58    /// The [SystemState] just after execution has completed.
59    pub post: MaybePruned<SystemState>,
60
61    /// The exit code for the execution.
62    pub exit_code: ExitCode,
63
64    /// Input to the guest.
65    pub input: MaybePruned<Option<Input>>,
66
67    /// [Output] of the guest, including the journal and assumptions set during execution.
68    pub output: MaybePruned<Option<Output>>,
69}
70
71impl ReceiptClaim {
72    /// Construct a [ReceiptClaim] representing a zkVM execution that ended normally (i.e.
73    /// Halted(0)) with the given image ID and journal.
74    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    /// Construct a [ReceiptClaim] representing a zkVM execution that ended in a normal paused
95    /// state (i.e. Paused(0)) with the given image ID and journal.
96    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    /// Decode a [ReceiptClaim] from a list of [u32]'s
117    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    /// Encode a [ReceiptClaim] to a list of [u32]'s
140    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        // TODO(flaub): implement this once shutdownCycle is supported in rv32im-v2 circuit
159        // if let Some(po2) = po2 {
160        //     let segment_threshold = (1 << po2) - MAX_INSN_CYCLES;
161        //     ensure!(claim.shutdown_cycle.unwrap() == segment_threshold as u32);
162        // }
163
164        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    /// Hash the [ReceiptClaim] to get a digest of the struct.
202    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/// Error returned when decoding [ReceiptClaim] fails.
218#[derive(Debug, Copy, Clone)]
219pub enum DecodeError {
220    /// Decoding failure due to an invalid exit code.
221    InvalidExitCode(InvalidExitCodeError),
222    /// Decoding failure due to an inner decoding failure.
223    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/// A type representing an unknown claim type.
251///
252/// A receipt (e.g. [SuccinctReceipt][crate::SuccinctReceipt]) may have an unknown claim type when
253/// only the digest of the claim is needed, and the full claim value cannot be determined by the
254/// compiler. This allows for a collection of receipts to be created even when the underlying
255/// claims are of heterogeneous types (e.g. `Vec<SuccinctReceipt<Unknown>>`).
256///
257/// Note that this is an uninhabited type, similar to the [never type].
258///
259/// [never type]: https://doc.rust-lang.org/std/primitive.never.html
260#[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 { /* unreachable  */ }
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/// Each UnionClaim can be used as an inner node in a Merkle mountain
283/// accumulator, the root of which commits to a set of claims.
284#[derive(Debug, Clone, Serialize, Deserialize)]
285pub struct UnionClaim {
286    /// Digest of the "left" Assumption struct.
287    ///
288    /// The left should always be lesser of the two when interpreting the digest as a big-endian number.
289    pub left: Digest,
290    /// Digest of the "right" Assumption struct.
291    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/// Input field in the [ReceiptClaim], committing to a public value accessible to the guest.
301///
302/// NOTE: This type is currently uninhabited (i.e. it cannot be constructed), and only its digest
303/// is accessible. It may become inhabited in a future release.
304#[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
305#[cfg_attr(test, derive(PartialEq))]
306pub struct Input {
307    // Private field to ensure this type cannot be constructed.
308    // By making this type uninhabited, it can be populated later without breaking backwards
309    // compatibility.
310    pub(crate) x: Unknown,
311}
312
313impl Digestible for Input {
314    /// Hash the [Input] to get a digest of the struct.
315    fn digest<S: Sha256>(&self) -> Digest {
316        match self.x { /* unreachable  */ }
317    }
318}
319
320/// Output field in the [ReceiptClaim], committing to a claimed journal and assumptions list.
321#[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
322#[cfg_attr(test, derive(PartialEq))]
323pub struct Output {
324    /// The journal committed to by the guest execution.
325    #[debug("{}", fmt_debug_journal(journal))]
326    pub journal: MaybePruned<Vec<u8>>,
327
328    /// An ordered list of [ReceiptClaim] digests corresponding to the
329    /// calls to `env::verify` and `env::verify_integrity`.
330    ///
331    /// Verifying the integrity of a [crate::Receipt] corresponding to a [ReceiptClaim] with a
332    /// non-empty assumptions list does not guarantee unconditionally any of the claims over the
333    /// guest execution (i.e. if the assumptions list is non-empty, then the journal digest cannot
334    /// be trusted to correspond to a genuine execution). The claims can be checked by additional
335    /// verifying a [crate::Receipt] for every digest in the assumptions list.
336    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    /// Hash the [Output] to get a digest of the struct.
349    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/// An [assumption] made in the course of proving program execution.
359///
360/// Assumptions are generated when the guest makes a recursive verification call. Each assumption
361/// commits the statement, such that only a receipt proving that statement can be used to resolve
362/// and remove the assumption.
363///
364/// [assumption]: https://dev.risczero.com/terminology#assumption
365#[derive(
366    Clone, Debug, Serialize, Deserialize, Eq, Hash, PartialEq, BorshSerialize, BorshDeserialize,
367)]
368pub struct Assumption {
369    /// Commitment to the assumption claim. It may be the digest of a [ReceiptClaim], or it could
370    /// be the digest of the claim for a different circuit such as an accelerator.
371    pub claim: Digest,
372
373    /// Commitment to the set of [recursion programs] that can be used to resolve this assumption.
374    ///
375    /// Binding the set of recursion programs also binds the circuits, and creates an assumption
376    /// resolved by independent set of circuits (e.g. keccak or Groth16 verify). Proofs of these
377    /// external claims are verified by a "lift" program implemented for the recursion VM which
378    /// brings the claim into the recursion system. This lift program is committed to in the
379    /// control root.
380    ///
381    /// A special value of all zeroes indicates "self-composition", where the control root used to
382    /// verify this claim is also used to verify the assumption.
383    ///
384    /// [recursion programs]: https://dev.risczero.com/terminology#recursion-program
385    pub control_root: Digest,
386}
387
388impl Digestible for Assumption {
389    /// Hash the [Assumption] to get a digest of the struct.
390    fn digest<S: Sha256>(&self) -> Digest {
391        tagged_struct::<S>("risc0.Assumption", &[self.claim, self.control_root], &[])
392    }
393}
394
395/// A list of assumptions, each a [Digest] or populated value of an [Assumption].
396#[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    /// Add an assumption to the head of the assumptions list.
402    pub fn add(&mut self, assumption: MaybePruned<Assumption>) {
403        self.0.insert(0, assumption);
404    }
405
406    /// Mark an assumption as resolved and remove it from the list.
407    ///
408    /// Assumptions can only be removed from the head of the list.
409    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        // Drop the head of the assumptions list.
423        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    /// Hash the [Assumptions] to get a digest of the struct.
438    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    /// Check if the (possibly pruned) assumptions list is empty.
448    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    /// Add an assumption to the head of the assumptions list.
456    ///
457    /// If this value is pruned, then the result will also be a pruned value.
458    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    /// Mark an assumption as resolved and remove it from the list.
472    ///
473    /// Assumptions can only be removed from the head of the list. If this value
474    /// is pruned, then the result will also be a pruned value. The `tail`
475    /// parameter should be equal to the digest of the list after the
476    /// resolved assumption is removed.
477    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                // Set the pruned digest value to be equal to the rest parameter.
491                *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/// Either a source value or a hash [Digest] of the source value.
517///
518/// This type supports creating "Merkle-ized structs". Each field of a Merkle-ized struct can have
519/// either the full value, or it can be "pruned" and replaced with a digest committing to that
520/// value. One way to think of this is as a special Merkle tree of a predefined shape. Each field
521/// is a child node. Any field/node in the tree can be opened by providing the Merkle inclusion
522/// proof. When a subtree is pruned, the digest commits to the value of all contained fields.
523/// [ReceiptClaim] is the motivating example of this type of Merkle-ized struct.
524#[derive(Clone, Deserialize, Serialize, BorshSerialize, BorshDeserialize)]
525pub enum MaybePruned<T>
526where
527    T: Clone + Serialize,
528{
529    /// Unpruned value.
530    Value(T),
531
532    /// Pruned value, which is a hash [Digest] of the value.
533    Pruned(Digest),
534}
535
536impl<T> MaybePruned<T>
537where
538    T: Clone + Serialize,
539{
540    /// Unwrap the value, or return an error.
541    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    /// Unwrap the value as a reference, or return an error.
549    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    /// Unwrap the value as a mutable reference, or return an error.
557    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    /// Returns true is the value is None, or the value is pruned as the zero
600    /// digest.
601    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    /// Returns true is the value is Some(_), or the value is pruned as a
610    /// non-zero digest.
611    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    /// Format [MaybePruned] values are if they were a struct with value and
635    /// digest fields. Digest field is always provided so that divergent
636    /// trees of [MaybePruned] values can be compared.
637    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/// Error returned when the source value was pruned, and is not available.
649#[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/// Merge two structures containing [MaybePruned] fields to produce a resulting structure with
662/// populated fields equal to the union of the two.
663///
664/// Viewing the two structs as Merkle trees, in which subtrees may be pruned, the result of this
665/// operation is a tree with a set of nodes equal to the union of the set of nodes for each input.
666#[cfg(feature = "prove")]
667pub(crate) trait Merge: Digestible + Sized {
668    /// Merge two structs to produce an output with a union of the fields populated in the inputs.
669    fn merge(&self, other: &Self) -> Result<Self, MergeInequalityError>;
670
671    /// Merge two structs to assigning self as the union of the fields populated in the two inputs.
672    fn merge_with(&mut self, other: &Self) -> Result<(), MergeInequalityError> {
673        // Not a very efficient implementation.
674        *self = self.merge(other)?;
675        Ok(())
676    }
677}
678
679/// Error returned when a merge it attempted with two values with unequal digests.
680#[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/// Private marker trait providing an implementation of merge to values which implement PartialEq and clone and do not contain Merge fields.
700#[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 { /* unreachable  */ }
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    /// Testing utility for randomly pruning structs.
839    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        // Run the test to 10k times to reach every combination with high probability.
941        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}