risc0_zkvm/claim/
maybe_pruned.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//! The [MaybePruned] type and related traits, supporting Merkle-ized structs.
16
17use core::fmt;
18
19use borsh::{BorshDeserialize, BorshSerialize};
20use derive_more::Debug;
21use risc0_binfmt::Digestible;
22use risc0_zkp::core::digest::Digest;
23use serde::{Deserialize, Serialize};
24
25use crate::sha::{self, Sha256};
26
27/// Either a source value or a hash [Digest] of the source value.
28///
29/// This type supports creating "Merkle-ized structs". Each field of a Merkle-ized struct can have
30/// either the full value, or it can be "pruned" and replaced with a digest committing to that
31/// value. One way to think of this is as a special Merkle tree of a predefined shape. Each field
32/// is a child node. Any field/node in the tree can be opened by providing the Merkle inclusion
33/// proof. When a subtree is pruned, the digest commits to the value of all contained fields.
34/// [ReceiptClaim][crate::ReceiptClaim] is the motivating example of this type of Merkle-ized struct.
35#[derive(Clone, Deserialize, Serialize, BorshSerialize, BorshDeserialize)]
36pub enum MaybePruned<T> {
37    /// Unpruned value.
38    Value(T),
39
40    /// Pruned value, which is a hash [Digest] of the value.
41    Pruned(Digest),
42}
43
44impl<T> MaybePruned<T> {
45    /// Unwrap the value, or return an error.
46    pub fn value(self) -> Result<T, PrunedValueError> {
47        match self {
48            MaybePruned::Value(value) => Ok(value),
49            MaybePruned::Pruned(digest) => Err(PrunedValueError(digest)),
50        }
51    }
52
53    /// Unwrap the value as a reference, or return an error.
54    pub fn as_value(&self) -> Result<&T, PrunedValueError> {
55        match self {
56            MaybePruned::Value(ref value) => Ok(value),
57            MaybePruned::Pruned(ref digest) => Err(PrunedValueError(*digest)),
58        }
59    }
60
61    /// Unwrap the value as a mutable reference, or return an error.
62    pub fn as_value_mut(&mut self) -> Result<&mut T, PrunedValueError> {
63        match self {
64            MaybePruned::Value(ref mut value) => Ok(value),
65            MaybePruned::Pruned(ref digest) => Err(PrunedValueError(*digest)),
66        }
67    }
68}
69
70impl<T> From<T> for MaybePruned<T> {
71    fn from(value: T) -> Self {
72        Self::Value(value)
73    }
74}
75
76impl<T> Digestible for MaybePruned<T>
77where
78    T: Digestible,
79{
80    fn digest<S: Sha256>(&self) -> Digest {
81        match self {
82            MaybePruned::Value(ref val) => val.digest::<S>(),
83            MaybePruned::Pruned(digest) => *digest,
84        }
85    }
86}
87
88impl<T> Default for MaybePruned<T>
89where
90    T: Digestible + Default,
91{
92    fn default() -> Self {
93        MaybePruned::Value(Default::default())
94    }
95}
96
97impl<T> MaybePruned<Option<T>> {
98    /// Returns true is the value is None, or the value is pruned as the zero
99    /// digest.
100    pub fn is_none(&self) -> bool {
101        match self {
102            MaybePruned::Value(Some(_)) => false,
103            MaybePruned::Value(None) => true,
104            MaybePruned::Pruned(digest) => digest == &Digest::ZERO,
105        }
106    }
107
108    /// Returns true is the value is Some(_), or the value is pruned as a
109    /// non-zero digest.
110    pub fn is_some(&self) -> bool {
111        !self.is_none()
112    }
113}
114
115#[cfg(test)]
116impl<T> PartialEq for MaybePruned<T>
117where
118    T: PartialEq,
119{
120    fn eq(&self, other: &Self) -> bool {
121        match (self, other) {
122            (Self::Value(a), Self::Value(b)) => a == b,
123            (Self::Pruned(a), Self::Pruned(b)) => a == b,
124            _ => false,
125        }
126    }
127}
128
129impl<T> fmt::Debug for MaybePruned<T>
130where
131    T: Digestible + fmt::Debug,
132{
133    /// Format [MaybePruned] values are if they were a struct with value and
134    /// digest fields. Digest field is always provided so that divergent
135    /// trees of [MaybePruned] values can be compared.
136    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
137        let mut builder = fmt.debug_struct("MaybePruned");
138        if let MaybePruned::Value(value) = self {
139            builder.field("value", value);
140        }
141        builder
142            .field("digest", &self.digest::<sha::Impl>())
143            .finish()
144    }
145}
146
147/// Error returned when the source value was pruned, and is not available.
148#[derive(Debug, Clone)]
149pub struct PrunedValueError(pub Digest);
150
151impl fmt::Display for PrunedValueError {
152    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
153        write!(f, "value is pruned: {}", &self.0)
154    }
155}
156
157#[cfg(feature = "std")]
158impl std::error::Error for PrunedValueError {}