Your First ZK Program
In the previous chapter, you ran a pre-compiled Fibonacci example. Now, let’s look at the actual Rust code for that guest program and understand how it works.
The goal of this program is to calculate the n-th Fibonacci number inside the Ceno zkVM and then commit the result to
the public output.
The Code
Here is the complete source code for the Fibonacci example (examples/examples/fibonacci.rs):
extern crate ceno_rt;
fn main() {
// Compute the (1 << log_n) 'th fibonacci number, using normal Rust code.
let log_n: u32 = ceno_rt::read();
let mut a = 0_u32;
let mut b = 1_u32;
let n = 1 << log_n;
for _ in 0..n {
let mut c = a + b;
c %= 7919; // Modulus to prevent overflow.
a = b;
b = c;
}
// Constrain with public io
ceno_rt::commit(&b);
}
Code Breakdown
Let’s break down the key parts of this program.
1. Importing the Ceno Runtime
#![allow(unused)]
fn main() {
extern crate ceno_rt;
}
Every Ceno guest program needs to import the ceno_rt crate. This crate provides essential functions for interacting
with the zkVM environment, such as reading private inputs and committing public outputs.
2. Reading Private Inputs
fn main() {
let log_n: u32 = ceno_rt::read();
}
The ceno_rt::read() function is used to read private data that the host provides. In the previous chapter, we passed
--hints=10. This is the value that ceno_rt::read() retrieves. The program receives it as an Archived<u32>, which
is then converted into a standard u32.
3. Core Logic
fn main() {
let mut a = 0_u32;
let mut b = 1_u32;
let n = 1 << log_n;
for _ in 0..n {
let mut c = a + b;
c %= 7919; // Modulus to prevent overflow.
a = b;
b = c;
}
}
This is standard Rust code for calculating a Fibonacci sequence. It uses the log_n input to determine the number of
iterations (n = 1 << log_n, which is 2^10 = 1024). The calculation is performed modulo 7919 to keep the numbers
within a manageable size.
This is a key takeaway: You can write normal Rust code for your core computational logic.
4. Committing Public Output
fn main() {
ceno_rt::commit(&b);
}
After the calculation is complete, ceno_rt::commit() is called. This function takes the final result (b) and commits
it as a public output of the zkVM. The host can then verify that the program produced the correct public output. In our
run command, this is checked against the --public-io=4191 argument.
Building the Program
To build this program so that it can be run inside the Ceno zkVM, you can use the cargo ceno build command:
cargo ceno build --example fibonacci
This will produce an ELF file at examples/target/riscv32im-ceno-zkvm-elf/release/fibonacci. This is the file that is
executed by the run command.
Running the Program
As you saw in the previous chapter, you can run the program with cargo ceno run:
cargo ceno run --example fibonacci --hints=10 --public-io=4191
or alternatively using the hints file to provide the hints:
cargo ceno run --example fibonacci --hints-file=hints.bin --public-io=4191
where hints.bin can be generated by the following rust program:
use std::fs::File;
use std::io::Write;
fn main() {
let mut file = File::create("hints.bin").unwrap();
file.write_all(&10u32.to_le_bytes()).unwrap();
}
TODO: support generating hints file by running the guest program (possibly in a different mode, say, hint generating mode)
TODO: support providing public io also in binary file
Now that you understand the basic components of a Ceno program, the next section will explore the interaction between the host and the guest in more detail.