Skip to main content

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:

bash
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:

bash
cargo-zisk new fibonacci
cd fibonacci

Download the sample input the guide uses and place it where the later commands expect it:

bash
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.

guest/src/main.rs
#![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.

guest/src/main.rs
#![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:

common/src/lib.rs
/// 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:

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:

guest/src/main.rs
#![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.

guest/src/main.rs
#![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:

bash
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:

bash
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:

bash
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
info

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:

bash
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:

bash
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:

bash
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:

bash
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:

bash
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.