risc0_zkvm/host/client/
env.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//! This module defines the [ExecutorEnv] and [ExecutorEnvBuilder].
16
17use std::{
18    cell::RefCell,
19    collections::HashMap,
20    io::{BufRead, BufReader, Cursor, Read, Write},
21    mem,
22    path::{Path, PathBuf},
23    rc::Rc,
24    sync::Arc,
25};
26
27use anyhow::{bail, Result};
28use bytemuck::Pod;
29use bytes::Bytes;
30use risc0_circuit_keccak::{KeccakState, KECCAK_PO2_RANGE};
31use risc0_zkp::core::digest::Digest;
32use risc0_zkvm_platform::{self, fileno};
33use serde::Serialize;
34use tempfile::TempDir;
35
36use crate::{
37    host::client::{
38        posix_io::PosixIo,
39        slice_io::{slice_io_from_fn, SliceIo, SliceIoTable},
40    },
41    serde::to_vec,
42    AssumptionReceipt, TraceCallback,
43};
44
45/// A builder pattern used to construct an [ExecutorEnv].
46#[derive(Default)]
47pub struct ExecutorEnvBuilder<'a> {
48    inner: ExecutorEnv<'a>,
49}
50
51#[allow(dead_code)]
52#[derive(Clone)]
53pub enum SegmentPath {
54    TempDir(Arc<TempDir>),
55    Path(PathBuf),
56}
57
58impl SegmentPath {
59    pub(crate) fn path(&self) -> &Path {
60        match self {
61            Self::TempDir(dir) => dir.path(),
62            Self::Path(path) => path.as_path(),
63        }
64    }
65}
66
67/// A ZKR proof request.
68#[stability::unstable]
69pub struct ProveZkrRequest {
70    /// The digest of the claim that this ZKR program is expected to produce.
71    pub claim_digest: Digest,
72
73    /// The control ID uniquely identifies the ZKR program to be proven.
74    pub control_id: Digest,
75
76    /// The input that the ZKR program should operate on.
77    pub input: Vec<u8>,
78}
79
80/// A Keccak proof request.
81#[stability::unstable]
82pub struct ProveKeccakRequest {
83    /// The digest of the claim that this keccak input is expected to produce.
84    pub claim_digest: Digest,
85
86    /// The requested size of the keccak proof, in powers of 2.
87    pub po2: usize,
88
89    /// The control root which identifies a particular keccak circuit revision.
90    pub control_root: Digest,
91
92    /// Input transcript to provide to the keccak circuit.
93    pub input: Vec<KeccakState>,
94}
95
96/// A trait that supports the ability to be notified of proof requests
97/// on-demand.
98#[stability::unstable]
99pub trait CoprocessorCallback {
100    /// Request that a ZKR proof is produced.
101    fn prove_zkr(&mut self, request: ProveZkrRequest) -> Result<()>;
102
103    /// Request that a keccak proof is produced.
104    fn prove_keccak(&mut self, request: ProveKeccakRequest) -> Result<()>;
105}
106
107pub type CoprocessorCallbackRef<'a> = Rc<RefCell<dyn CoprocessorCallback + 'a>>;
108
109/// Container for assumptions in the executor environment.
110#[derive(Default)]
111pub(crate) struct AssumptionReceipts(pub(crate) Vec<AssumptionReceipt>);
112
113/// The [Executor][crate::Executor] is configured from this object.
114///
115/// The executor environment holds configuration details that inform how the
116/// guest environment is set up prior to guest program execution.
117#[derive(Default)]
118pub struct ExecutorEnv<'a> {
119    pub(crate) env_vars: HashMap<String, String>,
120    pub(crate) args: Vec<String>,
121    pub(crate) segment_limit_po2: Option<u32>,
122    pub(crate) session_limit: Option<u64>,
123    pub(crate) posix_io: Rc<RefCell<PosixIo<'a>>>,
124    pub(crate) slice_io: Rc<RefCell<SliceIoTable<'a>>>,
125    pub(crate) input: Vec<u8>,
126    pub(crate) trace: Vec<Rc<RefCell<dyn TraceCallback + 'a>>>,
127    pub(crate) assumptions: Rc<RefCell<AssumptionReceipts>>,
128    pub(crate) segment_path: Option<SegmentPath>,
129    pub(crate) pprof_out: Option<PathBuf>,
130    pub(crate) input_digest: Option<Digest>,
131    pub(crate) coprocessor: Option<CoprocessorCallbackRef<'a>>,
132}
133
134impl<'a> ExecutorEnv<'a> {
135    /// Construct a [ExecutorEnvBuilder].
136    ///
137    /// # Example
138    ///
139    /// ```
140    /// use risc0_zkvm::ExecutorEnv;
141    ///
142    /// let env = ExecutorEnv::builder().build();
143    /// ```
144    pub fn builder() -> ExecutorEnvBuilder<'a> {
145        ExecutorEnvBuilder::default()
146    }
147}
148
149impl<'a> ExecutorEnvBuilder<'a> {
150    /// Finalize this builder to construct an [ExecutorEnv].
151    ///
152    /// # Example
153    ///
154    /// ```
155    /// use risc0_zkvm::ExecutorEnv;
156    ///
157    /// let env = ExecutorEnv::builder().build().unwrap();
158    /// ```
159    ///
160    /// After calling `build`, the [ExecutorEnvBuilder] will be reset to
161    /// default.
162    pub fn build(&mut self) -> Result<ExecutorEnv<'a>> {
163        let mut inner = mem::take(&mut self.inner);
164
165        if !inner.input.is_empty() {
166            let reader = Cursor::new(inner.input.clone());
167            inner
168                .posix_io
169                .borrow_mut()
170                .with_read_fd(fileno::STDIN, reader);
171        }
172
173        if inner.pprof_out.is_none() {
174            if let Ok(env_var) = std::env::var("RISC0_PPROF_OUT") {
175                inner.pprof_out = Some(env_var.into());
176            }
177        }
178
179        if let Ok(po2) = std::env::var("RISC0_KECCAK_PO2") {
180            let po2_val = po2.parse::<u32>()?;
181            if !KECCAK_PO2_RANGE.contains(&(po2_val as usize)) {
182                bail!(
183                    "invalid keccak po2 {po2}. Expected range: {:?}",
184                    KECCAK_PO2_RANGE
185                );
186            }
187            inner.env_vars.insert("RISC0_KECCAK_PO2".to_string(), po2);
188        }
189
190        Ok(inner)
191    }
192
193    /// Set a segment limit, specified in powers of 2 cycles.
194    ///
195    /// Lowering this value will reduce the memory consumption of the prover. Memory consumption is
196    /// roughly linear with the segment size, so lowering this value by 1 will cut memory
197    /// consumpton by about half.
198    ///
199    /// The default value is chosen to be performant on commonly used hardware. Tuning this value,
200    /// either up or down, may result in better proving performance.
201    ///
202    /// Given value must be between [risc0_zkp::MIN_CYCLES_PO2] and
203    /// [risc0_zkp::MAX_CYCLES_PO2] (inclusive).
204    pub fn segment_limit_po2(&mut self, limit: u32) -> &mut Self {
205        self.inner.segment_limit_po2 = Some(limit);
206        self
207    }
208
209    /// Set a session limit, specified in number of cycles.
210    ///
211    /// # Example
212    ///
213    /// ```
214    /// use risc0_zkvm::ExecutorEnv;
215    ///
216    /// let env = ExecutorEnv::builder()
217    ///     .session_limit(Some(32 * 1024 * 1024)) // 32M cycles
218    ///     .build()
219    ///     .unwrap();
220    /// ```
221    pub fn session_limit(&mut self, limit: Option<u64>) -> &mut Self {
222        self.inner.session_limit = limit;
223        self
224    }
225
226    /// Add environment variables to the guest environment.
227    ///
228    /// # Example
229    ///
230    /// ```
231    /// use std::collections::HashMap;
232    /// use risc0_zkvm::ExecutorEnv;
233    ///
234    /// let mut vars = HashMap::new();
235    /// vars.insert("VAR1".to_string(), "SOME_VALUE".to_string());
236    /// vars.insert("VAR2".to_string(), "SOME_VALUE".to_string());
237    ///
238    /// let env = ExecutorEnv::builder()
239    ///     .env_vars(vars)
240    ///     .build()
241    ///     .unwrap();
242    /// ```
243    pub fn env_vars(&mut self, vars: HashMap<String, String>) -> &mut Self {
244        self.inner.env_vars = vars;
245        self
246    }
247
248    /// Add an argument array to the guest environment.
249    ///
250    /// # Example
251    /// ```
252    /// # use risc0_zkvm::ExecutorEnv;
253    ///
254    /// let env = ExecutorEnv::builder()
255    ///     .args(&["grep".to_string(), "-c".to_string(), "foo".to_string(), "-".to_string()])
256    ///     .build()
257    ///     .unwrap();
258    /// ```
259    pub fn args(&mut self, args: &[String]) -> &mut Self {
260        self.inner.args.extend_from_slice(args);
261        self
262    }
263
264    /// Add an environment variable to the guest environment.
265    ///
266    /// # Example
267    ///
268    /// ```
269    /// use risc0_zkvm::ExecutorEnv;
270    ///
271    /// let env = ExecutorEnv::builder()
272    ///     .env_var("VAR1", "SOME_VALUE")
273    ///     .build()
274    ///     .unwrap();
275    /// ```
276    pub fn env_var(&mut self, name: &str, val: &str) -> &mut Self {
277        self.inner
278            .env_vars
279            .insert(name.to_string(), val.to_string());
280        self
281    }
282
283    /// Write input data to the zkVM guest stdin.
284    ///
285    /// This function will serialize `data` using a zkVM-optimized codec that
286    /// can be deserialized in the guest with a corresponding `env::read` with
287    /// the same data type.
288    ///
289    /// # Example
290    ///
291    /// ```
292    /// use risc0_zkvm::ExecutorEnv;
293    /// use serde::Serialize;
294    ///
295    /// #[derive(Serialize)]
296    /// struct Input {
297    ///     a: u32,
298    ///     b: u32,
299    /// }
300    ///
301    /// let input1 = Input{ a: 1, b: 2 };
302    /// let input2 = Input{ a: 3, b: 4 };
303    /// let env = ExecutorEnv::builder()
304    ///     .write(&input1).unwrap()
305    ///     .write(&input2).unwrap()
306    ///     .build()
307    ///     .unwrap();
308    /// ```
309    pub fn write<T: Serialize>(&mut self, data: &T) -> Result<&mut Self> {
310        Ok(self.write_slice(&to_vec(data)?))
311    }
312
313    /// Write input data to the zkVM guest stdin.
314    ///
315    /// This function writes a slice directly to the underlying buffer. A
316    /// corresponding `env::read_slice` can be used within the guest to read the
317    /// data.
318    ///
319    /// # Example
320    ///
321    /// ```
322    /// use risc0_zkvm::ExecutorEnv;
323    ///
324    /// let slice1 = [0, 1, 2, 3];
325    /// let slice2 = [3, 2, 1, 0];
326    /// let env = ExecutorEnv::builder()
327    ///     .write_slice(&slice1)
328    ///     .write_slice(&slice2)
329    ///     .build()
330    ///     .unwrap();
331    /// ```
332    pub fn write_slice<T: Pod>(&mut self, slice: &[T]) -> &mut Self {
333        self.inner
334            .input
335            .extend_from_slice(bytemuck::cast_slice(slice));
336        self
337    }
338
339    /// Write a frame to the zkVM guest via stdin.
340    ///
341    /// A frame contains a length header along with the payload. Reading a frame
342    /// can be more efficient than deserializing a message on-demand. On-demand
343    /// deserialization can cause many syscalls, whereas a frame will only have
344    /// two.
345    #[stability::unstable]
346    pub fn write_frame(&mut self, payload: &[u8]) -> &mut Self {
347        let len = payload.len() as u32;
348        self.inner.input.extend_from_slice(&len.to_le_bytes());
349        self.inner.input.extend_from_slice(payload);
350        self
351    }
352
353    /// Add a posix-style standard input.
354    pub fn stdin(&mut self, reader: impl Read + 'a) -> &mut Self {
355        self.read_fd(fileno::STDIN, BufReader::new(reader))
356    }
357
358    /// Add a posix-style standard output.
359    pub fn stdout(&mut self, writer: impl Write + 'a) -> &mut Self {
360        self.write_fd(fileno::STDOUT, writer)
361    }
362
363    /// Add a posix-style standard error.
364    pub fn stderr(&mut self, writer: impl Write + 'a) -> &mut Self {
365        self.write_fd(fileno::STDERR, writer)
366    }
367
368    /// Add a posix-style file descriptor for reading.
369    pub fn read_fd(&mut self, fd: u32, reader: impl BufRead + 'a) -> &mut Self {
370        self.inner.posix_io.borrow_mut().with_read_fd(fd, reader);
371        self
372    }
373
374    /// Add a posix-style file descriptor for writing.
375    pub fn write_fd(&mut self, fd: u32, writer: impl Write + 'a) -> &mut Self {
376        self.inner.posix_io.borrow_mut().with_write_fd(fd, writer);
377        self
378    }
379
380    /// Add a handler for simple I/O handling.
381    pub fn slice_io(&mut self, channel: &str, handler: impl SliceIo + 'a) -> &mut Self {
382        self.inner
383            .slice_io
384            .borrow_mut()
385            .with_handler(channel, handler);
386        self
387    }
388
389    /// Add a handler for simple I/O handling.
390    pub fn io_callback<C: AsRef<str>>(
391        &mut self,
392        channel: C,
393        callback: impl Fn(Bytes) -> Result<Bytes> + 'a,
394    ) -> &mut Self {
395        self.inner
396            .slice_io
397            .borrow_mut()
398            .with_handler(channel.as_ref(), slice_io_from_fn(callback));
399        self
400    }
401
402    /// Add an [AssumptionReceipt] to the [ExecutorEnv], for use in [composition].
403    ///
404    /// During execution, when the guest calls `env::verify` or `env::verify_integrity`, this
405    /// collection will be searched for an [AssumptionReceipt] that corresponds the verification
406    /// call.
407    ///
408    /// Either a [crate::Receipt] or a [crate::ReceiptClaim] can be provided. If a [crate::Receipt]
409    /// is provided, then an [AssumptionReceipt::Proven] will be added to the [ExecutorEnv]
410    /// and the [crate::Receipt] generated by proving will be unconditional.
411    ///
412    /// [composition]: https://dev.risczero.com/terminology#composition
413    pub fn add_assumption(&mut self, assumption: impl Into<AssumptionReceipt>) -> &mut Self {
414        self.inner
415            .assumptions
416            .borrow_mut()
417            .0
418            .push(assumption.into());
419        self
420    }
421
422    /// Add a callback handler for raw trace messages.
423    pub fn trace_callback(&mut self, callback: impl TraceCallback + 'a) -> &mut Self {
424        self.inner.trace.push(Rc::new(RefCell::new(callback)));
425        self
426    }
427
428    /// Set the path where segments will be stored.
429    pub fn segment_path<P: AsRef<Path>>(&mut self, path: P) -> &mut Self {
430        self.inner.segment_path = Some(SegmentPath::Path(path.as_ref().to_path_buf()));
431        self
432    }
433
434    /// Enable the profiler and output results to the specified path.
435    pub fn enable_profiler<P: AsRef<Path>>(&mut self, path: P) -> &mut Self {
436        self.inner.pprof_out = Some(path.as_ref().to_path_buf());
437        self
438    }
439
440    /// Set the input digest.
441    pub fn input_digest(&mut self, digest: Digest) -> &mut Self {
442        self.inner.input_digest = Some(digest);
443        self
444    }
445
446    /// Add a callback for coprocessor requests.
447    #[stability::unstable]
448    pub fn coprocessor_callback(&mut self, callback: impl CoprocessorCallback + 'a) -> &mut Self {
449        self.inner.coprocessor = Some(Rc::new(RefCell::new(callback)));
450        self
451    }
452
453    /// Add a callback for coprocessor requests.
454    #[stability::unstable]
455    pub fn coprocessor_callback_ref(&mut self, callback: CoprocessorCallbackRef<'a>) -> &mut Self {
456        self.inner.coprocessor = Some(callback);
457        self
458    }
459}