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.
Guest and Host Programs
RISC Zero applications follow a unique architecture that separates code into two distinct execution environments: the guest (running inside the zkVM) and the host (running outside the zkVM). Understanding this separation is crucial for effective zkVM development.
The Guest/Host Model
What is the Guest?
The guest is code that executes inside the zkVM. It represents the computation you want to prove:
Runs in a RISC-V environment
All execution is proven
Has access to private inputs from the host
Can write public outputs to the journal
Cannot perform unproven operations (network I/O, system calls)
What is the Host?
The host is standard code that runs outside the zkVM:
Manages guest execution
Provides input data to the guest
Generates and verifies proofs
Handles I/O, networking, and other system operations
Can be written in any language (typically Rust)
Think of the guest as the “prover’s secret code” and the host as the “coordinator” that sets up execution and manages proofs.
Communication Model
The guest and host communicate through well-defined channels:
┌─────────────────────────────────────────────┐
│ HOST PROGRAM │
│ │
│ ┌──────────────────────────────────────┐ │
│ │ ExecutorEnv (Input Data) │ │
│ └───────────────┬──────────────────────┘ │
│ │ write() │
│ ▼ │
│ ┌─────────────────────────────────────┐ │
│ │ GUEST PROGRAM (zkVM) │ │
│ │ │ │
│ │ env::read() ──────► Process │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ env::commit() │ │
│ └─────────────┬───────────────────────┘ │
│ │ Journal Output │
│ ▼ │
│ ┌─────────────────────────────────────┐ │
│ │ Receipt (Proof + Journal) │ │
│ └─────────────────────────────────────┘ │
└─────────────────────────────────────────────┘
The host sends data to the guest using the ExecutorEnv:
use risc0_zkvm :: ExecutorEnv ;
// Host code: prepare input
let env = ExecutorEnv :: builder ()
. write ( & secret_a ) ?
. write ( & secret_b ) ?
. build () ? ;
The guest receives this data with env::read():
use risc0_zkvm :: guest :: env;
// Guest code: read input
let a : u64 = env :: read ();
let b : u64 = env :: read ();
Input data is read in FIFO order - the guest reads data in the same sequence the host wrote it.
Output: Guest → Host
The guest writes public outputs to the journal with env::commit():
// Guest code: write output
let result = a * b ;
env :: commit ( & result );
The host extracts journal data from the receipt:
// Host code: extract output
let result : u64 = receipt . journal . decode () ? ;
println! ( "Result: {}" , result );
Writing Guest Code
Guest Structure
A minimal guest program looks like this:
#![no_main]
#![no_std]
use risc0_zkvm :: guest :: env;
risc0_zkvm :: guest :: entry! ( main );
fn main () {
// Read inputs
let a : u64 = env :: read ();
let b : u64 = env :: read ();
// Perform computation
let product = a . checked_mul ( b ) . expect ( "Integer overflow" );
// Commit public output
env :: commit ( & product );
}
Key elements:
#![no_main] : Guest uses custom entry point
#![no_std] : No standard library by default (use std feature if needed)
risc0_zkvm::guest::entry!(main) : Defines the guest entry point
By default, guest code runs in a no_std environment to minimize the code size and complexity. This means:
Only core library is available
No heap allocation by default
No file system, networking, or OS features
You can enable std support by adding the feature flag: [ dependencies ]
risc0-zkvm = { version = "1.0" , default-features = false , features = [ "std" ] }
With std enabled, you can omit #![no_main] and #![no_std], and use many standard library features.
Guest I/O Functions
The env module provides several I/O functions (from risc0/zkvm/src/guest/env/mod.rs):
// Read any deserializable type
let data : MyStruct = env :: read ();
// Read raw bytes (more efficient)
let mut buffer = [ 0 u8 ; 1024 ];
env :: read_slice ( & mut buffer );
// Access stdin directly
use risc0_zkvm :: guest :: env :: stdin;
let mut stdin = stdin ();
Writing Output
// Commit to journal (public output)
env :: commit ( & public_data );
// Commit raw bytes
env :: commit_slice ( & bytes );
// Write to stdout (debugging)
env :: write ( & debug_info );
// Access journal directly
use risc0_zkvm :: guest :: env :: journal;
let mut journal = journal ();
File Descriptors
Guest code can access standard file descriptors:
stdin(): Read private input from host
stdout(): Write data (not proven)
stderr(): Write error messages (not proven)
journal(): Write public outputs (proven)
Only data written to the journal via env::commit() is proven and publicly verifiable. Data written to stdout/stderr is for debugging only.
Guest Control Flow
use risc0_zkvm :: guest :: env;
// Normal exit (success)
env :: exit ( 0 );
// Exit with error code
env :: exit ( 1 );
// Pause execution (can be resumed)
env :: pause ( 0 );
Writing Host Code
Host Structure
A complete host program:
use risc0_zkvm :: {default_prover, ExecutorEnv };
use my_methods :: MULTIPLY_ELF ;
fn main () {
// 1. Prepare input
let a = 17 u64 ;
let b = 23 u64 ;
// 2. Build execution environment
let env = ExecutorEnv :: builder ()
. write ( & a ) . unwrap ()
. write ( & b ) . unwrap ()
. build ()
. unwrap ();
// 3. Obtain prover
let prover = default_prover ();
// 4. Execute and prove
let prove_info = prover . prove ( env , MULTIPLY_ELF ) . unwrap ();
// 5. Extract receipt
let receipt = prove_info . receipt;
// 6. Decode output
let result : u64 = receipt . journal . decode () . unwrap ();
println! ( "Result: {}" , result );
}
Execution Environment Options
The ExecutorEnvBuilder supports many configuration options:
let env = ExecutorEnv :: builder ()
// Input data
. write ( & data ) ?
. write_slice ( & bytes )
// Environment variables
. env_var ( "DEBUG" , "1" )
// Segment configuration
. segment_limit_po2 ( 20 ) // 2^20 cycles per segment
// Add trace callback for debugging
. trace_callback ( my_trace_fn )
// Include assumption receipts (for proof composition)
. add_assumption ( assumption_receipt )
. build () ? ;
Lowering segment_limit_po2 reduces memory usage. A value of 20 (2^20 = ~1M cycles) is typical. Decreasing by 1 roughly halves memory consumption.
Prover Options
The host can choose different provers and receipt types:
use risc0_zkvm :: { ProverOpts , ReceiptKind };
let opts = ProverOpts {
receipt_kind : ReceiptKind :: Succinct , // or Groth16, Composite
};
let prove_info = prover . prove_with_opts ( env , ELF , & opts ) ? ;
Receipt kinds:
Succinct : Recursively composed STARK (constant size)
Groth16 : SNARK for on-chain verification
Composite : Multiple receipts with assumptions
Development Mode
During development, enable dev mode to skip actual proving:
This generates fake receipts instantly, useful for:
Testing logic without waiting for proofs
Debugging guest code
Rapid iteration
Never use dev mode in production! Fake receipts provide no security guarantees. Use the disable-dev-mode feature flag to prevent accidental misuse.
Advanced Guest Patterns
Proof Composition
Guests can verify other receipts, enabling modular proofs:
use risc0_zkvm :: guest :: env;
// Guest verifies another receipt
let receipt_data : Vec < u8 > = env :: read ();
let receipt : Receipt = bincode :: deserialize ( & receipt_data ) ? ;
receipt . verify ( EXPECTED_IMAGE_ID )
. expect ( "Receipt verification failed" );
// Use the verified receipt's journal
let input : DataType = receipt . journal . decode () ? ;
Accessing Verified Outputs
When verifying receipts in the guest:
use risc0_zkvm :: guest :: env;
// Verify and extract output in one call
env :: verify ( receipt_bytes , IMAGE_ID ) ? ;
// Or verify integrity only (doesn't check image ID or exit code)
env :: verify_integrity ( receipt_bytes ) ? ;
Private vs Public Data
Design your guest to maintain privacy:
// Guest code
let private_input : Secret = env :: read (); // Never revealed
let public_param : u64 = env :: read (); // Could be committed or not
let result = compute_with_secret ( private_input , public_param );
// Only commit what should be public
env :: commit ( & result ); // Proven output
Common Patterns
Batch Processing
// Host
let items : Vec < Item > = load_items ();
let env = ExecutorEnv :: builder ()
. write ( & items ) ?
. build () ? ;
// Guest
let items : Vec < Item > = env :: read ();
let results : Vec < Output > = items . iter () . map ( process ) . collect ();
env :: commit ( & results );
Conditional Output
// Guest
let data : InputData = env :: read ();
if data . is_valid () {
let result = process ( data );
env :: commit ( & result );
env :: exit ( 0 );
} else {
env :: exit ( 1 ); // Error exit
}
Incremental Computation
// Guest can pause and resume
for batch in batches {
process_batch ( batch );
env :: commit ( & intermediate_state );
env :: pause ( 0 ); // Can resume later
}
env :: exit ( 0 );
Use Slice Methods
For large data, use _slice variants:
// Less efficient (serialization overhead)
let data : Vec < u8 > = env :: read ();
// More efficient (direct memory copy)
let mut buffer = vec! [ 0 u8 ; size ];
env :: read_slice ( & mut buffer );
Minimize Journal Writes
Journal writes are proven operations:
// Less efficient - multiple commits
for item in items {
env :: commit ( & process ( item ));
}
// More efficient - single commit
let results : Vec < _ > = items . iter () . map ( process ) . collect ();
env :: commit ( & results );
Cycle Counting
Profile guest performance:
use risc0_zkvm :: guest :: env;
let start = env :: cycle_count ();
expensive_operation ();
let end = env :: cycle_count ();
env :: log ( & format! ( "Operation took {} cycles" , end - start ));
Next Steps