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