risc0_zkvm/host/client/prove/mod.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
15#[cfg(feature = "bonsai")]
16pub(crate) mod bonsai;
17pub(crate) mod external;
18#[cfg(feature = "prove")]
19pub(crate) mod local;
20
21use std::{path::PathBuf, rc::Rc};
22
23use anyhow::{anyhow, Result};
24use risc0_circuit_recursion::control_id::ALLOWED_CONTROL_IDS;
25use risc0_zkp::core::digest::Digest;
26use serde::{Deserialize, Serialize};
27
28#[cfg(feature = "bonsai")]
29use {self::bonsai::BonsaiProver, crate::is_dev_mode};
30
31use self::external::ExternalProver;
32
33use crate::{
34 get_version, host::prove_info::ProveInfo, receipt::DEFAULT_MAX_PO2, ExecutorEnv, Receipt,
35 SessionInfo, VerifierContext,
36};
37
38/// A Prover can execute a given ELF binary and produce a
39/// [Receipt] that can be used to verify correct computation.
40///
41/// # Usage
42/// To produce a proof, you must minimally provide an [ExecutorEnv] and an ELF
43/// binary. See the [risc0_build](https://docs.rs/risc0-build/*/risc0_build)
44/// crate for more information on producing ELF binaries from Rust source code.
45///
46/// ```rust
47/// use risc0_zkvm::{
48/// default_prover,
49/// ExecutorEnv,
50/// ProverOpts,
51/// VerifierContext,
52/// };
53/// use risc0_zkvm_methods::FIB_ELF;
54///
55/// # #[cfg(not(feature = "cuda"))]
56/// # {
57/// // A straightforward case with an ELF binary
58/// let env = ExecutorEnv::builder().write(&20u32).unwrap().build().unwrap();
59/// let receipt = default_prover().prove(env, FIB_ELF).unwrap();
60///
61/// // Or you can specify a context and options
62/// // Here we are using ProverOpts::succinct() to get a constant size proof through recursion.
63/// let env = ExecutorEnv::builder().write(&20u32).unwrap().build().unwrap();
64/// let opts = ProverOpts::succinct();
65/// let receipt = default_prover().prove_with_opts(env, FIB_ELF, &opts).unwrap();
66/// # }
67/// ```
68pub trait Prover {
69 /// Return a name for this [Prover].
70 fn get_name(&self) -> String;
71
72 /// Prove zkVM execution of the specified ELF binary.
73 ///
74 /// Use this method unless you have need to configure the prover options or verifier context.
75 /// Default [VerifierContext] and [ProverOpts] will be used.
76 fn prove(&self, env: ExecutorEnv<'_>, elf: &[u8]) -> Result<ProveInfo> {
77 let opts = ProverOpts::default();
78 let ctx = VerifierContext::default();
79 self.prove_with_ctx(env, &ctx, elf, &opts)
80 }
81
82 /// Prove zkVM execution of the specified ELF binary and using the specified [ProverOpts].
83 ///
84 /// Use this method when you want to specify the receipt type you would like (e.g. groth16 or
85 /// succinct), or if you need to tweak other parameter in [ProverOpts].
86 ///
87 /// Default [VerifierContext] will be used.
88 fn prove_with_opts(
89 &self,
90 env: ExecutorEnv<'_>,
91 elf: &[u8],
92 opts: &ProverOpts,
93 ) -> Result<ProveInfo> {
94 let ctx = VerifierContext::default();
95 self.prove_with_ctx(env, &ctx, elf, opts)
96 }
97
98 /// Prove zkVM execution of the specified ELF binary and using the specified [VerifierContext]
99 /// and [ProverOpts].
100 ///
101 /// Use this method if you are using non-standard verification parameters. The
102 /// [VerifierContext] specified here should match what you expect the verifier to use in your
103 /// application.
104 fn prove_with_ctx(
105 &self,
106 env: ExecutorEnv<'_>,
107 ctx: &VerifierContext,
108 elf: &[u8],
109 opts: &ProverOpts,
110 ) -> Result<ProveInfo>;
111
112 /// Compress a [Receipt], proving the same computation using a smaller representation.
113 ///
114 /// Proving will, by default, produce a [CompositeReceipt](crate::CompositeReceipt), which
115 /// may contain an arbitrary number of receipts assembled into segments and assumptions.
116 /// Together, these receipts collectively prove a top-level
117 /// [ReceiptClaim](crate::ReceiptClaim). This function can be used to compress all of the constituent
118 /// receipts of a [CompositeReceipt](crate::CompositeReceipt) into a single
119 /// [SuccinctReceipt](crate::SuccinctReceipt) or [Groth16Receipt](crate::Groth16Receipt) that proves the same top-level claim.
120 ///
121 /// Compression from [Groth16Receipt](crate::CompositeReceipt) to
122 /// [SuccinctReceipt](crate::SuccinctReceipt) is accomplished by iterative application of the
123 /// recursion programs including lift, join, and resolve.
124 ///
125 /// Compression from [SuccinctReceipt](crate::SuccinctReceipt) to
126 /// [Groth16Receipt](crate::Groth16Receipt) is accomplished by running a Groth16 recursive
127 /// verifier, referred to as the "STARK-to-SNARK" operation.
128 ///
129 /// NOTE: Compression to [Groth16Receipt](crate::Groth16Receipt) is currently only supported on
130 /// x86 hosts, and requires Docker to be installed. See issue
131 /// [#1749](https://github.com/risc0/risc0/issues/1749) for more information.
132 ///
133 /// If the receipt is already at least as compressed as the requested compression level (e.g.
134 /// it is already succinct or Groth16 and a succinct receipt is required) this function is a
135 /// no-op. As a result, it is idempotent.
136 fn compress(&self, opts: &ProverOpts, receipt: &Receipt) -> Result<Receipt>;
137}
138
139/// An Executor can execute a given ELF binary.
140pub trait Executor {
141 /// Execute the specified ELF binary.
142 ///
143 /// This only executes the program and does not generate a receipt.
144 fn execute(&self, env: ExecutorEnv<'_>, elf: &[u8]) -> Result<SessionInfo>;
145}
146
147/// Options to configure a [Prover].
148#[derive(Clone, Debug, Serialize, Deserialize)]
149#[non_exhaustive]
150pub struct ProverOpts {
151 /// Identifier of the hash function to use for the STARK proving protocol.
152 pub hashfn: String,
153
154 /// When false, only prove execution sessions that end in a successful
155 /// [crate::ExitCode] (i.e. `Halted(0)` or `Paused(0)`).
156 /// When set to true, any completed execution session will be proven, including indicated
157 /// errors (e.g. `Halted(1)`) and sessions ending in `Fault`.
158 pub prove_guest_errors: bool,
159
160 /// Kind of receipt to be generated by the prover.
161 pub receipt_kind: ReceiptKind,
162
163 /// List of control IDs to enable for recursion proving.
164 ///
165 /// This list is used to construct the control root, which commits to the set of recursion
166 /// programs that are allowed to run and is a key field in the
167 /// [SuccinctReceiptVerifierParameters][crate::SuccinctReceiptVerifierParameters].
168 pub control_ids: Vec<Digest>,
169
170 /// Maximum cycle count, as a power of two (po2) that these prover options support.
171 pub(crate) max_segment_po2: usize,
172}
173
174/// An enumeration of receipt kinds that can be requested to be generated.
175#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)]
176#[non_exhaustive]
177pub enum ReceiptKind {
178 /// Request that a [CompositeReceipt][crate::CompositeReceipt] be generated.
179 ///
180 /// Composite receipts are made up of a receipt for every segment in a zkVM execution, and
181 /// every assumption. They are linear in size with respect to the execution length.
182 Composite,
183
184 /// Request that a [SuccinctReceipt][crate::SuccinctReceipt] be generated.
185 ///
186 /// Succinct receipts are constant in size, with respect to the execution length.
187 ///
188 Succinct,
189
190 /// Request that a [Groth16Receipt][crate::Groth16Receipt] be generated.
191 ///
192 /// Groth16 receipts are proven using Groth16, are constant in size, and are the smallest
193 /// available receipt format. A Groth16 receipt can be serialized to a few hundred bytes.
194 Groth16,
195}
196
197impl Default for ProverOpts {
198 /// Return [ProverOpts] that are intended to work for most applications.
199 ///
200 /// Proof generated with these options may be linear in size with the execution length, but
201 /// can be compressed using the [Prover::compress] methods.
202 fn default() -> Self {
203 Self {
204 hashfn: "poseidon2".to_string(),
205 prove_guest_errors: false,
206 receipt_kind: ReceiptKind::Composite,
207 control_ids: ALLOWED_CONTROL_IDS.to_vec(),
208 max_segment_po2: DEFAULT_MAX_PO2,
209 }
210 }
211}
212
213impl ProverOpts {
214 /// Construct a [ProverOpts] ready to prove segments with up to the given max cycle count as a
215 /// power of two (po2). All fields are equal to the default expect where they need to be
216 /// adjusted to support a larger po2.
217 ///
218 /// NOTE: If the po2 used to prove is greater than the targeted verifier supports,
219 /// [DEFAULT_MAX_PO2] by default, receipts will be rejected by the verifier.
220 #[stability::unstable]
221 pub fn from_max_po2(po2_max: usize) -> Self {
222 Self {
223 hashfn: "poseidon2".to_string(),
224 prove_guest_errors: false,
225 receipt_kind: ReceiptKind::Composite,
226 control_ids: crate::receipt::succinct::allowed_control_ids("poseidon2", po2_max)
227 .unwrap()
228 .collect(),
229 max_segment_po2: po2_max,
230 }
231 }
232
233 /// Construct a verifier context that will accept receipts with control any of the default
234 /// control ID associated with cycle counts of all supported powers of two (po2).
235 #[stability::unstable]
236 pub fn all_po2s() -> Self {
237 Self::from_max_po2(risc0_zkp::MAX_CYCLES_PO2)
238 }
239
240 /// Choose the fastest prover options. Receipt will be linear in length of the execution,
241 /// and does not support compression via recursion.
242 pub fn fast() -> Self {
243 Self {
244 hashfn: "poseidon2".to_string(),
245 prove_guest_errors: false,
246 receipt_kind: ReceiptKind::Composite,
247 control_ids: Vec::new(),
248 max_segment_po2: DEFAULT_MAX_PO2,
249 }
250 }
251
252 /// Choose the prover that generates composite receipts, linear in the length of the execution,
253 /// and supports compression via recursion.
254 pub fn composite() -> Self {
255 Self {
256 hashfn: "poseidon2".to_string(),
257 prove_guest_errors: false,
258 receipt_kind: ReceiptKind::Composite,
259 control_ids: ALLOWED_CONTROL_IDS.to_vec(),
260 max_segment_po2: DEFAULT_MAX_PO2,
261 }
262 }
263
264 /// Choose the prover that generates succinct receipts, which are constant size in the length
265 /// of execution.
266 pub fn succinct() -> Self {
267 Self {
268 hashfn: "poseidon2".to_string(),
269 prove_guest_errors: false,
270 receipt_kind: ReceiptKind::Succinct,
271 control_ids: ALLOWED_CONTROL_IDS.to_vec(),
272 max_segment_po2: DEFAULT_MAX_PO2,
273 }
274 }
275
276 /// Choose the prover that generates Groth16 receipts which are constant size in the length of
277 /// the execution and small enough to verify on blockchains, like Ethereum.
278 ///
279 /// Only supported for x86_64 Linux with Docker installed.
280 pub fn groth16() -> Self {
281 Self {
282 hashfn: "poseidon2".to_string(),
283 prove_guest_errors: false,
284 receipt_kind: ReceiptKind::Groth16,
285 control_ids: ALLOWED_CONTROL_IDS.to_vec(),
286 max_segment_po2: DEFAULT_MAX_PO2,
287 }
288 }
289
290 /// Return [ProverOpts] with the hashfn set to the given value.
291 pub fn with_hashfn(self, hashfn: String) -> Self {
292 Self {
293 hashfn: hashfn.to_owned(),
294 ..self
295 }
296 }
297
298 /// Return [ProverOpts] with prove_guest_errors set to the given value.
299 pub fn with_prove_guest_errors(self, prove_guest_errors: bool) -> Self {
300 Self {
301 prove_guest_errors,
302 ..self
303 }
304 }
305
306 /// Return [ProverOpts] with the receipt_kind set to the given value.
307 pub fn with_receipt_kind(self, receipt_kind: ReceiptKind) -> Self {
308 Self {
309 receipt_kind,
310 ..self
311 }
312 }
313
314 /// Return [ProverOpts] with the control_ids set to the given value.
315 pub fn with_control_ids(self, control_ids: Vec<Digest>) -> Self {
316 Self {
317 control_ids,
318 ..self
319 }
320 }
321
322 /// Return [ProverOpts] with the max_segment_po2 set to the given value.
323 #[stability::unstable]
324 pub fn with_segment_po2_max(self, max_segment_po2: usize) -> Self {
325 Self {
326 max_segment_po2,
327 ..self
328 }
329 }
330
331 #[cfg(feature = "prove")]
332 pub(crate) fn hash_suite(
333 &self,
334 ) -> Result<risc0_zkp::core::hash::HashSuite<risc0_zkp::field::baby_bear::BabyBear>> {
335 risc0_zkp::core::hash::hash_suite_from_name(&self.hashfn)
336 .ok_or_else(|| anyhow::anyhow!("unsupported hash suite: {}", self.hashfn))
337 }
338}
339
340/// Return a default [Prover] based on environment variables and feature flags.
341///
342/// The `RISC0_PROVER` environment variable, if specified, will select the
343/// following [Prover] implementation:
344/// * `bonsai`: [BonsaiProver] to prove on Bonsai.
345/// * `local`: LocalProver to prove locally in-process. Note: this
346/// requires the `prove` feature flag.
347/// * `ipc`: [ExternalProver] to prove using an `r0vm` sub-process. Note: `r0vm`
348/// must be installed. To specify the path to `r0vm`, use `RISC0_SERVER_PATH`.
349///
350/// If `RISC0_PROVER` is not specified, the following rules are used to select a
351/// [Prover]:
352/// * [BonsaiProver] if the `BONSAI_API_URL` and `BONSAI_API_KEY` environment
353/// variables are set unless `RISC0_DEV_MODE` is enabled.
354/// * LocalProver if the `prove` feature flag is enabled.
355/// * [ExternalProver] otherwise.
356pub fn default_prover() -> Rc<dyn Prover> {
357 let explicit = std::env::var("RISC0_PROVER").unwrap_or_default();
358 if !explicit.is_empty() {
359 return match explicit.to_lowercase().as_str() {
360 #[cfg(feature = "bonsai")]
361 "bonsai" => Rc::new(BonsaiProver::new("bonsai")),
362 "ipc" => Rc::new(ExternalProver::new("ipc", get_r0vm_path().unwrap())),
363 #[cfg(feature = "prove")]
364 "local" => Rc::new(self::local::LocalProver::new("local")),
365 _ => unimplemented!("Unsupported prover: {explicit}"),
366 };
367 }
368
369 #[cfg(feature = "bonsai")]
370 {
371 if !is_dev_mode()
372 && std::env::var("BONSAI_API_URL").is_ok()
373 && std::env::var("BONSAI_API_KEY").is_ok()
374 {
375 return Rc::new(BonsaiProver::new("bonsai"));
376 }
377 }
378
379 if cfg!(feature = "prove") {
380 #[cfg(feature = "prove")]
381 return Rc::new(self::local::LocalProver::new("local"));
382 }
383
384 Rc::new(ExternalProver::new("ipc", get_r0vm_path().unwrap()))
385}
386
387/// Return a default [Executor] based on environment variables and feature
388/// flags.
389///
390/// The `RISC0_EXECUTOR` environment variable, if specified, will select the
391/// following [Executor] implementation:
392/// * `local`: LocalProver to execute locally in-process. Note: this is
393/// only available when the `prove` feature is enabled.
394/// * `ipc`: [ExternalProver] to execute using an `r0vm` sub-process. Note:
395/// `r0vm` must be installed. To specify the path to `r0vm`, use
396/// `RISC0_SERVER_PATH`.
397///
398/// If `RISC0_EXECUTOR` is not specified, the following rules are used to select
399/// an [Executor]:
400/// * LocalProver if the `prove` feature flag is enabled.
401/// * [ExternalProver] otherwise.
402pub fn default_executor() -> Rc<dyn Executor> {
403 let explicit = std::env::var("RISC0_EXECUTOR").unwrap_or_default();
404 if !explicit.is_empty() {
405 return match explicit.to_lowercase().as_str() {
406 "ipc" => Rc::new(ExternalProver::new("ipc", get_r0vm_path().unwrap())),
407 #[cfg(feature = "prove")]
408 "local" => Rc::new(self::local::LocalProver::new("local")),
409 _ => unimplemented!("Unsupported executor: {explicit}"),
410 };
411 }
412
413 if cfg!(feature = "prove") {
414 #[cfg(feature = "prove")]
415 return Rc::new(self::local::LocalProver::new("local"));
416 }
417
418 Rc::new(ExternalProver::new("ipc", get_r0vm_path().unwrap()))
419}
420
421/// Return a local [Executor].
422#[cfg(feature = "prove")]
423pub fn local_executor() -> Rc<dyn Executor> {
424 Rc::new(self::local::LocalProver::new("local"))
425}
426
427pub(crate) fn get_r0vm_path() -> Result<PathBuf> {
428 if let Ok(path) = std::env::var("RISC0_SERVER_PATH") {
429 let path = PathBuf::from(path);
430 if path.is_file() {
431 return Ok(path);
432 }
433 }
434
435 let mut version = get_version().map_err(|err| anyhow!(err))?;
436 tracing::debug!("version: {version}");
437
438 if let Ok(rzup) = rzup::Rzup::new() {
439 if let Ok(dir) = rzup.get_version_dir(&rzup::Component::R0Vm, &version) {
440 return Ok(dir.join("r0vm"));
441 }
442
443 // Try again, but with these fields stripped
444 version.patch = 0;
445 version.build = Default::default();
446
447 if let Ok(dir) = rzup.get_version_dir(&rzup::Component::R0Vm, &version) {
448 return Ok(dir.join("r0vm"));
449 }
450 }
451
452 Ok("r0vm".into())
453}