risc0_zkvm/receipt/
composite.rs1use alloc::{vec, vec::Vec};
16use core::fmt::Debug;
17
18use anyhow::Result;
19use borsh::{BorshDeserialize, BorshSerialize};
20use risc0_binfmt::{tagged_struct, Digestible, ExitCode};
21use risc0_circuit_recursion::CircuitImpl;
22use risc0_zkp::{
23 adapter::{CircuitInfo, PROOF_SYSTEM_INFO},
24 core::{digest::Digest, hash::sha::Sha256},
25 verify::VerificationError,
26};
27use serde::{Deserialize, Serialize};
28
29use super::{
31 Groth16ReceiptVerifierParameters, SegmentReceipt, SegmentReceiptVerifierParameters,
32 SuccinctReceiptVerifierParameters, VerifierContext,
33};
34use crate::{
35 sha, Assumption, InnerAssumptionReceipt, MaybePruned, Output, PrunedValueError, ReceiptClaim,
36};
37
38#[derive(Clone, Debug, Deserialize, Serialize, BorshSerialize, BorshDeserialize)]
42#[cfg_attr(test, derive(PartialEq))]
43#[non_exhaustive]
44pub struct CompositeReceipt {
45 pub segments: Vec<SegmentReceipt>,
47
48 pub assumption_receipts: Vec<InnerAssumptionReceipt>,
54
55 pub verifier_parameters: Digest,
61}
62
63impl CompositeReceipt {
64 pub fn verify_integrity_with_context(
67 &self,
68 ctx: &VerifierContext,
69 ) -> Result<(), VerificationError> {
70 tracing::debug!("CompositeReceipt::verify_integrity_with_context");
71 let (final_receipt, receipts) = self
73 .segments
74 .as_slice()
75 .split_last()
76 .ok_or(VerificationError::ReceiptFormatError)?;
77
78 let mut expected_pre_state_digest = None;
80 for receipt in receipts {
81 receipt.verify_integrity_with_context(ctx)?;
82 tracing::debug!("claim: {:#?}", receipt.claim);
83 if let Some(id) = expected_pre_state_digest {
84 if id != receipt.claim.pre.digest::<sha::Impl>() {
85 return Err(VerificationError::ImageVerificationError);
86 }
87 }
88 if receipt.claim.exit_code != ExitCode::SystemSplit {
89 return Err(VerificationError::UnexpectedExitCode);
90 }
91 if !receipt.claim.output.is_none() {
92 return Err(VerificationError::ReceiptFormatError);
93 }
94 expected_pre_state_digest = Some(
95 receipt
96 .claim
97 .post
98 .as_value()
99 .map_err(|_| VerificationError::ReceiptFormatError)?
100 .digest::<sha::Impl>(),
101 );
102 }
103
104 final_receipt.verify_integrity_with_context(ctx)?;
106 tracing::debug!("final: {:#?}", final_receipt.claim);
107 if let Some(id) = expected_pre_state_digest {
108 if id != final_receipt.claim.pre.digest::<sha::Impl>() {
109 return Err(VerificationError::ImageVerificationError);
110 }
111 }
112
113 let assumptions = self.assumptions()?;
117 if assumptions.len() != self.assumption_receipts.len() {
118 tracing::debug!(
119 "only {} receipts provided for {} assumptions",
120 assumptions.len(),
121 self.assumption_receipts.len()
122 );
123 return Err(VerificationError::ReceiptFormatError);
124 }
125 for (assumption, receipt) in assumptions.into_iter().zip(self.assumption_receipts.iter()) {
126 let assumption_ctx = match assumption.control_root {
127 Digest::ZERO => None,
129 control_root => Some(
131 VerifierContext::empty()
132 .with_suites(ctx.suites.clone())
133 .with_succinct_verifier_parameters(SuccinctReceiptVerifierParameters {
134 control_root,
135 inner_control_root: None,
136 proof_system_info: PROOF_SYSTEM_INFO,
137 circuit_info: CircuitImpl::CIRCUIT_INFO,
138 }),
139 ),
140 };
141 tracing::debug!("verifying assumption: {assumption:?}");
142 receipt.verify_integrity_with_context(assumption_ctx.as_ref().unwrap_or(ctx))?;
143 if receipt.claim_digest()? != assumption.claim {
144 tracing::debug!(
145 "verifying assumption failed due to claim mismatch: assumption: {assumption:?}, receipt claim digest: {}",
146 receipt.claim_digest()?
147 );
148 return Err(VerificationError::ClaimDigestMismatch {
149 expected: assumption.claim,
150 received: receipt.claim_digest()?,
151 });
152 }
153 }
154
155 Ok(())
156 }
157
158 pub fn claim(&self) -> Result<ReceiptClaim, VerificationError> {
160 let first_claim = &self
161 .segments
162 .first()
163 .ok_or(VerificationError::ReceiptFormatError)?
164 .claim;
165 let last_claim = &self
166 .segments
167 .last()
168 .ok_or(VerificationError::ReceiptFormatError)?
169 .claim;
170
171 let output = last_claim
175 .output
176 .as_value()
177 .map_err(|_| VerificationError::ReceiptFormatError)?
178 .as_ref()
179 .map(|output| Output {
180 journal: output.journal.clone(),
181 assumptions: vec![].into(),
182 })
183 .into();
184
185 Ok(ReceiptClaim {
186 pre: first_claim.pre.clone(),
187 post: last_claim.post.clone(),
188 exit_code: last_claim.exit_code,
189 input: first_claim.input.clone(),
190 output,
191 })
192 }
193
194 fn assumptions(&self) -> Result<Vec<Assumption>, VerificationError> {
195 Ok(self
198 .segments
199 .last()
200 .ok_or(VerificationError::ReceiptFormatError)?
201 .claim
202 .output
203 .as_value()
204 .map_err(|_| VerificationError::ReceiptFormatError)?
205 .as_ref()
206 .map(|output| match output.assumptions.is_empty() {
207 true => Ok(Default::default()),
208 false => Ok(output
209 .assumptions
210 .as_value()?
211 .iter()
212 .map(|a| a.as_value().cloned())
213 .collect::<Result<_, _>>()?),
214 })
215 .transpose()
216 .map_err(|_: PrunedValueError| VerificationError::ReceiptFormatError)?
217 .unwrap_or_default())
218 }
219
220 pub fn seal_size(&self) -> usize {
222 let mut result = 0;
224
225 for receipt in &self.segments {
226 result += receipt.seal_size();
227 }
228
229 for receipt in &self.assumption_receipts {
232 result += receipt.seal_size();
233 }
234
235 result
236 }
237}
238
239#[derive(Clone, Debug, Deserialize, Serialize)]
245#[non_exhaustive]
246pub struct CompositeReceiptVerifierParameters {
247 pub segment: MaybePruned<SegmentReceiptVerifierParameters>,
249
250 pub succinct: MaybePruned<SuccinctReceiptVerifierParameters>,
252
253 pub groth16: MaybePruned<Groth16ReceiptVerifierParameters>,
255}
256
257impl CompositeReceiptVerifierParameters {
258 #[stability::unstable]
262 pub fn from_max_po2(po2_max: usize) -> Self {
263 Self {
264 segment: MaybePruned::Value(SegmentReceiptVerifierParameters::default()),
265 succinct: MaybePruned::Value(SuccinctReceiptVerifierParameters::from_max_po2(po2_max)),
266 groth16: MaybePruned::Value(Groth16ReceiptVerifierParameters::from_max_po2(po2_max)),
267 }
268 }
269
270 #[stability::unstable]
273 pub fn all_po2s() -> Self {
274 Self::from_max_po2(risc0_zkp::MAX_CYCLES_PO2)
275 }
276}
277
278impl Digestible for CompositeReceiptVerifierParameters {
279 fn digest<S: Sha256>(&self) -> Digest {
281 tagged_struct::<S>(
282 "risc0.CompositeReceiptVerifierParameters",
283 &[
284 &self.segment.digest::<S>(),
285 &self.succinct.digest::<S>(),
286 &self.groth16.digest::<S>(),
287 ],
288 &[],
289 )
290 }
291}
292
293impl Default for CompositeReceiptVerifierParameters {
294 fn default() -> Self {
296 Self {
297 segment: MaybePruned::Value(Default::default()),
298 succinct: MaybePruned::Value(Default::default()),
299 groth16: MaybePruned::Value(Default::default()),
300 }
301 }
302}
303
304#[cfg(test)]
305mod tests {
306 use super::CompositeReceiptVerifierParameters;
307 use crate::sha::Digestible;
308 use risc0_zkp::core::digest::digest;
309
310 #[test]
315 fn composite_receipt_verifier_parameters_is_stable() {
316 assert_eq!(
317 CompositeReceiptVerifierParameters::default().digest(),
318 digest!("3daead8f1ec08eb96b60ce6cad42f82eba80f6cf89ba5007ca317e57256b6038")
319 );
320 }
321}