Skip to main content
Get started with Jolt by creating a simple zkVM program that proves Fibonacci number computation.

Prerequisites

Before you begin, make sure you have completed the installation steps.

Create Your First Project

1

Create a new Jolt project

Use the Jolt CLI to scaffold a new project:
jolt new my-jolt-app
cd my-jolt-app
This creates a workspace with two parts:
  • guest/ - Code that runs inside the zkVM (RISC-V)
  • src/ (host) - Code that compiles, proves, and verifies guest execution
2

Understand the guest code

Open guest/src/lib.rs to see the guest program:
guest/src/lib.rs
#![cfg_attr(feature = "guest", no_std)]

#[jolt::provable(heap_size = 32768, max_trace_length = 65536)]
fn fib(n: u32) -> u128 {
    let mut a: u128 = 0;
    let mut b: u128 = 1;
    let mut sum: u128;
    for _ in 1..n {
        sum = a + b;
        a = b;
        b = sum;
    }
    b
}
The #[jolt::provable] macro transforms this function into a zkVM program. It generates:
  • compile_fib() - Compiles the guest to RISC-V ELF
  • prove_fib() - Generates a zero-knowledge proof of execution
  • verify_fib() - Verifies the proof
  • Preprocessing functions for optimization
3

Understand the host code

The host code in src/main.rs orchestrates the proving and verification:
src/main.rs
use tracing::info;

pub fn main() {
    tracing_subscriber::fmt::init();

    let target_dir = "/tmp/jolt-guest-targets";
    let mut program = guest::compile_fib(target_dir);

    let shared_preprocessing = guest::preprocess_shared_fib(&mut program);

    let prover_preprocessing = guest::preprocess_prover_fib(shared_preprocessing.clone());
    let verifier_setup = prover_preprocessing.generators.to_verifier_setup();
    let verifier_preprocessing =
        guest::preprocess_verifier_fib(shared_preprocessing, verifier_setup);

    let prove_fib = guest::build_prover_fib(program, prover_preprocessing);
    let verify_fib = guest::build_verifier_fib(verifier_preprocessing);

    let (output, proof, io_device) = prove_fib(50);
    let is_valid = verify_fib(50, output, io_device.panic, proof);

    info!("output: {output}");
    info!("valid: {is_valid}");
}
This code:
  1. Compiles the guest program to RISC-V
  2. Preprocesses (generates proving/verifying keys - done once per program)
  3. Proves execution with input 50
  4. Verifies the proof
4

Run your first proof

Build and run the program:
cargo run --release
You should see output like:
INFO output: 12586269025
INFO valid: true
The first run will be slow (~1-5 minutes) as it compiles dependencies and the guest program. Subsequent runs are much faster.

What Just Happened?

  1. Compilation: The guest Fibonacci function was compiled to a RISC-V ELF binary
  2. Preprocessing: Jolt generated proving and verifying keys for this specific program
  3. Proving: Jolt executed the guest program, computed the 50th Fibonacci number, and generated a ZK proof
  4. Verification: The verifier checked the proof without re-executing the program
The proof demonstrates that the computation was performed correctly, without revealing the intermediate steps.

Understanding the Macro

The #[jolt::provable] macro accepts several parameters to configure the zkVM:
heap_size
number
default:"1048576"
Size of the guest heap in bytes (default: 1MB)
max_trace_length
number
default:"1048576"
Maximum number of CPU cycles the program can execute
stack_size
number
default:"4194304"
Size of the guest stack in bytes (default: 4MB)
Example with different parameters:
#[jolt::provable(
    heap_size = 65536,           // 64KB heap
    max_trace_length = 1000000,  // 1M cycles max
    stack_size = 8388608         // 8MB stack
)]
fn my_function(input: u64) -> u64 {
    // Your computation here
    input * 2
}

Next Steps

Core Concepts

Learn about the guest/host architecture

Runtime Advice

Provide non-deterministic hints to speed up proving

CLI Reference

Explore all CLI commands

API Reference

Dive into the full API documentation

Common Patterns

Multiple Functions

You can mark multiple functions as provable in the same guest:
#[jolt::provable]
fn add(a: u64, b: u64) -> u64 {
    a + b
}

#[jolt::provable]
fn multiply(a: u64, b: u64) -> u64 {
    a * b
}

Working with Complex Types

Functions can accept and return complex types that implement Serialize:
use serde::{Serialize, Deserialize};

#[derive(Serialize, Deserialize)]
struct Point {
    x: i32,
    y: i32,
}

#[jolt::provable]
fn distance_squared(p1: Point, p2: Point) -> i32 {
    let dx = p2.x - p1.x;
    let dy = p2.y - p1.y;
    dx * dx + dy * dy
}

Cycle Tracking

Measure performance of specific code sections:
use jolt::{start_cycle_tracking, end_cycle_tracking};

#[jolt::provable]
fn compute_with_tracking(n: u32) -> u128 {
    start_cycle_tracking("computation");
    
    // Your computation here
    let result = expensive_calculation(n);
    
    end_cycle_tracking("computation");
    result
}
The cycle counts will appear in the execution summary when you use analyze_<function_name>() instead of prove_<function_name>().

Troubleshooting

Make sure you have the RISC-V target installed:
rustup target add riscv64imac-unknown-none-elf
If issues persist, try reinstalling the Jolt CLI:
cd jolt
cargo install --path . --locked
For development, use analyze_<function_name>() instead of prove_<function_name>() to skip proof generation:
// Fast analysis without proof
let summary = guest::analyze_fib(50);
println!("Cycles: {}", summary.total_cycles());
Reduce the trace length or use streaming mode (if available). Also ensure you have sufficient RAM (16GB+ recommended for complex proofs).
For more troubleshooting tips, see Troubleshooting.

Build docs developers (and LLMs) love