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