Documentation Index
Fetch the complete documentation index at: https://mintlify.com/risc0/risc0/llms.txt
Use this file to discover all available pages before exploring further.
The host is the untrusted machine that runs the zkVM, executes guest programs, and generates cryptographic proofs. The host sets up the execution environment, provides inputs, and collects outputs.
Overview
In a zkVM application, the host performs these key steps:
- Build an execution environment with inputs
- Execute the guest program
- Generate a proof (receipt)
- Verify the receipt (or pass it to a third party)
Basic Host Program
Here’s a minimal host program that executes a guest and generates a proof:
use risc0_zkvm::{default_prover, ExecutorEnv};
use hello_world_methods::MULTIPLY_ELF;
fn main() {
// Build the execution environment
let env = ExecutorEnv::builder().build().unwrap();
// Get the default prover
let prover = default_prover();
// Prove execution of the guest program
let receipt = prover.prove(env, MULTIPLY_ELF).unwrap().receipt;
println!("Proof generated successfully!");
}
Building the Execution Environment
The ExecutorEnv is where you configure the zkVM execution and provide inputs:
use risc0_zkvm::ExecutorEnv;
let env = ExecutorEnv::builder()
.write(&17u64).unwrap() // First input
.write(&23u64).unwrap() // Second input
.build()
.unwrap();
use risc0_zkvm::ExecutorEnv;
let data = vec![1u8, 2, 3, 4, 5];
let env = ExecutorEnv::builder()
.write_slice(&data)
.build()
.unwrap();
Writing Frames
Frames include a length header and are more efficient for large data:
use risc0_zkvm::ExecutorEnv;
let payload = b"large data payload";
let env = ExecutorEnv::builder()
.write_frame(payload)
.build()
.unwrap();
Complete Example with I/O
Here’s a real example from the hello-world demo:
use hello_world_methods::MULTIPLY_ELF;
use risc0_zkvm::{ExecutorEnv, Receipt, default_prover};
pub fn multiply(a: u64, b: u64) -> (Receipt, u64) {
// Build environment with inputs
let env = ExecutorEnv::builder()
.write(&a).unwrap()
.write(&b).unwrap()
.build()
.unwrap();
// Get the prover
let prover = default_prover();
// Execute and prove
let receipt = prover.prove(env, MULTIPLY_ELF).unwrap().receipt;
// Extract output from the journal
let c: u64 = receipt.journal.decode().expect(
"Journal output should deserialize into the same types (& order) that it was written",
);
println!("I know the factors of {c}, and I can prove it!");
(receipt, c)
}
Configuring the Executor Environment
Environment Variables
use risc0_zkvm::ExecutorEnv;
use std::collections::HashMap;
let mut vars = HashMap::new();
vars.insert("VAR1".to_string(), "VALUE1".to_string());
vars.insert("VAR2".to_string(), "VALUE2".to_string());
let env = ExecutorEnv::builder()
.env_vars(vars)
.build()
.unwrap();
Or add variables individually:
let env = ExecutorEnv::builder()
.env_var("DEBUG", "true")
.env_var("LOG_LEVEL", "info")
.build()
.unwrap();
Command-Line Arguments
use risc0_zkvm::ExecutorEnv;
let env = ExecutorEnv::builder()
.args(&[
"program".to_string(),
"--flag".to_string(),
"value".to_string(),
])
.build()
.unwrap();
POSIX I/O (stdin/stdout/stderr)
use risc0_zkvm::ExecutorEnv;
use std::io::Cursor;
let input_data = b"input from stdin";
let input = Cursor::new(input_data.to_vec());
let mut stdout_buf = Vec::new();
let mut stderr_buf = Vec::new();
let env = ExecutorEnv::builder()
.stdin(input)
.stdout(&mut stdout_buf)
.stderr(&mut stderr_buf)
.build()
.unwrap();
Segment Limits
Control memory usage by setting segment size:
use risc0_zkvm::ExecutorEnv;
let env = ExecutorEnv::builder()
.segment_limit_po2(20) // 2^20 cycles per segment
.build()
.unwrap();
Lowering the segment limit by 1 roughly cuts memory consumption in half. The default is tuned for common hardware.
Session Limits
Limit total execution cycles:
use risc0_zkvm::ExecutorEnv;
let env = ExecutorEnv::builder()
.session_limit(Some(32 * 1024 * 1024)) // 32M cycles
.build()
.unwrap();
Keccak Configuration
use risc0_zkvm::ExecutorEnv;
let env = ExecutorEnv::builder()
.keccak_max_po2(18).unwrap()
.build()
.unwrap();
Proof Composition
Use verified receipts as assumptions in new proofs:
use risc0_zkvm::{ExecutorEnv, Receipt};
fn compose_proofs(previous_receipt: Receipt) -> Receipt {
let env = ExecutorEnv::builder()
.add_assumption(previous_receipt).unwrap()
.build()
.unwrap();
// The guest can now call env::verify() on this receipt
let prover = default_prover();
prover.prove(env, COMPOSE_ELF).unwrap().receipt
}
CompositeReceipt cannot be used directly as an assumption. Compress it first using ProverOpts::succinct().
Verifying Receipts
Once you have a receipt, verify it against the expected image ID:
use hello_world_methods::{MULTIPLY_ELF, MULTIPLY_ID};
use risc0_zkvm::{default_prover, ExecutorEnv};
fn main() {
// Generate proof
let env = ExecutorEnv::builder().build().unwrap();
let prover = default_prover();
let receipt = prover.prove(env, MULTIPLY_ELF).unwrap().receipt;
// Verify the receipt
receipt.verify(MULTIPLY_ID).expect(
"Code you have proven should successfully verify"
);
println!("Receipt verified successfully!");
}
use risc0_zkvm::Receipt;
fn extract_output(receipt: Receipt) -> u64 {
// Decode journal output
let output: u64 = receipt.journal.decode().expect(
"Failed to decode journal"
);
output
}
Decoding Multiple Values
use serde::{Deserialize, Serialize};
use risc0_zkvm::Receipt;
#[derive(Serialize, Deserialize)]
struct Output {
result: u64,
hash: [u8; 32],
}
fn extract_structured(receipt: Receipt) -> Output {
receipt.journal.decode().expect("Failed to decode")
}
Prover Options
Customize proof generation:
use risc0_zkvm::{ExecutorEnv, ProverOpts, ReceiptKind, default_prover};
let env = ExecutorEnv::builder().build().unwrap();
let prover = default_prover();
// Generate different receipt types
let opts = ProverOpts::succinct(); // Compressed proof
let receipt = prover
.prove_with_opts(env, MULTIPLY_ELF, &opts)
.unwrap()
.receipt;
Error Handling
use risc0_zkvm::{ExecutorEnv, default_prover};
use anyhow::Result;
fn prove_with_error_handling() -> Result<()> {
let env = ExecutorEnv::builder()
.write(&42u64)?
.build()?;
let prover = default_prover();
match prover.prove(env, MULTIPLY_ELF) {
Ok(prove_info) => {
println!("Proof generated successfully!");
prove_info.receipt.verify(MULTIPLY_ID)?;
Ok(())
}
Err(e) => {
eprintln!("Proving failed: {}", e);
Err(e)
}
}
}
Advanced Features
Trace Callbacks
Monitor execution with custom callbacks:
use risc0_zkvm::{ExecutorEnv, TraceCallback, TraceEvent};
struct MyTracer;
impl TraceCallback for MyTracer {
fn trace_callback(&mut self, event: TraceEvent) {
// Process trace events
println!("Trace: {:?}", event);
}
}
let env = ExecutorEnv::builder()
.trace_callback(MyTracer)
.build()
.unwrap();
Profiling
Enable profiler output:
use risc0_zkvm::ExecutorEnv;
let env = ExecutorEnv::builder()
.enable_profiler("/tmp/profile.pb")
.build()
.unwrap();
Custom Segment Storage
use risc0_zkvm::ExecutorEnv;
use std::path::Path;
let env = ExecutorEnv::builder()
.segment_path("/custom/segment/path")
.build()
.unwrap();
Image IDs and ELF Binaries
When you build guest methods with risc0-build, it generates constants:
// Auto-generated in methods/src/lib.rs
pub const MULTIPLY_ELF: &[u8] = include_bytes!("...");
pub const MULTIPLY_ID: [u32; 8] = [...];
pub const MULTIPLY_PATH: &str = "...";
MULTIPLY_ELF - The compiled guest binary
MULTIPLY_ID - The image ID used for verification
MULTIPLY_PATH - Path to the ELF file
Best Practices
Always use ExecutorEnv::builder() for maximum flexibility and clarity.
Use Result<T> and the ? operator for clean error propagation.
Always verify receipts after generation, even in development, to catch issues early.
Ensure the order of write() calls in the host matches read() calls in the guest.
Use ProverOpts::succinct() when receipt size matters (e.g., on-chain verification).
See Also