SDK Quick Start
1. Install the Nexus zkVM
First, install Rust: Rust Install Instructions (opens in a new tab).
Also, make sure you have a working version of cmake (opens in a new tab).
Next, install the RISC-V target:
rustup target add riscv32i-unknown-none-elf
Then, install the Nexus zkVM:
cargo install --git https://github.com/nexus-xyz/nexus-zkvm cargo-nexus --tag 'v0.2.3'
Verify the installation:
cargo nexus --help
This should print the available CLI commands.
2. Create a new Nexus host project
To use the zkVM programmatically, we need two programs: a guest program that runs on the zkVM, and a host program that operates the zkVM itself.
Somewhere out of the nexus-zkvm checkout, run:
cargo nexus host nexus-host
This will create a new Rust project directory with the following structure:
./nexus-host
├── Cargo.lock
├── Cargo.toml
└── src
└── main.rs
└── guest
└── Cargo.toml
└── rust-toolchain.toml
└── src
└── main.rs
Here, ./src/main.rs
is our host program, while ./src/guest/src/main.rs
is our guest program.
As an example, you can change the content of ./src/guest/src/main.rs
to:
#![cfg_attr(target_arch = "riscv32", no_std, no_main)]
use nexus_rt::{println, read_private_input, write_output};
#[nexus_rt::main]
fn main() {
let input = read_private_input::<(u32, u32)>();
let mut z: i32 = -1;
if let Ok((x, y)) = input {
println!("Read private input: ({}, {})", x, y);
z = (x * y) as i32;
} else {
println!("No private input provided...");
}
write_output::<i32>(&z)
}
This guest program tries to read two integers off the input tape, logs whether they exist, and then returns their product if they do.
Then, change the content of ./src/main.rs
to:
use nexus_sdk::{
compile::CompileOpts,
nova::seq::{Generate, Nova, PP},
Local, Prover, Verifiable,
};
type Input = (u32, u32);
type Output = i32;
const PACKAGE: &str = "guest";
fn main() {
println!("Setting up Nova public parameters...");
let pp: PP = PP::generate().expect("failed to generate parameters");
let mut opts = CompileOpts::new(PACKAGE);
opts.set_memlimit(8); // use an 8mb memory
println!("Compiling guest program...");
let prover: Nova<Local> = Nova::compile(&opts).expect("failed to compile guest program");
let input: Input = (3, 5);
print!("Proving execution of vm...");
let proof = prover
.prove_with_input::<Input>(&pp, &input)
.expect("failed to prove program");
println!(
" output is {}!",
proof
.output::<Output>()
.expect("failed to deserialize output")
);
println!(">>>>> Logging\n{}<<<<<", proof.logs().join("\n"));
print!("Verifying execution...");
proof.verify(&pp).expect("failed to verify proof");
println!(" Succeeded!");
}
This host program compiles the guest program with a custom memory limit, and then invokes the resultant binary with (3, 5)
as the input.
The zkVM will then run the guest program and produce a proof of its correct execution.
After the proving completes, the host program then reads the output off the output tape and prints it, along with any logs, and then verifies the proof.
3. Run your program
cargo run -r
Notice that we use cargo run
, rather than cargo nexus run
. Our host program is just a normal Rust program, whereas cargo nexus run
is used to execute guest programs from the command line.
You should see the host program print:
Setting up Nova public parameters...
Proving execution of vm... output is 15!
>>>>> Logging
Read private input: (3, 5)
<<<<<
Verifying execution... Succeeded!
If you want to execute guest programs from the command line, you can navigate into ./src/guest
and then use cargo nexus
commands to do so.
4. Other host program options
There are a few ways to configure how the guest program is run and proven. Perhaps most importantly, the SDK supports two alternative provers: HyperNova and Jolt.
To use HyperNova, just use the example above, replacing nova
with hypernova
in the program.
Jolt support is experimental, and in particular does not currently allow inputs, outputs, logging, or assertions in the guest program. You can test it using a guest program like
#![no_std]
#![no_main]
fn fib(n: u32) -> u32 {
match n {
0 => 0,
1 => 1,
_ => fib(n - 1) + fib(n - 2),
}
}
#[nexus_rt::main]
fn main() {
let n = 10;
let result = fib(n);
core::hint::black_box(result);
}
and a host program like
use nexus_sdk::{compile::CompileOpts, jolt::Jolt, Local};
const PACKAGE: &str = "guest";
fn main() {
let opts = CompileOpts::new(PACKAGE);
// defaults to local proving
let prover: Jolt<Local> = Jolt::compile(&opts).expect("failed to load program");
println!("Proving execution of vm...");
let proof = prover.prove().expect("failed to prove program");
print!("Verifying execution...");
proof.verify().expect("failed to verify proof");
println!(" Succeeded!");
}
To see more example of using the SDK, check out the examples folder (opens in a new tab).