risc0_zkvm/guest/env/
verify.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
15use core::{convert::Infallible, fmt};
16
17use bytemuck::Pod;
18use risc0_zkp::core::digest::Digest;
19use risc0_zkvm_platform::syscall::sys_verify_integrity;
20#[cfg(feature = "unstable")]
21use risc0_zkvm_platform::syscall::sys_verify_integrity2;
22
23use crate::{sha::Digestible, Assumption, MaybePruned, PrunedValueError, ReceiptClaim};
24
25use super::ASSUMPTIONS_DIGEST;
26
27/// Verify there exists a receipt for an execution with `image_id` and `journal`.
28///
29/// Calling this function in the guest is logically equivalent to verifying a receipt with the same
30/// image ID and journal. Any party verifying the receipt produced by this execution can then be
31/// sure that the receipt verified by this call is also valid. In this way, multiple receipts from
32/// potentially distinct guests can be combined into one. This feature is know as [composition].
33///
34/// In order to be valid, the [crate::Receipt] must have [ExitCode::Halted(0)][crate::ExitCode] or
35/// [ExitCode::Paused(0)][crate::ExitCode], an empty assumptions list, and an all-zeroes input
36/// hash. It may have any post [crate::SystemState].
37///
38/// # Example
39///
40/// ```rust,ignore
41/// use risc0_zkvm::guest::env;
42///
43/// # let HELLO_WORLD_ID = Digest::ZERO;
44/// env::verify(HELLO_WORLD_ID, b"hello world".as_slice()).unwrap();
45/// ```
46///
47/// [composition]: https://dev.risczero.com/terminology#composition
48pub fn verify(image_id: impl Into<Digest>, journal: &[impl Pod]) -> Result<(), Infallible> {
49    let journal_digest: Digest = bytemuck::cast_slice::<_, u8>(journal).digest();
50    let assumption_claim = ReceiptClaim::ok(image_id, MaybePruned::Pruned(journal_digest));
51
52    let claim_digest = assumption_claim.digest();
53
54    unsafe {
55        // Use the zero digest as the control root, which indicates that the assumption is a zkVM
56        // assumption to be verified with the same control root as the current execution.
57        sys_verify_integrity(claim_digest.as_ref(), Digest::ZERO.as_ref());
58        #[allow(static_mut_refs)]
59        ASSUMPTIONS_DIGEST.add(
60            Assumption {
61                claim: claim_digest,
62                control_root: Digest::ZERO,
63            }
64            .into(),
65        );
66    }
67
68    Ok(())
69}
70
71/// Verify that there exists a valid receipt with the specified [ReceiptClaim][crate::ReceiptClaim].
72///
73/// Calling this function in the guest is logically equivalent to verifying a receipt with the same
74/// [ReceiptClaim][crate::ReceiptClaim]. Any party verifying the receipt produced by this execution
75/// can then be sure that the receipt verified by this call is also valid. In this way, multiple
76/// receipts from  potentially distinct guests can be combined into one. This feature is known as
77/// [composition].
78///
79/// In order for a receipt to be valid, it must have a verifying cryptographic seal and
80/// additionally have no assumptions. Note that executions with no output (e.g. those ending in
81/// [ExitCode::SystemSplit][crate::ExitCode]) will not have any encoded assumptions even if [verify] or
82/// [verify_integrity] is called and these receipts will be rejected by this function.
83///
84/// [composition]: https://dev.risczero.com/terminology#composition
85pub fn verify_integrity(claim: &ReceiptClaim) -> Result<(), VerifyIntegrityError> {
86    // Check that the assumptions list is empty.
87    #[allow(clippy::unnecessary_map_or)]
88    let assumptions_empty = claim
89        .output
90        .as_value()?
91        .as_ref()
92        .map_or(true, |output| output.assumptions.is_empty());
93
94    if !assumptions_empty {
95        return Err(VerifyIntegrityError::NonEmptyAssumptionsList);
96    }
97
98    let claim_digest = claim.digest();
99
100    unsafe {
101        // Use the zero digest as the control root, which indicates that the assumption is a zkVM
102        // assumption to be verified with the same control root as the current execution.
103        sys_verify_integrity(claim_digest.as_ref(), Digest::ZERO.as_ref());
104        #[allow(static_mut_refs)]
105        ASSUMPTIONS_DIGEST.add(
106            Assumption {
107                claim: claim_digest,
108                control_root: Digest::ZERO,
109            }
110            .into(),
111        );
112    }
113
114    Ok(())
115}
116
117/// Error encountered during a call to [verify_integrity].
118///
119/// Note that an error is only returned for "provable" errors. In particular, if the host fails to
120/// find a receipt matching the requested claim digest, this is not a provable error. In this
121/// case, [verify_integrity] will not return.
122#[derive(Debug)]
123#[non_exhaustive]
124pub enum VerifyIntegrityError {
125    /// Provided [crate::ReceiptClaim] struct contained a non-empty assumptions list.
126    ///
127    /// This is a semantic error as only unconditional receipts can be verified
128    /// inside the guest. If there is a conditional receipt to verify, it's
129    /// assumptions must first be verified to make the receipt
130    /// unconditional.
131    NonEmptyAssumptionsList,
132
133    /// Metadata output was pruned and not equal to the zero hash. It is
134    /// impossible to determine whether the assumptions list is empty.
135    PrunedValueError(PrunedValueError),
136}
137
138impl From<PrunedValueError> for VerifyIntegrityError {
139    fn from(err: PrunedValueError) -> Self {
140        Self::PrunedValueError(err)
141    }
142}
143
144impl fmt::Display for VerifyIntegrityError {
145    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
146        match self {
147            VerifyIntegrityError::NonEmptyAssumptionsList => {
148                write!(f, "assumptions list is not empty")
149            }
150            VerifyIntegrityError::PrunedValueError(err) => {
151                write!(f, "claim output is pruned and non-zero: {}", err.0)
152            }
153        }
154    }
155}
156
157#[cfg(feature = "std")]
158impl std::error::Error for VerifyIntegrityError {}
159
160/// Verify that there exists a valid receipt with the specified claim digest and control root.
161///
162/// This function is a generalization of [verify] and [verify_integrity] to allow verification of
163/// any claim, including claims that are not claims of zkVM execution. It can be used to verify a
164/// receipt for accelerators, such as a specialized RSA verifier. The given control root is used as
165/// a commitment to the set of recursion programs allowed to resolve the resulting assumption.
166///
167/// Do not use this method if you are looking to verify a zkVM receipt. Use [verify] or
168/// [verify_integrity] instead. This method does not check anything about the claim. In the case of
169/// zkVM execution, it is important to check that e.g. there are no assumptions on the claim.
170pub fn verify_assumption(claim: Digest, control_root: Digest) -> Result<(), Infallible> {
171    unsafe {
172        sys_verify_integrity(claim.as_ref(), control_root.as_ref());
173        #[allow(static_mut_refs)]
174        ASSUMPTIONS_DIGEST.add(
175            Assumption {
176                claim,
177                control_root,
178            }
179            .into(),
180        );
181    }
182
183    Ok(())
184}
185
186/// TODO
187#[cfg(feature = "unstable")]
188pub fn verify_assumption2(claim: Digest, control_root: Digest) -> Result<(), Infallible> {
189    unsafe {
190        sys_verify_integrity2(claim.as_ref(), control_root.as_ref());
191        #[allow(static_mut_refs)]
192        ASSUMPTIONS_DIGEST.add(
193            Assumption {
194                claim,
195                control_root,
196            }
197            .into(),
198        );
199    }
200
201    Ok(())
202}