risc0_zkvm/host/client/prove/
bonsai.rs1use std::time::Duration;
16
17use anyhow::{anyhow, bail, ensure, Context, Result};
18use bonsai_sdk::blocking::Client;
19
20use super::Prover;
21use crate::{
22 compute_image_id, is_dev_mode, AssumptionReceipt, ExecutorEnv, InnerAssumptionReceipt,
23 InnerReceipt, ProveInfo, ProverOpts, Receipt, ReceiptKind, SessionStats, VerifierContext,
24 VERSION,
25};
26
27pub struct BonsaiProver {
32 name: String,
33}
34
35impl BonsaiProver {
36 pub fn new(name: &str) -> Self {
38 Self {
39 name: name.to_string(),
40 }
41 }
42}
43
44fn get_inner_assumption_receipt(assumption: &AssumptionReceipt) -> Result<&InnerAssumptionReceipt> {
46 match assumption {
47 AssumptionReceipt::Proven(receipt) => {
48 if !matches!(receipt, InnerAssumptionReceipt::Succinct(_)) {
49 bail!(
50 "Bonsai only supports succinct assumption receipts. \
51 Use `ProverOpts::succinct()` when proving any assumptions."
52 );
53 };
54 Ok(receipt)
55 }
56 AssumptionReceipt::Unresolved(_) => {
57 bail!("only proven assumptions can be uploaded to Bonsai.")
58 }
59 }
60}
61
62impl Prover for BonsaiProver {
63 fn get_name(&self) -> String {
64 self.name.clone()
65 }
66
67 fn prove_with_ctx(
68 &self,
69 env: ExecutorEnv<'_>,
70 ctx: &VerifierContext,
71 elf: &[u8],
72 opts: &ProverOpts,
73 ) -> Result<ProveInfo> {
74 let client = Client::from_env(VERSION)?;
75
76 let image_id = compute_image_id(elf)?;
78 let image_id_hex = hex::encode(image_id);
79 client.upload_img(&image_id_hex, elf.to_vec())?;
80
81 let input_id = client.upload_input(env.input)?;
83
84 let mut receipts_ids = vec![];
86 for assumption in env.assumptions.borrow().0.iter() {
87 let inner_receipt = get_inner_assumption_receipt(assumption)?;
88 let serialized_receipt = bincode::serialize(inner_receipt)?;
89 let receipt_id = client.upload_receipt(serialized_receipt)?;
90 receipts_ids.push(receipt_id);
91 }
92
93 let session = client.create_session_with_limit(
97 image_id_hex,
98 input_id,
99 receipts_ids,
100 false,
101 env.session_limit,
102 )?;
103 tracing::debug!("Bonsai proving SessionID: {}", session.uuid);
104
105 let polling_interval = if let Ok(ms) = std::env::var("BONSAI_POLL_INTERVAL_MS") {
107 Duration::from_millis(ms.parse().context("invalid bonsai poll interval")?)
108 } else {
109 Duration::from_secs(1)
110 };
111 let succinct_prove_info = loop {
112 let res = session.status(&client)?;
115 if res.status == "RUNNING" {
116 std::thread::sleep(polling_interval);
117 continue;
118 }
119 if res.status == "SUCCEEDED" {
120 let receipt_url = res
122 .receipt_url
123 .ok_or_else(|| anyhow!("API error, missing receipt on completed session"))?;
124
125 let stats = res
126 .stats
127 .context("Missing stats object on Bonsai status res")?;
128 tracing::debug!(
129 "Bonsai usage: cycles: {} total_cycles: {}",
130 stats.cycles,
131 stats.total_cycles
132 );
133
134 let receipt_buf = client.download(&receipt_url)?;
135 let receipt: Receipt = bincode::deserialize(&receipt_buf)?;
136
137 if opts.prove_guest_errors {
138 receipt.verify_integrity_with_context(ctx)?;
139 } else {
140 receipt.verify_with_context(ctx, image_id)?;
141 }
142 break ProveInfo {
143 receipt,
144 stats: SessionStats {
145 segments: stats.segments,
146 total_cycles: stats.total_cycles,
147 user_cycles: stats.cycles,
148 paging_cycles: 0,
150 reserved_cycles: 0,
151 },
152 };
153 } else {
154 bail!(
155 "Bonsai prover workflow [{}] exited: {} err: {}",
156 session.uuid,
157 res.status,
158 res.error_msg
159 .unwrap_or("Bonsai workflow missing error_msg".into()),
160 );
161 }
162 };
163 match opts.receipt_kind {
164 ReceiptKind::Composite | ReceiptKind::Succinct => {
166 return Ok(succinct_prove_info);
167 }
168 ReceiptKind::Groth16 => {}
170 }
171
172 let snark_session = client.create_snark(session.uuid)?;
174 let snark_receipt_url = loop {
175 let res = snark_session.status(&client)?;
176 match res.status.as_str() {
177 "RUNNING" => {
178 std::thread::sleep(polling_interval);
179 continue;
180 }
181 "SUCCEEDED" => {
182 break res.output.with_context(|| {
183 format!(
184 "Bonsai prover workflow [{}] reported success, but provided no receipt",
185 snark_session.uuid
186 )
187 })?;
188 }
189 _ => {
190 bail!(
191 "Bonsai prover workflow [{}] exited: {} err: {}",
192 snark_session.uuid,
193 res.status,
194 res.error_msg
195 .unwrap_or("Bonsai workflow missing error_msg".into()),
196 );
197 }
198 }
199 };
200
201 let receipt_buf = client.download(&snark_receipt_url)?;
209 let groth16_receipt: Receipt = bincode::deserialize(&receipt_buf)?;
210 groth16_receipt
211 .verify_integrity_with_context(ctx)
212 .context("failed to verify Groth16Receipt returned by Bonsai")?;
213
214 Ok(ProveInfo {
216 receipt: groth16_receipt,
217 stats: succinct_prove_info.stats,
218 })
219 }
220
221 fn compress(&self, opts: &ProverOpts, receipt: &Receipt) -> Result<Receipt> {
222 match (&receipt.inner, opts.receipt_kind) {
223 (InnerReceipt::Composite(_), ReceiptKind::Composite)
225 | (InnerReceipt::Succinct(_), ReceiptKind::Composite | ReceiptKind::Succinct)
226 | (
227 InnerReceipt::Groth16(_),
228 ReceiptKind::Composite | ReceiptKind::Succinct | ReceiptKind::Groth16,
229 ) => Ok(receipt.clone()),
230 (InnerReceipt::Fake { .. }, _) => {
232 ensure!(
233 is_dev_mode(),
234 "dev mode must be enabled to compress fake receipts"
235 );
236 Ok(receipt.clone())
237 }
238 (_, ReceiptKind::Succinct) => {
241 bail!("BonsaiProver does not support compression on existing receipts");
242 }
243 (_, ReceiptKind::Groth16) => {
244 bail!([
246 "BonsaiProver does not support compression on existing receipts",
247 "Set receipt_kind on ProverOpts in initial prove request to get a Groth16Receipt."
248 ].join(""));
249 }
250 }
251 }
252}