1pub mod zkr;
16
17use std::{
18 collections::{BTreeMap, VecDeque},
19 fmt::Debug,
20 sync::Mutex,
21};
22
23use anyhow::{anyhow, bail, ensure, Context, Result};
24use risc0_binfmt::read_sha_halfs;
25use risc0_circuit_recursion::{
26 control_id::BN254_IDENTITY_CONTROL_ID,
27 prove::{DigestKind, RecursionReceipt},
28 CircuitImpl,
29};
30use risc0_circuit_rv32im::RV32IM_SEAL_VERSION;
31use risc0_zkp::{
32 adapter::{CircuitInfo, PROOF_SYSTEM_INFO},
33 core::{
34 digest::{Digest, DIGEST_SHORTS},
35 hash::{hash_suite_from_name, poseidon2::Poseidon2HashSuite},
36 },
37 field::baby_bear::BabyBearElem,
38 verify::ReadIOP,
39};
40
41use crate::{
42 claim::{
43 merge::Merge,
44 receipt::{Assumption, UnionClaim},
45 Unknown,
46 },
47 receipt::{
48 merkle::{MerkleGroup, MerkleProof},
49 SegmentReceipt, SuccinctReceipt, SuccinctReceiptVerifierParameters,
50 },
51 sha::Digestible,
52 Assumptions, MaybePruned, Output, ProverOpts, ReceiptClaim, WorkClaim,
53};
54
55use risc0_circuit_recursion::prove::Program;
56
57pub const RECURSION_PO2: usize = 18;
59
60pub(crate) type ZkrRegistryEntry = Box<dyn Fn() -> Result<Program> + Send + 'static>;
61
62pub(crate) type ZkrRegistry = BTreeMap<Digest, ZkrRegistryEntry>;
63
64pub(crate) static ZKR_REGISTRY: Mutex<ZkrRegistry> = Mutex::new(BTreeMap::new());
66
67pub fn lift(segment_receipt: &SegmentReceipt) -> Result<SuccinctReceipt<ReceiptClaim>> {
74 tracing::debug!("Proving lift: claim = {:#?}", segment_receipt.claim);
75 let mut prover = Prover::new_lift(segment_receipt, ProverOpts::succinct())?;
76
77 let receipt = prover.prover.run()?;
78 let claim_decoded = ReceiptClaim::decode(&mut receipt.out_stream())?;
79 tracing::debug!("Proving lift finished: decoded claim = {claim_decoded:#?}");
80
81 let claim = claim_decoded.merge(&segment_receipt.claim)?;
82
83 make_succinct_receipt(prover, receipt, claim)
84}
85
86pub fn lift_povw(
91 segment_receipt: &SegmentReceipt,
92) -> Result<SuccinctReceipt<WorkClaim<ReceiptClaim>>> {
93 tracing::debug!("Proving lift_povw: claim = {:#?}", segment_receipt.claim);
94 let mut prover = Prover::new_lift_povw(segment_receipt, ProverOpts::succinct())?;
95
96 let receipt = prover.prover.run()?;
97 let mut out_stream = receipt.out_stream();
98 tracing::debug!("Proving lift_povw finished: out = {out_stream:?}");
99 let claim_decoded = WorkClaim::<ReceiptClaim>::decode_from_seal(&mut out_stream)?;
100 tracing::debug!("Proving lift_povw finished: decoded claim = {claim_decoded:#?}");
101
102 let mut claim = claim_decoded;
104 claim
105 .claim
106 .merge_with(&segment_receipt.claim.clone().into())
107 .context("failed to merge segment receipt claim into decode claim")?;
108
109 make_succinct_receipt(prover, receipt, claim)
110}
111
112pub fn join(
117 a: &SuccinctReceipt<ReceiptClaim>,
118 b: &SuccinctReceipt<ReceiptClaim>,
119) -> Result<SuccinctReceipt<ReceiptClaim>> {
120 tracing::debug!("Proving join: a.claim = {:#?}", a.claim);
121 tracing::debug!("Proving join: b.claim = {:#?}", b.claim);
122
123 let mut prover = Prover::new_join(a, b, ProverOpts::succinct())?;
124 let receipt = prover.prover.run()?;
125
126 let claim_decoded = ReceiptClaim::decode(&mut receipt.out_stream())?;
127 tracing::debug!("Proving join finished: decoded claim = {claim_decoded:#?}");
128
129 let claim = claim_decoded.merge(&a.claim.join(&b.claim)?.value()?)?;
131
132 make_succinct_receipt(prover, receipt, claim)
133}
134
135pub fn join_povw(
140 a: &SuccinctReceipt<WorkClaim<ReceiptClaim>>,
141 b: &SuccinctReceipt<WorkClaim<ReceiptClaim>>,
142) -> Result<SuccinctReceipt<WorkClaim<ReceiptClaim>>> {
143 tracing::debug!("Proving join_povw: a.claim = {:#?}", a.claim);
144 tracing::debug!("Proving join_povw: b.claim = {:#?}", b.claim);
145
146 let mut prover = Prover::new_join_povw(a, b, false, ProverOpts::succinct())?;
147 let receipt = prover.prover.run()?;
148
149 let claim_decoded = WorkClaim::<ReceiptClaim>::decode_from_seal(&mut receipt.out_stream())?;
150 tracing::debug!("Proving join_povw finished: decoded claim = {claim_decoded:#?}");
151
152 let claim = claim_decoded.merge(&a.claim.join(&b.claim)?.value()?)?;
154
155 make_succinct_receipt(prover, receipt, claim)
156}
157
158pub fn join_unwrap_povw(
163 a: &SuccinctReceipt<WorkClaim<ReceiptClaim>>,
164 b: &SuccinctReceipt<WorkClaim<ReceiptClaim>>,
165) -> Result<SuccinctReceipt<ReceiptClaim>> {
166 tracing::debug!("Proving join_unwrap_povw: a.claim = {:#?}", a.claim);
167 tracing::debug!("Proving join_unwrap_povw: b.claim = {:#?}", b.claim);
168
169 let mut prover = Prover::new_join_povw(a, b, true, ProverOpts::succinct())?;
170 let receipt = prover.prover.run()?;
171
172 let claim_decoded = ReceiptClaim::decode(&mut receipt.out_stream())?;
173 tracing::debug!("Proving join_unwrap_povw finished: decoded claim = {claim_decoded:#?}");
174
175 let claim = claim_decoded.merge(&a.claim.join(&b.claim)?.value()?.claim.value()?)?;
177
178 make_succinct_receipt(prover, receipt, claim)
179}
180
181pub fn union(
186 a: &SuccinctReceipt<Unknown>,
187 b: &SuccinctReceipt<Unknown>,
188) -> Result<SuccinctReceipt<UnionClaim>> {
189 let a_assumption = a.to_assumption(false)?.digest();
192 let b_assumption = b.to_assumption(false)?.digest();
193
194 let ((left_assumption, left_receipt), (right_assumption, right_receipt)) =
195 if a_assumption <= b_assumption {
196 ((a_assumption, a), (b_assumption, b))
197 } else {
198 ((b_assumption, b), (a_assumption, a))
199 };
200
201 tracing::debug!("Proving union: left assumption = {:#?}", left_assumption);
202 tracing::debug!("Proving union: right assumption = {:#?}", right_assumption);
203
204 let mut prover = Prover::new_union(left_receipt, right_receipt, ProverOpts::succinct())?;
205 let receipt = prover.prover.run()?;
206
207 let claim = UnionClaim {
208 left: left_assumption,
209 right: right_assumption,
210 };
211
212 make_succinct_receipt(prover, receipt, claim)
213}
214
215pub fn resolve<Claim>(
221 conditional: &SuccinctReceipt<ReceiptClaim>,
222 assumption: &SuccinctReceipt<Claim>,
223) -> Result<SuccinctReceipt<ReceiptClaim>>
224where
225 Claim: risc0_binfmt::Digestible + Debug,
226{
227 tracing::debug!(
228 "Proving resolve: conditional.claim = {:#?}",
229 conditional.claim,
230 );
231 tracing::debug!(
232 "Proving resolve: assumption.claim = {:#?}",
233 assumption.claim,
234 );
235
236 let mut prover = Prover::new_resolve(conditional, assumption, ProverOpts::succinct())?;
237 let receipt = prover.prover.run()?;
238 let claim_decoded = ReceiptClaim::decode(&mut receipt.out_stream())?;
239 tracing::debug!("Proving resolve finished: decoded claim = {claim_decoded:#?}");
240
241 let claim = conditional
242 .claim
243 .as_value()
244 .context("conditional receipt claim is pruned")?
245 .resolve(&assumption.claim)
246 .context("failed to compute resolved claim")?
247 .merge(&claim_decoded)
248 .context("failed to merge resolved and decoded claims")?;
249
250 make_succinct_receipt(prover, receipt, claim)
251}
252
253pub fn resolve_povw<Claim>(
258 conditional: &SuccinctReceipt<WorkClaim<ReceiptClaim>>,
259 assumption: &SuccinctReceipt<Claim>,
260) -> Result<SuccinctReceipt<WorkClaim<ReceiptClaim>>>
261where
262 Claim: risc0_binfmt::Digestible + Debug,
263{
264 tracing::debug!(
265 "Proving resolve_povw: conditional.claim = {:#?}",
266 conditional.claim,
267 );
268 tracing::debug!(
269 "Proving resolve_povw: assumption.claim = {:#?}",
270 assumption.claim,
271 );
272
273 let mut prover =
274 Prover::new_resolve_povw(conditional, assumption, false, ProverOpts::succinct())?;
275 let receipt = prover.prover.run()?;
276 let claim_decoded = WorkClaim::<ReceiptClaim>::decode_from_seal(&mut receipt.out_stream())?;
277 tracing::debug!("Proving resolve_povw finished: decoded claim = {claim_decoded:#?}");
278
279 let claim = conditional
280 .claim
281 .as_value()
282 .context("conditional receipt povw claim is pruned")?
283 .resolve(&assumption.claim)
284 .context("failed to compute resolved claim")?
285 .merge(&claim_decoded)
286 .context("failed to merge resolved and decoded claims")?;
287
288 make_succinct_receipt(prover, receipt, claim)
289}
290
291pub fn resolve_unwrap_povw<Claim>(
296 conditional: &SuccinctReceipt<WorkClaim<ReceiptClaim>>,
297 assumption: &SuccinctReceipt<Claim>,
298) -> Result<SuccinctReceipt<ReceiptClaim>>
299where
300 Claim: risc0_binfmt::Digestible + Debug,
301{
302 tracing::debug!(
303 "Proving resolve_unwrap_povw: conditional.claim = {:#?}",
304 conditional.claim,
305 );
306 tracing::debug!(
307 "Proving resolve_unwrap_povw: assumption.claim = {:#?}",
308 assumption.claim,
309 );
310
311 let mut prover =
312 Prover::new_resolve_povw(conditional, assumption, true, ProverOpts::succinct())?;
313 let receipt = prover.prover.run()?;
314 let claim_decoded = ReceiptClaim::decode(&mut receipt.out_stream())?;
315 tracing::debug!("Proving resolve_unwrap_povw finished: decoded claim = {claim_decoded:#?}");
316
317 let claim = conditional
318 .claim
319 .as_value()
320 .context("conditional receipt povw claim is pruned")?
321 .claim
322 .as_value()
323 .context("conditional receipt claim is pruned")?
324 .resolve(&assumption.claim)
325 .context("failed to compute resolved claim")?
326 .merge(&claim_decoded)
327 .context("failed to merge resolved and decoded claims")?;
328
329 make_succinct_receipt(prover, receipt, claim)
330}
331
332pub fn unwrap_povw(
337 a: &SuccinctReceipt<WorkClaim<ReceiptClaim>>,
338) -> Result<SuccinctReceipt<ReceiptClaim>> {
339 tracing::debug!("Proving unwrap_povw: a.claim = {:#?}", a.claim);
340
341 let mut prover = Prover::new_unwrap_povw(a, ProverOpts::succinct())?;
342 let receipt = prover.prover.run()?;
343
344 let claim_decoded = ReceiptClaim::decode(&mut receipt.out_stream())?;
345 tracing::debug!("Proving unwrap_povw finished: decoded claim = {claim_decoded:#?}");
346
347 let claim = MaybePruned::Value(claim_decoded).merge(&a.claim.as_value()?.claim)?;
349
350 make_succinct_receipt(prover, receipt, claim)
351}
352
353pub fn identity_p254(
359 inner: &SuccinctReceipt<ReceiptClaim>,
360) -> Result<SuccinctReceipt<ReceiptClaim>> {
361 tracing::debug!("identity_p254");
362
363 let opts = ProverOpts::succinct()
364 .with_hashfn("poseidon_254".to_string())
365 .with_control_ids(vec![BN254_IDENTITY_CONTROL_ID]);
366 let mut prover = Prover::new_identity(inner, opts)?;
367 let receipt = prover.prover.run()?;
368 let claim =
369 MaybePruned::Value(ReceiptClaim::decode(&mut receipt.out_stream())?).merge(&inner.claim)?;
370
371 let hashfn = prover.opts.hash_suite()?.hashfn;
373 let control_inclusion_proof = prover.control_inclusion_proof()?;
374 let control_root = control_inclusion_proof.root(&prover.control_id, hashfn.as_ref());
375 let params = SuccinctReceiptVerifierParameters {
376 control_root,
377 inner_control_root: Some(inner.control_root()?),
378 proof_system_info: PROOF_SYSTEM_INFO,
379 circuit_info: CircuitImpl::CIRCUIT_INFO,
380 };
381
382 Ok(SuccinctReceipt {
383 seal: receipt.seal,
384 hashfn: prover.opts.hashfn.clone(),
385 control_id: prover.control_id,
386 control_inclusion_proof,
387 claim,
388 verifier_parameters: params.digest(),
389 })
390}
391
392pub fn prove_zkr(
394 program: Program,
395 control_id: &Digest,
396 allowed_control_ids: Vec<Digest>,
397 input: &[u8],
398) -> Result<SuccinctReceipt<Unknown>> {
399 let opts = ProverOpts::succinct().with_control_ids(allowed_control_ids);
400 let mut prover = Prover::new(program, *control_id, opts.clone());
401 prover.add_input(bytemuck::cast_slice(input));
402
403 tracing::debug!("Running prover");
404 let receipt = prover.run()?;
405
406 tracing::trace!("zkr receipt: {receipt:?}");
407
408 let claim_digest: Digest = read_sha_halfs(&mut VecDeque::from_iter(
410 bytemuck::checked::cast_slice::<_, BabyBearElem>(
411 &receipt.seal[DIGEST_SHORTS..2 * DIGEST_SHORTS],
412 )
413 .iter()
414 .copied()
415 .map(u32::from),
416 ))?;
417
418 let hashfn = opts.hash_suite()?.hashfn;
419 let control_group = MerkleGroup::new(opts.control_ids.clone())?;
420 let control_root = control_group.calc_root(hashfn.as_ref());
421 let control_inclusion_proof = control_group.get_proof(control_id, hashfn.as_ref())?;
422
423 let verifier_parameters = SuccinctReceiptVerifierParameters {
424 control_root,
425 inner_control_root: None,
426 proof_system_info: PROOF_SYSTEM_INFO,
427 circuit_info: CircuitImpl::CIRCUIT_INFO,
428 }
429 .digest();
430
431 Ok(SuccinctReceipt {
432 seal: receipt.seal,
433 hashfn: opts.hashfn,
434 control_id: *control_id,
435 control_inclusion_proof,
436 claim: MaybePruned::<Unknown>::Pruned(claim_digest),
437 verifier_parameters,
438 })
439}
440
441pub fn prove_registered_zkr(
443 control_id: &Digest,
444 allowed_control_ids: Vec<Digest>,
445 input: &[u8],
446) -> Result<SuccinctReceipt<Unknown>> {
447 let zkr = get_registered_zkr(control_id)?;
448 prove_zkr(zkr, control_id, allowed_control_ids, input)
449}
450
451pub fn register_zkr(
453 control_id: &Digest,
454 get_program_fn: impl Fn() -> Result<Program> + Send + 'static,
455) -> Option<ZkrRegistryEntry> {
456 let mut registry = ZKR_REGISTRY.lock().unwrap();
457 registry.insert(*control_id, Box::new(get_program_fn))
458}
459
460pub fn get_registered_zkr(control_id: &Digest) -> Result<Program> {
462 let registry = ZKR_REGISTRY.lock().unwrap();
463 registry
464 .get(control_id)
465 .map(|f| f())
466 .unwrap_or_else(|| bail!("Control id {control_id} unregistered"))
467}
468
469fn make_succinct_receipt<Claim>(
471 prover: Prover,
472 receipt: RecursionReceipt,
473 claim: impl Into<MaybePruned<Claim>>,
474) -> Result<SuccinctReceipt<Claim>> {
475 Ok(SuccinctReceipt {
476 seal: receipt.seal,
477 hashfn: prover.opts.hashfn.clone(),
478 control_id: prover.control_id,
479 control_inclusion_proof: prover.control_inclusion_proof()?,
480 claim: claim.into(),
481 verifier_parameters: SuccinctReceiptVerifierParameters::default().digest(),
483 })
484}
485
486#[cfg(test)]
491pub fn test_zkr(
492 digest1: &Digest,
493 digest2: &Digest,
494 po2: usize,
495) -> Result<SuccinctReceipt<crate::claim::Unknown>> {
496 use risc0_circuit_recursion::prove::zkr::get_zkr;
497 use risc0_zkp::core::hash::poseidon2::Poseidon2HashSuite;
498
499 let program = get_zkr("test_recursion_circuit.zkr", po2)?;
500 let suite = Poseidon2HashSuite::new_suite();
501 let control_id = program.compute_control_id(suite.clone()).unwrap();
502 let opts = ProverOpts::succinct().with_control_ids(vec![control_id]);
503
504 let mut prover = Prover::new(program, control_id, opts.clone());
505 prover.add_input_digest(digest1, DigestKind::Poseidon2);
506 prover.add_input_digest(digest2, DigestKind::Poseidon2);
507
508 let receipt = prover.run()?;
509
510 let claim_digest = read_sha_halfs(&mut VecDeque::from_iter(
512 bytemuck::checked::cast_slice::<_, BabyBearElem>(
513 &receipt.seal[DIGEST_SHORTS..2 * DIGEST_SHORTS],
514 )
515 .iter()
516 .copied()
517 .map(u32::from),
518 ))?;
519
520 let hashfn = opts.hash_suite()?.hashfn;
522 let control_inclusion_proof = MerkleGroup::new(opts.control_ids.clone())?
523 .get_proof(&prover.control_id, hashfn.as_ref())?;
524 let control_root = control_inclusion_proof.root(&prover.control_id, hashfn.as_ref());
525 let params = SuccinctReceiptVerifierParameters {
526 control_root,
527 inner_control_root: Some(digest1.to_owned()),
528 proof_system_info: PROOF_SYSTEM_INFO,
529 circuit_info: CircuitImpl::CIRCUIT_INFO,
530 };
531 Ok(SuccinctReceipt {
532 seal: receipt.seal,
533 hashfn: suite.name,
534 control_id: prover.control_id,
535 control_inclusion_proof,
536 claim: MaybePruned::Pruned(claim_digest),
537 verifier_parameters: params.digest(),
538 })
539}
540
541pub struct Prover {
543 prover: risc0_circuit_recursion::prove::Prover,
544 control_id: Digest,
545 opts: ProverOpts,
546}
547
548macro_rules! ensure_poseidon2 {
550 ($receipt:expr) => {
551 ensure!(
552 $receipt.hashfn == "poseidon2",
553 "recursion programs only supports poseidon2 hashfn; received {}",
554 $receipt.hashfn
555 );
556 };
557}
558
559impl Prover {
560 pub(crate) fn new(program: Program, control_id: Digest, opts: ProverOpts) -> Self {
561 Self {
562 prover: risc0_circuit_recursion::prove::Prover::new(program, &opts.hashfn),
563 control_id,
564 opts,
565 }
566 }
567
568 pub fn control_id(&self) -> &Digest {
570 &self.control_id
571 }
572
573 pub fn control_inclusion_proof(&self) -> Result<MerkleProof> {
575 let hashfn = self
576 .opts
577 .hash_suite()
578 .context("ProverOpts contains invalid hashfn")?
579 .hashfn;
580 MerkleGroup::new(self.opts.control_ids.clone())?
581 .get_proof(&self.control_id, hashfn.as_ref())
582 }
583
584 pub fn new_test_recursion_circuit(digests: [&Digest; 2], opts: ProverOpts) -> Result<Self> {
587 let (program, control_id) = zkr::test_recursion_circuit(&opts.hashfn)?;
588 let mut prover = Prover::new(program, control_id, opts);
589
590 for digest in digests {
591 prover.add_input_digest(digest, DigestKind::Poseidon2);
592 }
593
594 Ok(prover)
595 }
596
597 pub fn new_lift(segment: &SegmentReceipt, opts: ProverOpts) -> Result<Self> {
606 Self::new_lift_inner(segment, opts, false)
607 }
608
609 pub fn new_lift_povw(segment: &SegmentReceipt, opts: ProverOpts) -> Result<Self> {
614 Self::new_lift_inner(segment, opts, true)
615 }
616
617 fn new_lift_inner(segment: &SegmentReceipt, opts: ProverOpts, povw: bool) -> Result<Self> {
620 ensure_poseidon2!(segment);
621
622 let inner_hash_suite = hash_suite_from_name(&segment.hashfn)
623 .ok_or_else(|| anyhow!("unsupported hash function: {}", segment.hashfn))?;
624 let allowed_ids = MerkleGroup::new(opts.control_ids.clone())?;
625 let merkle_root = allowed_ids.calc_root(inner_hash_suite.hashfn.as_ref());
626
627 let out_size = risc0_circuit_rv32im::CircuitImpl::OUTPUT_SIZE;
628
629 ensure!(
630 segment.seal[0] == RV32IM_SEAL_VERSION,
631 "seal version mismatch"
632 );
633
634 let seal = &segment.seal[1..];
635
636 let mut iop = ReadIOP::new(seal, inner_hash_suite.rng.as_ref());
639 iop.read_field_elem_slice::<BabyBearElem>(out_size);
640 let po2 = *iop.read_u32s(1).first().unwrap() as usize;
641
642 let (program, control_id) = match povw {
644 false => zkr::lift(po2, &opts.hashfn)?,
645 true => zkr::lift_povw(po2, &opts.hashfn)?,
646 };
647 let mut prover = Prover::new(program, control_id, opts);
648
649 prover.add_input_digest(&merkle_root, DigestKind::Poseidon2);
650 prover.add_input(seal);
651
652 Ok(prover)
653 }
654
655 pub fn new_union(
661 a: &SuccinctReceipt<Unknown>,
662 b: &SuccinctReceipt<Unknown>,
663 opts: ProverOpts,
664 ) -> Result<Self> {
665 ensure_poseidon2!(a);
666 ensure_poseidon2!(b);
667
668 let hash_suite = Poseidon2HashSuite::new_suite();
669 let allowed_ids = MerkleGroup::new(opts.control_ids.clone())?;
670 let merkle_root = allowed_ids.calc_root(hash_suite.hashfn.as_ref());
671
672 let (program, control_id) = zkr::union(&opts.hashfn)?;
673 let mut prover = Prover::new(program, control_id, opts);
674
675 prover.add_input_digest(&merkle_root, DigestKind::Poseidon2);
676 prover.add_succinct_generic_receipt(a)?;
677 prover.add_succinct_generic_receipt(b)?;
678 Ok(prover)
679 }
680
681 pub fn new_join(
687 a: &SuccinctReceipt<ReceiptClaim>,
688 b: &SuccinctReceipt<ReceiptClaim>,
689 opts: ProverOpts,
690 ) -> Result<Self> {
691 ensure_poseidon2!(a);
692 ensure_poseidon2!(b);
693
694 let (program, control_id) = zkr::join(&opts.hashfn)?;
695 let mut prover = Prover::new(program, control_id, opts);
696
697 let merkle_root = a.control_root()?;
701 ensure!(
702 merkle_root == b.control_root()?,
703 "merkle roots for a and b do not match: {} != {}",
704 merkle_root,
705 b.control_root()?
706 );
707
708 prover.add_input_digest(&merkle_root, DigestKind::Poseidon2);
709 prover.add_succinct_rv32im_receipt(a)?;
710 prover.add_succinct_rv32im_receipt(b)?;
711 Ok(prover)
712 }
713
714 pub fn new_join_povw(
720 a: &SuccinctReceipt<WorkClaim<ReceiptClaim>>,
721 b: &SuccinctReceipt<WorkClaim<ReceiptClaim>>,
722 unwrap: bool,
723 opts: ProverOpts,
724 ) -> Result<Self> {
725 ensure_poseidon2!(a);
726 ensure_poseidon2!(b);
727
728 let (program, control_id) = match unwrap {
729 false => zkr::join_povw(&opts.hashfn)?,
730 true => zkr::join_unwrap_povw(&opts.hashfn)?,
731 };
732 let mut prover = Prover::new(program, control_id, opts);
733
734 let merkle_root = a.control_root()?;
738 ensure!(
739 merkle_root == b.control_root()?,
740 "merkle roots for a and b do not match: {} != {}",
741 merkle_root,
742 b.control_root()?
743 );
744
745 prover.add_input_digest(&merkle_root, DigestKind::Poseidon2);
746 prover.add_succinct_work_claim_rv32im_receipt(a)?;
747 prover.add_succinct_work_claim_rv32im_receipt(b)?;
748 Ok(prover)
749 }
750
751 pub fn new_resolve<Claim>(
758 cond: &SuccinctReceipt<ReceiptClaim>,
759 assum: &SuccinctReceipt<Claim>,
760 opts: ProverOpts,
761 ) -> Result<Self>
762 where
763 Claim: risc0_binfmt::Digestible + Debug,
764 {
765 ensure_poseidon2!(cond);
766 ensure_poseidon2!(assum);
767
768 let (program, control_id) = zkr::resolve(&opts.hashfn)?;
770 let mut prover = Prover::new(program, control_id, opts);
771
772 prover.add_input_digest(&cond.control_root()?, DigestKind::Poseidon2);
776 prover.add_succinct_rv32im_receipt(cond)?;
777
778 let (head, tail, output) = check_resolve_assumption(&cond.claim, &assum.claim)?;
779
780 let expected_root = match head.control_root == Digest::ZERO {
782 true => cond.control_root()?,
783 false => head.control_root,
784 };
785 ensure!(
786 expected_root == assum.control_root()?,
787 "assumption receipt control root does not match head of assumptions list"
788 );
789
790 prover.add_assumption_receipt(head, assum)?;
791 prover.add_input_digest(&tail.digest(), DigestKind::Sha256);
792 prover.add_input_digest(&output.journal.digest(), DigestKind::Sha256);
793 Ok(prover)
794 }
795
796 pub fn new_resolve_povw<Claim>(
802 cond: &SuccinctReceipt<WorkClaim<ReceiptClaim>>,
803 assum: &SuccinctReceipt<Claim>,
804 unwrap: bool,
805 opts: ProverOpts,
806 ) -> Result<Self>
807 where
808 Claim: risc0_binfmt::Digestible + Debug,
809 {
810 ensure_poseidon2!(cond);
811 ensure_poseidon2!(assum);
812
813 let (program, control_id) = match unwrap {
815 false => zkr::resolve_povw(&opts.hashfn)?,
816 true => zkr::resolve_unwrap_povw(&opts.hashfn)?,
817 };
818 let mut prover = Prover::new(program, control_id, opts);
819
820 prover.add_input_digest(&cond.control_root()?, DigestKind::Poseidon2);
824 prover.add_succinct_work_claim_rv32im_receipt(cond)?;
825
826 let cond_claim = &cond
827 .claim
828 .as_value()
829 .context("cannot resolve assumption receipt with pruned work claim")?
830 .claim;
831 let (head, tail, output) = check_resolve_assumption(cond_claim, &assum.claim)?;
832
833 let expected_root = match head.control_root == Digest::ZERO {
835 true => cond.control_root()?,
836 false => head.control_root,
837 };
838 ensure!(
839 expected_root == assum.control_root()?,
840 "assumption receipt control root does not match head of assumptions list"
841 );
842
843 prover.add_assumption_receipt(head, assum)?;
844 prover.add_input_digest(&tail.digest(), DigestKind::Sha256);
845 prover.add_input_digest(&output.journal.digest(), DigestKind::Sha256);
846 Ok(prover)
847 }
848
849 pub fn new_identity(a: &SuccinctReceipt<ReceiptClaim>, opts: ProverOpts) -> Result<Self> {
854 ensure_poseidon2!(a);
855
856 let (program, control_id) = zkr::identity(&opts.hashfn)?;
857 let mut prover = Prover::new(program, control_id, opts);
858
859 prover.add_input_digest(&a.control_root()?, DigestKind::Poseidon2);
860 prover.add_succinct_rv32im_receipt(a)?;
861 Ok(prover)
862 }
863
864 pub fn new_unwrap_povw(
870 a: &SuccinctReceipt<WorkClaim<ReceiptClaim>>,
871 opts: ProverOpts,
872 ) -> Result<Self> {
873 ensure_poseidon2!(a);
874
875 let (program, control_id) = zkr::unwrap_povw(&opts.hashfn)?;
876 let mut prover = Prover::new(program, control_id, opts);
877
878 prover.add_input_digest(&a.control_root()?, DigestKind::Poseidon2);
879 prover.add_succinct_work_claim_rv32im_receipt(a)?;
880 Ok(prover)
881 }
882
883 pub(crate) fn add_input(&mut self, input: &[u32]) {
884 self.prover.add_input(input)
885 }
886
887 fn add_input_digest(&mut self, digest: &Digest, kind: DigestKind) {
889 self.prover.add_input_digest(digest, kind)
890 }
891
892 pub fn add_seal(
894 &mut self,
895 seal: &[u32],
896 control_id: &Digest,
897 control_inclusion_proof: &MerkleProof,
898 ) -> Result<()> {
899 tracing::debug!("Control ID = {:?}", control_id);
900 self.add_input(seal);
901 tracing::debug!("index = {:?}", control_inclusion_proof.index);
902 self.add_input(bytemuck::cast_slice(&[BabyBearElem::new(
903 control_inclusion_proof.index,
904 )]));
905 for digest in &control_inclusion_proof.digests {
906 tracing::debug!("path = {:?}", digest);
907 self.add_input_digest(digest, DigestKind::Poseidon2);
908 }
909 Ok(())
910 }
911
912 fn add_assumption_receipt<Claim>(
914 &mut self,
915 assumption: Assumption,
916 receipt: &SuccinctReceipt<Claim>,
917 ) -> Result<()> {
918 self.add_seal(
919 &receipt.seal,
920 &receipt.control_id,
921 &receipt.control_inclusion_proof,
922 )?;
923 let zero_root = BabyBearElem::new((assumption.control_root == Digest::ZERO) as u32);
925 self.add_input(bytemuck::cast_slice(&[zero_root]));
926 Ok(())
927 }
928
929 fn add_succinct_work_claim_rv32im_receipt(
930 &mut self,
931 a: &SuccinctReceipt<WorkClaim<ReceiptClaim>>,
932 ) -> Result<()> {
933 self.add_seal(&a.seal, &a.control_id, &a.control_inclusion_proof)?;
934 let mut data = Vec::<u32>::new();
935 a.claim.as_value()?.encode_to_seal(&mut data)?;
936 let data_fp: Vec<BabyBearElem> = data.iter().map(|x| BabyBearElem::new(*x)).collect();
937 self.add_input(bytemuck::cast_slice(&data_fp));
938 Ok(())
939 }
940
941 fn add_succinct_rv32im_receipt(&mut self, a: &SuccinctReceipt<ReceiptClaim>) -> Result<()> {
943 self.add_seal(&a.seal, &a.control_id, &a.control_inclusion_proof)?;
944 let mut data = Vec::<u32>::new();
945 a.claim.as_value()?.encode(&mut data)?;
946 let data_fp: Vec<BabyBearElem> = data.iter().map(|x| BabyBearElem::new(*x)).collect();
947 self.add_input(bytemuck::cast_slice(&data_fp));
948 Ok(())
949 }
950
951 fn add_succinct_generic_receipt<Claim>(&mut self, a: &SuccinctReceipt<Claim>) -> Result<()> {
953 self.add_seal(&a.seal, &a.control_id, &a.control_inclusion_proof)?;
954 let zero_root = BabyBearElem::new((a.control_root()? == Digest::ZERO) as u32);
956 self.add_input(bytemuck::cast_slice(&[zero_root]));
957 Ok(())
958 }
959
960 pub fn run(&mut self) -> Result<RecursionReceipt> {
963 self.prover.run()
964 }
965}
966
967fn check_resolve_assumption<Claim>(
968 cond: &MaybePruned<ReceiptClaim>,
969 assum: &MaybePruned<Claim>,
970) -> Result<(Assumption, Assumptions, Output)>
971where
972 Claim: risc0_binfmt::Digestible + Debug,
973{
974 let output = cond
975 .as_value()
976 .context("cannot resolve conditional receipt with pruned claim")?
977 .output
978 .as_value()
979 .context("cannot resolve conditional receipt with pruned output")?
980 .as_ref()
981 .ok_or_else(|| anyhow!("cannot resolve conditional receipt with no output"))?
982 .clone();
983
984 let assumptions = output
987 .assumptions
988 .clone()
989 .value()
990 .context("cannot resolve conditional receipt with pruned assumptions")?;
991 let head: Assumption = assumptions
992 .0
993 .first()
994 .ok_or_else(|| anyhow!("cannot resolve conditional receipt with no assumptions"))?
995 .as_value()
996 .context("cannot resolve conditional receipt with pruned head assumption")?
997 .clone();
998
999 ensure!(
1000 head.claim == assum.digest(),
1001 "assumption receipt claim does not match head of assumptions list"
1002 );
1003
1004 let mut assumptions_tail = assumptions;
1005 assumptions_tail.resolve(&head.digest())?;
1006
1007 Ok((head, assumptions_tail, output))
1008}