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