1use alloc::{collections::VecDeque, vec::Vec};
23use core::{fmt, ops::Deref};
24
25#[cfg(feature = "std")]
26use anyhow::Context;
27use anyhow::{anyhow, bail, ensure};
28use borsh::{BorshDeserialize, BorshSerialize};
29use derive_more::Debug;
30use risc0_binfmt::{
31 read_sha_halfs, tagged_list, tagged_list_cons, tagged_struct, write_sha_halfs, Digestible,
32 ExitCode, InvalidExitCodeError,
33};
34use risc0_circuit_rv32im::{HighLowU16, Rv32imV2Claim, TerminateState};
35use risc0_zkp::core::digest::Digest;
36use risc0_zkvm_platform::syscall::halt;
37use serde::{Deserialize, Serialize};
38
39use super::{
40 maybe_pruned::{MaybePruned, PrunedValueError},
41 work::WorkClaimError,
42 Unknown,
43};
44use crate::{
45 sha::{self, Sha256},
46 SystemState, WorkClaim,
47};
48
49#[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
56#[cfg_attr(test, derive(PartialEq))]
57pub struct ReceiptClaim {
58 pub pre: MaybePruned<SystemState>,
60
61 pub post: MaybePruned<SystemState>,
63
64 pub exit_code: ExitCode,
66
67 pub input: MaybePruned<Option<Input>>,
69
70 pub output: MaybePruned<Option<Output>>,
72}
73
74impl ReceiptClaim {
75 pub fn ok(
78 image_id: impl Into<Digest>,
79 journal: impl Into<MaybePruned<Vec<u8>>>,
80 ) -> ReceiptClaim {
81 Self {
82 pre: MaybePruned::Pruned(image_id.into()),
83 post: MaybePruned::Value(SystemState {
84 pc: 0,
85 merkle_root: Digest::ZERO,
86 }),
87 exit_code: ExitCode::Halted(0),
88 input: None.into(),
89 output: Some(Output {
90 journal: journal.into(),
91 assumptions: MaybePruned::Pruned(Digest::ZERO),
92 })
93 .into(),
94 }
95 }
96
97 pub fn paused(
100 image_id: impl Into<Digest>,
101 journal: impl Into<MaybePruned<Vec<u8>>>,
102 ) -> ReceiptClaim {
103 Self {
104 pre: MaybePruned::Pruned(image_id.into()),
105 post: MaybePruned::Value(SystemState {
106 pc: 0,
107 merkle_root: Digest::ZERO,
108 }),
109 exit_code: ExitCode::Paused(0),
110 input: None.into(),
111 output: Some(Output {
112 journal: journal.into(),
113 assumptions: MaybePruned::Pruned(Digest::ZERO),
114 })
115 .into(),
116 }
117 }
118
119 pub fn decode(flat: &mut VecDeque<u32>) -> Result<Self, DecodeError> {
121 let input = read_sha_halfs(flat)?;
122 let pre = SystemState::decode(flat)?;
123 let post = SystemState::decode(flat)?;
124 let sys_exit = flat
125 .pop_front()
126 .ok_or(risc0_binfmt::DecodeError::EndOfStream)?;
127 let user_exit = flat
128 .pop_front()
129 .ok_or(risc0_binfmt::DecodeError::EndOfStream)?;
130 let exit_code = ExitCode::from_pair(sys_exit, user_exit)?;
131 let output = read_sha_halfs(flat)?;
132
133 Ok(Self {
134 input: MaybePruned::Pruned(input),
135 pre: pre.into(),
136 post: post.into(),
137 exit_code,
138 output: MaybePruned::Pruned(output),
139 })
140 }
141
142 pub fn encode(&self, flat: &mut Vec<u32>) -> Result<(), PrunedValueError> {
144 write_sha_halfs(flat, &self.input.digest::<sha::Impl>());
145 self.pre.as_value()?.encode(flat);
146 self.post.as_value()?.encode(flat);
147 let (sys_exit, user_exit) = self.exit_code.into_pair();
148 flat.push(sys_exit);
149 flat.push(user_exit);
150 write_sha_halfs(flat, &self.output.digest::<sha::Impl>());
151 Ok(())
152 }
153
154 pub(crate) fn decode_from_seal_v2(
155 seal: &[u32],
156 _po2: Option<u32>,
157 ) -> anyhow::Result<ReceiptClaim> {
158 let claim = Rv32imV2Claim::decode(seal)?;
159 tracing::debug!("claim: {claim:#?}");
160
161 let exit_code = exit_code_from_terminate_state(&claim.terminate_state)?;
168 let post_state = match exit_code {
169 ExitCode::Halted(_) => Digest::ZERO,
170 _ => claim.post_state,
171 };
172
173 Ok(ReceiptClaim {
174 pre: MaybePruned::Value(SystemState {
175 pc: 0,
176 merkle_root: claim.pre_state,
177 }),
178 post: MaybePruned::Value(SystemState {
179 pc: 0,
180 merkle_root: post_state,
181 }),
182 exit_code,
183 input: MaybePruned::Pruned(claim.input),
184 output: MaybePruned::Pruned(claim.output.unwrap_or_default()),
185 })
186 }
187
188 pub fn join(&self, other: &Self) -> Self {
191 ReceiptClaim {
192 pre: self.pre.clone(),
193 post: other.post.clone(),
194 exit_code: other.exit_code,
195 input: self.input.clone(),
196 output: other.output.clone(),
197 }
198 }
199
200 #[cfg(feature = "std")]
204 pub fn resolve<Claim: risc0_binfmt::Digestible + ?Sized>(
205 &self,
206 assumption: &Claim,
207 ) -> anyhow::Result<Self> {
208 let mut resolved_claim = self.clone();
209
210 let assumptions: &mut Assumptions = resolved_claim
212 .output
213 .as_value_mut()
214 .context("conditional receipt output is pruned")?
215 .as_mut()
216 .ok_or_else(|| anyhow!("conditional receipt has empty output and no assumptions"))?
217 .assumptions
218 .as_value_mut()
219 .context("conditional receipt has pruned assumptions")?;
220
221 let head_control_root = assumptions
225 .first()
226 .context("assumptions list is empty")?
227 .as_value()?
228 .control_root;
229
230 assumptions.resolve(
232 &Assumption {
233 control_root: head_control_root,
234 claim: assumption.digest::<sha::Impl>(),
235 }
236 .digest::<sha::Impl>(),
237 )?;
238
239 Ok(resolved_claim)
240 }
241}
242
243impl MaybePruned<ReceiptClaim> {
244 pub fn join(&self, other: &Self) -> Result<Self, PrunedValueError> {
247 Ok(self.as_value()?.join(other.as_value()?).into())
248 }
249
250 #[cfg(feature = "std")]
254 pub fn resolve<Claim: risc0_binfmt::Digestible + ?Sized>(
255 &self,
256 assumption: &Claim,
257 ) -> anyhow::Result<Self> {
258 Ok(self
259 .as_value()
260 .context("conditional claim is pruned")?
261 .resolve(assumption)?
262 .into())
263 }
264}
265
266impl WorkClaim<ReceiptClaim> {
267 pub fn join(&self, other: &Self) -> Result<Self, WorkClaimError> {
270 Ok(Self {
271 claim: self.claim.join(&other.claim)?,
272 work: self.work.join(&other.work)?,
273 })
274 }
275
276 #[cfg(feature = "std")]
278 pub fn resolve<Claim: risc0_binfmt::Digestible + ?Sized>(
279 &self,
280 assumption: &Claim,
281 ) -> anyhow::Result<Self> {
282 Ok(Self {
283 claim: self.claim.resolve(assumption)?,
284 work: self.work.clone(),
285 })
286 }
287}
288
289impl MaybePruned<WorkClaim<ReceiptClaim>> {
290 pub fn join(&self, other: &Self) -> Result<Self, WorkClaimError> {
293 Ok(self.as_value()?.join(other.as_value()?)?.into())
294 }
295
296 #[cfg(feature = "std")]
298 pub fn resolve<Claim: risc0_binfmt::Digestible + ?Sized>(
299 &self,
300 assumption: &Claim,
301 ) -> anyhow::Result<Self> {
302 Ok(self
303 .as_value()
304 .context("conditional povw claim is pruned")?
305 .resolve(assumption)?
306 .into())
307 }
308}
309
310pub(crate) fn exit_code_from_terminate_state(
311 terminate_state: &Option<TerminateState>,
312) -> anyhow::Result<ExitCode> {
313 let exit_code = if let Some(term) = terminate_state {
314 let HighLowU16(user_exit, halt_type) = term.a0;
315 match halt_type as u32 {
316 halt::TERMINATE => ExitCode::Halted(user_exit as u32),
317 halt::PAUSE => ExitCode::Paused(user_exit as u32),
318 _ => bail!("Illegal halt type: {halt_type}"),
319 }
320 } else {
321 ExitCode::SystemSplit
322 };
323 Ok(exit_code)
324}
325
326impl Digestible for ReceiptClaim {
327 fn digest<S: Sha256>(&self) -> Digest {
329 let (sys_exit, user_exit) = self.exit_code.into_pair();
330 tagged_struct::<S>(
331 "risc0.ReceiptClaim",
332 &[
333 self.input.digest::<S>(),
334 self.pre.digest::<S>(),
335 self.post.digest::<S>(),
336 self.output.digest::<S>(),
337 ],
338 &[sys_exit, user_exit],
339 )
340 }
341}
342
343#[derive(Debug, Copy, Clone)]
345pub enum DecodeError {
346 InvalidExitCode(InvalidExitCodeError),
348 Decode(risc0_binfmt::DecodeError),
350}
351
352impl fmt::Display for DecodeError {
353 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
354 match self {
355 Self::InvalidExitCode(e) => write!(f, "failed to decode receipt claim: {e}"),
356 Self::Decode(e) => write!(f, "failed to decode receipt claim: {e}"),
357 }
358 }
359}
360
361impl From<risc0_binfmt::DecodeError> for DecodeError {
362 fn from(e: risc0_binfmt::DecodeError) -> Self {
363 Self::Decode(e)
364 }
365}
366
367impl From<InvalidExitCodeError> for DecodeError {
368 fn from(e: InvalidExitCodeError) -> Self {
369 Self::InvalidExitCode(e)
370 }
371}
372
373#[cfg(feature = "std")]
374impl std::error::Error for DecodeError {}
375
376#[derive(Debug, Clone, Serialize, Deserialize)]
379pub struct UnionClaim {
380 pub left: Digest,
384 pub right: Digest,
386}
387
388impl Digestible for UnionClaim {
389 fn digest<S: Sha256>(&self) -> Digest {
390 tagged_struct::<S>("risc0.UnionClaim", &[self.left, self.right], &[])
391 }
392}
393
394#[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
399#[cfg_attr(test, derive(PartialEq))]
400pub struct Input {
401 pub(crate) x: Unknown,
405}
406
407impl Digestible for Input {
408 fn digest<S: Sha256>(&self) -> Digest {
410 match self.x { }
411 }
412}
413
414#[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
416#[cfg_attr(test, derive(PartialEq))]
417pub struct Output {
418 #[debug("{}", fmt_debug_journal(journal))]
420 pub journal: MaybePruned<Vec<u8>>,
421
422 pub assumptions: MaybePruned<Assumptions>,
431}
432
433#[allow(dead_code)]
434fn fmt_debug_journal(journal: &MaybePruned<Vec<u8>>) -> alloc::string::String {
435 match journal {
436 MaybePruned::Value(bytes) => alloc::format!("{} bytes", bytes.len()),
437 MaybePruned::Pruned(_) => alloc::format!("{journal:?}"),
438 }
439}
440
441impl Digestible for Output {
442 fn digest<S: Sha256>(&self) -> Digest {
444 tagged_struct::<S>(
445 "risc0.Output",
446 &[self.journal.digest::<S>(), self.assumptions.digest::<S>()],
447 &[],
448 )
449 }
450}
451
452#[derive(
460 Clone, Debug, Serialize, Deserialize, Eq, Hash, PartialEq, BorshSerialize, BorshDeserialize,
461)]
462pub struct Assumption {
463 pub claim: Digest,
466
467 pub control_root: Digest,
480}
481
482impl Digestible for Assumption {
483 fn digest<S: Sha256>(&self) -> Digest {
485 tagged_struct::<S>("risc0.Assumption", &[self.claim, self.control_root], &[])
486 }
487}
488
489#[derive(Clone, Default, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
491#[cfg_attr(test, derive(PartialEq))]
492pub struct Assumptions(pub Vec<MaybePruned<Assumption>>);
493
494impl Assumptions {
495 pub fn add(&mut self, assumption: MaybePruned<Assumption>) {
497 self.0.insert(0, assumption);
498 }
499
500 pub fn resolve(&mut self, resolved: &Digest) -> anyhow::Result<()> {
504 let head = self
505 .0
506 .first()
507 .ok_or_else(|| anyhow!("cannot resolve assumption from empty list"))?;
508
509 ensure!(
510 &head.digest::<sha::Impl>() == resolved,
511 "resolved assumption is not equal to the head of the list: {} != {}",
512 resolved,
513 head.digest::<sha::Impl>()
514 );
515
516 self.0 = self.0.split_off(1);
518 Ok(())
519 }
520}
521
522impl Deref for Assumptions {
523 type Target = [MaybePruned<Assumption>];
524
525 fn deref(&self) -> &Self::Target {
526 &self.0
527 }
528}
529
530impl Digestible for Assumptions {
531 fn digest<S: Sha256>(&self) -> Digest {
533 tagged_list::<S>(
534 "risc0.Assumptions",
535 &self.0.iter().map(|a| a.digest::<S>()).collect::<Vec<_>>(),
536 )
537 }
538}
539
540impl MaybePruned<Assumptions> {
541 pub fn is_empty(&self) -> bool {
543 match self {
544 MaybePruned::Value(list) => list.is_empty(),
545 MaybePruned::Pruned(digest) => digest == &Digest::ZERO,
546 }
547 }
548
549 pub fn add(&mut self, assumption: MaybePruned<Assumption>) {
553 match self {
554 MaybePruned::Value(list) => list.add(assumption),
555 MaybePruned::Pruned(list_digest) => {
556 *list_digest = tagged_list_cons::<sha::Impl>(
557 "risc0.Assumptions",
558 &assumption.digest::<sha::Impl>(),
559 &*list_digest,
560 );
561 }
562 }
563 }
564
565 pub fn resolve(&mut self, resolved: &Digest, tail: &Digest) -> anyhow::Result<()> {
572 match self {
573 MaybePruned::Value(list) => list.resolve(resolved),
574 MaybePruned::Pruned(list_digest) => {
575 let reconstructed =
576 tagged_list_cons::<sha::Impl>("risc0.Assumptions", resolved, tail);
577 ensure!(
578 &reconstructed == list_digest,
579 "reconstructed list digest does not match; expected {}, reconstructed {}",
580 list_digest,
581 reconstructed
582 );
583
584 *list_digest = *tail;
586 Ok(())
587 }
588 }
589 }
590}
591
592impl From<Vec<MaybePruned<Assumption>>> for Assumptions {
593 fn from(value: Vec<MaybePruned<Assumption>>) -> Self {
594 Self(value)
595 }
596}
597
598impl From<Vec<Assumption>> for Assumptions {
599 fn from(value: Vec<Assumption>) -> Self {
600 Self(value.into_iter().map(Into::into).collect())
601 }
602}
603
604impl From<Vec<Assumption>> for MaybePruned<Assumptions> {
605 fn from(value: Vec<Assumption>) -> Self {
606 Self::Value(value.into())
607 }
608}