Your first guest program
This guide walks through writing your first ZisK guest program and proving it step by step, using a fibonacci computation as the running example. It introduces the core concepts: the entry point, reading private inputs, computing with them, and committing public outputs.
Create the project
You have two ways to get a working ZisK project for this guide. Pick whichever fits your situation; the rest of the guide is identical either way.
Clone the examples repository
If you want the finished version of the program this guide builds, or
just want to skim a complete project before writing your own, clone the
companion examples repo and move into the fibonacci directory:
git clone https://github.com/0xPolygonHermez/zisk.git
cd zisk/examples/fibonacci
This is the same starting point used in the
Quickstart. The fibonacci directory
already contains the guest, host, and common crates wired together, so
you can follow along in the existing files instead of writing them
from scratch.
Scaffold a new project
To start from an empty project and write the program yourself, use the
cargo-zisk CLI to scaffold a new workspace. It handles workspace
setup, toolchain configuration, and dependency wiring so you can move
straight to writing logic:
cargo-zisk new fibonacci
cd fibonacci
Download the sample input the guide uses and place it where the later commands expect it:
mkdir -p samples
curl -L -o samples/example-input.bin https://raw.githubusercontent.com/0xPolygonHermez/zisk/refs/heads/main/examples/fibonacci/samples/example-input.bin
Either path lands you in a project with the same shape:
fibonacci/
├── Cargo.toml
├── common/ => Crate to define shared types and functionalities
│ ├── Cargo.toml between guest and host crates.
│ └── src/
| └── lib.rs
├── guest/ => The guest program is the one that is executed and
│ ├── Cargo.toml proven inside the zkVM.
│ ├── samples/
| | └── example-input.bin
│ └── src/
| └── main.rs
└── host/ => The host program is the one that drives execution
├── Cargo.toml and proving from outside the zkVM. It is not proven.
├── build.rs
└── src/
└──main.rs
For this guide we focus entirely on the guest program and drive
execution from the CLI using cargo-zisk. Later guides cover the ZisK
SDK, which lets you drive proving programmatically from the host.
Write the guest program
Fill in each file with the code below. Follow the sections in order; each one builds on the previous until you have a complete, working program.
Define the entry point
Every guest starts with two lines. #![no_main] tells the compiler
this is not a normal binary but that it runs inside ZisK's RISC-V VM, not
on your operating system. ziskos::entrypoint!(main) registers your
main function as the VM's entry point.
#![no_main]
ziskos::entrypoint!(main);
fn main() {
}
Read the input
The guest receives data through ZisK's I/O channel. io::read()
deserializes the next value from the input buffer into whatever type
you specify. Here the input provided will be a u32.
#![no_main]
ziskos::entrypoint!(main);
fn main() {
// Read the input from the guest's standard input.
let input = ziskos::io::read::<u8>();
Implement the program logic
Define the fibonacci function in the common crate. Keeping pure
logic out of the guest entry point lets reuse it from any other
crate in the workspace:
/// 256-bit unsigned integer re-exported from [`ruint`].
pub use ruint::aliases::U256;
/// Returns the zero-indexed *n*-th Fibonacci number as a [`U256`].
pub fn fibonacci(n: u8) -> U256 {
if n == 0 { return U256::ZERO; }
let mut a = U256::ZERO;
let mut b = U256::ONE;
for _ in 1..n {
let c = a + b;
a = b;
b = c;
}
b
}
As fibonacci numbers grow fast and
overflow a primitive integer within a few dozen terms, so we use U256,
a 256-bit unsigned integer from the ruint crate.
Add it as a dependency in common/Cargo.toml:
[dependencies]
ruint = { version = "1.18.0", default-features = false, features = ["serde", "alloc"] }
With the logic defined in common, switch back to the guest entry
point at guest/src/main.rs. Pull the function in and apply it to
the value read from the input buffer, so the computation actually
runs inside the zkVM:
#![no_main]
ziskos::entrypoint!(main);
use fibonacci_common::fibonacci;
fn main() {
// Read the input from the guest's standard input.
let input = ziskos::io::read::<u8>();
// Compute the value we want to prove.
let result = fibonacci(input);
}
At this point the result exists in memory but the verifier has no way to see it. The next step commits it as a public value in the proof.
Commit the public output
Public values are outputs that the verifier can inspect without
re-executing the program. Call io::commit() to write the
result to the public output channel.
#![no_main]
ziskos::entrypoint!(main);
use fibonacci_common::fibonacci;
fn main() {
// Read the input from the guest's standard input.
let input = ziskos::io::read::<u8>();
// Compute the value we want to prove.
let result = fibonacci(input);
// Commit the result as a public output so a verifier can inspect it
// without re-executing the program.
ziskos::io::commit(&result);
println!("fibonacci({input}) => {result}");
}
The println! is included only for debugging, it prints from inside
the VM during execution but is not part of the proof. Remove it for
production programs.
From code to proof
With the guest written, it is time to go from source code to a verified proof. This section walks through each stage of the pipeline: compiling, running, setting up the program, proving its execution, and verifying its proof.
Build the guest program
Compile the guest to a RISC-V ELF binary using the ZisK toolchain:
cd guest
cargo-zisk build --release
Compiling fibonacci-guest v0.1.0 (/zisk-examples/fibonacci/guest)
Finished `release` profile [optimized] target(s) in 1.56s
The compiled binary lands at:
/target/elf/riscv64ima-zisk-zkvm-elf/release/fibonacci-guest
The /elf/riscv64ima-zisk-zkvm-elf target in the path confirms the binary
was compiled for ZisK's RISC-V environment, not for your host machine.
This ELF is the exact program the zkVM will execute.
Run the guest program
Run the guest in the emulator to verify the output before committing to a full proving run:
cargo-zisk run --release -i samples/example-input.bin
fibonnaci(10) => 55
cargo-zisk run executes the guest in emulated mode and prints any
println! output from inside the VM. No proof is generated at this
stage, this is a fast sanity check to confirm the logic and inputs
are correct before spending time proving.
Set up the guest program
Before ZisK can generate a proof it needs a program setup derived from your specific guest binary. This key encodes the structure of the program so the prover can efficiently commit to the correct execution trace:
cargo-zisk setup --release
INFO: --- SETUP SUMMARY -------------
INFO: Setup completed for /root/zisk/examples/fibonacci/guest/target/elf/riscv64ima-zisk-zkvm-elf/release/fibonacci-guest
INFO: Program name: fibonacci-guest
INFO: Hash ID: 3f9abe9baa06555df4ca811012589b2ee37e4afcb05401d0f02b79a1b3c41d05
Setup is a one-time step per guest binary. Re-run it only when the guest source changes and you rebuild the ELF. You do not need to repeat it between proving runs with different inputs. It can take some time on the first run.
Prove the guest program
With setup complete, run the prover. It re-executes the guest over the given input, generates arithmetic constraints over the execution trace, and produces a cryptographic proof that the output is correct:
cargo-zisk prove --release -i samples/example-input.bin -o proof.bin
INFO: --- PROVE SUMMARY -------------
INFO: Proof generated in 145.978s, steps: 13099
INFO: Proof saved to proof.bin
By default the prover runs the guest through the software emulator. On
Linux x86_64 you can append --asm to switch to the native Assembly
executor, which is significantly faster for larger programs:
cargo-zisk prove --release -i samples/example-input.bin -o proof.bin --asm
If your machine has a CUDA-capable GPU, append --gpu to offload
proof generation to it for a substantial speedup:
cargo-zisk prove --release -i samples/example-input.bin -o proof.bin --gpu
--asm and --gpu are independent and can be combined for the
fastest configuration on supported hardware:
cargo-zisk prove --release -i samples/example-input.bin -o proof.bin --asm --gpu
Verify the proof
Verification checks the mathematical structure of the proof without re-executing the program. It is fast regardless of how expensive the original computation was:
cargo-zisk verify -p proof.bin
INFO: ✓ STARK proof was verified
INFO: --- VERIFICATION SUMMARY ---
INFO: time: 27 milliseconds
INFO: ----------------------------
Anyone can run this command and be cryptographically certain that the committed digest was produced by a correct execution of the guest, without seeing the input or repeating the computation.
Summary
You just took arbitrary computation and turned it into a cryptographic proof that anyone can verify instantly, without re-running the program or seeing the inputs. That is what ZisK does, and you have now done it end to end.
Next steps
Before building a real application, it is worth understanding how to write efficient guest programs. Proof generation cost scales directly with execution, and a well-optimized guest can be orders of magnitude cheaper to prove:
- Working with inputs: understand how inputs flow into the guest and how to structure them efficiently.
- Committing outputs: understand how outputs are committed to the proof and best practices around public values.
- Profiling your program: measure execution cost to find bottlenecks before they become expensive to prove.
When you are ready to prove your programs from code rather than the CLI, head to Your first proof to run the full pipeline using the SDK.