SP1
Documentation for SP1 users and developers.
SP1 is a performant, 100% open-source, contributor-friendly zero-knowledge virtual machine (zkVM) that verifies the execution of arbitrary Rust (or any LLVM-compiled language) programs.
The future of truth is programmable
The future of ZK is writing normal code. Zero-knowledge proofs (ZKPs) are a powerful primitive that will enable a new generation of more secure, scalable and innovative blockchain architectures that rely on truth not trust. But ZKP adoption has been held back because it is “moon math”, requiring specialized knowledge in obscure ZKP frameworks and hard to maintain one-off deployments.
Performant, general-purpose zkVMs, like SP1, will obsolete the current paradigm of specialized teams hand rolling their own custom ZK stack and create a future where all blockchain infrastructure, including rollups, bridges, coprocessors, and more, utilize ZKPs via maintainable software written in Rust (or other LLVM-compiled languages).
Built from day one to be customizable and maintained by a diverse ecosystem of contributors
SP1 is 100% open-source (MIT / Apache 2.0) with no code obfuscation and built to be contributor friendly, with all development done in the open. Unlike existing zkVMs whose constraint logic is closed-source and impossible to modify, SP1 is modularly architected and designed to be customizable from day one. This customizability (unique to SP1) allows for users to add “precompiles” to the core zkVM logic that yield substantial performance gains, making SP1’s performance not only SOTA vs. existing zkVMs, but also competitive with circuits in a variety of use-cases.
Installation
SP1 currently runs on Linux and macOS. You can either use prebuilt binaries through sp1up or build the toolchain and CLI from source.
Requirements
Option 1: Prebuilt Binaries (Recommended)
Currently our prebuilt binaries are built on Ubuntu 20.04 (22.04 on ARM) and macOS. If your OS uses an older GLIBC version, it's possible these may not work and you will need to build the toolchain from source.
sp1up is the SP1 toolchain installer. Open your terminal and run the following command and follow the instructions:
curl -L https://sp1.succinct.xyz | bash
This will install sp1up, then simply follow the instructions on-screen, which will make the sp1up
command available in your CLI.
After following the instructions, you can run sp1up
to install the toolchain:
sp1up
This will install support for the riscv32im-succinct-zkvm-elf
compilation target within your Rust compiler
and a cargo prove
CLI tool that will let you compile provable programs and then prove their correctness.
You can verify the installation by running cargo prove --version
:
cargo prove --version
If this works, go to the next section to compile and prove a simple zkVM program.
Troubleshooting
If you have installed cargo-prove
from source, it may conflict with sp1up's cargo-prove
installation or vice versa. You can remove the cargo-prove
that was installed from source with the following command:
rm ~/.cargo/bin/cargo-prove
Or, you can remove the cargo-prove
that was installed through sp1up
:
rm ~/.sp1/bin/cargo-prove
Option 2: Building from Source
Make sure you have installed the dependencies needed to build the rust toolchain from source.
Clone the sp1
repository and navigate to the root directory.
git clone git@github.com:succinctlabs/sp1.git
cd sp1
cd cli
cargo install --locked --path .
cd ~
cargo prove build-toolchain
Building the toolchain can take a while, ranging from 30 mins to an hour depending on your machine. If you're on a machine that we have prebuilt binaries for (ARM Mac or x86 or ARM Linux), you can use the following to download a prebuilt version.
cargo prove install-toolchain
To verify the installation of the tooolchain, run and make sure you see succinct
:
rustup toolchain list
You can delete your existing installation of the toolchain with:
rustup toolchain remove succinct
Option 3: Using Docker
SP1 can also be used entirely within a Docker container. If you don't have it, Docker can be installed directly from Docker's website.
Then you can use:
cargo prove --docker
to automatically use the latest image of SP1 in a container.
Alternatively, it is possible to build the docker image locally by running:
docker build -t succinctlabs/sp1:latest ./cli/docker
You can then run the cargo prove
command by mounting your program directory into the container:
docker run -v "$(pwd):/root/program" -it succinctlabs/sp1:latest prove build
Quickstart
In this section, we will show you how to create a simple Fibonacci program using the SP1 zkVM.
Create Project
The first step is to create a new project using the cargo prove new <name>
command. This command will create a new folder in your current directory.
cargo prove new fibonacci
cd fibonacci
This will create a new project with the following structure:
.
├── program
│ ├── Cargo.toml
│ ├── elf
│ └── src
│ └── main.rs
└── script
├── Cargo.toml
└── src
└── main.rs
6 directories, 4 files
There are 2 directories (each a crate) in the project:
program
: the source code that will be proven inside the zkVM.script
: code that contains proof generation and verification code.
We recommend you install the rust-analyzer extension.
Note that if you use cargo prove new
inside a monorepo, you will need to add the manifest file to rust-analyzer.linkedProjects
to get full IDE support.
Build Program
Before we can run the program inside the zkVM, we must compile it to a RISCV executable using the succinct
Rust toolchain. The cargo prove
CLI tool exposes a build
command which you can run at the root of the program
directory to do this.
The program is a very simple program to compute the n
-th Fibonacci number.
cd program
cargo prove build
The resulting compiled executable is called an ELF (Executable and Linkable Format) and contains bytecode that can be executed by the SP1 zkVM.
After running the above command, you can verify that the ELF was generated by looking in the elf
directory and looking for a file called riscv32im-succinct-zkvm-elf
:
ls elf # should show riscv32im-succinct-zkvm-elf
Generate Proof
To generate a proof, we take the ELF file that was generated in the previous step and execute it within the SP1 zkVM. The code in the script
directory is already scaffolded with a script that has logic to generate a proof, save the proof to disk, and verify it.
cd ../script
RUST_LOG=info cargo run --release
The output should show
...
Compiling fibonacci-script v0.1.0 (/Users/umaroy/Documents/fibonacci/script)
Finished release [optimized] target(s) in 26.14s
Running `target/release/fibonacci-script`
a: 205697230343233228174223751303346572685
b: 332825110087067562321196029789634457848
successfully generated and verified proof for the program!
The program by default is quite small, so proof generation will only take a few seconds locally. After it completes, the proof will be saved in the proof-with-io.json
file and also be verified for correctness.
You can play around with how many rounds of Fibonacci are executed by playing around with n
(by default set to 186
) in the file script/src/main.rs
. Integer overflow will cause larger n
to result in non-fibonacci output, although the proof will still be generated and verified.
Project Template
Another option for getting started with SP1 is to use the SP1 Project Template.
Writing Programs: Setup
In this section, we will teach you how to setup a self-contained crate which can be compiled as an program that can be executed inside the zkVM.
CLI (Recommended)
The recommended way to setup your first program to prove inside the zkVM is using the method described in Quickstart which will create a program folder.
cargo prove new <name>
cd program
Build
To build the program, simply run:
cargo prove build
This will compile the ELF that can be executed in the zkVM and put the executable in elf/riscv32im-succinct-zkvm-elf
.
Manual
You can also manually setup a project. First create a new cargo project:
cargo new program
cd program
Cargo Manifest
Inside this crate, add the sp1-zkvm
crate as a dependency. Your Cargo.toml
should look like as follows:
[workspace]
[package]
version = "0.1.0"
name = "program"
edition = "2021"
[dependencies]
sp1-zkvm = { git = "https://github.com/succinctlabs/sp1.git" }
The sp1-zkvm
crate includes necessary utilities for your program, including handling inputs and outputs,
precompiles, patches, and more.
main.rs
Inside the src/main.rs
file, you must make sure to include these two lines to ensure that the crate
properly compiles.
#![no_main]
sp1_zkvm::entrypoint!(main);
These two lines of code wrap your main function with some additional logic to ensure that your program compiles correctly with the RISCV target.
Build
To build the program, simply run:
cargo prove build
This will compile the ELF (RISCV binary) that can be executed in the zkVM and put the executable in elf/riscv32im-succinct-zkvm-elf
.
Writing Programs: Basics
A zero-knowledge proof generally proves that some function f
when applied to some input x
produces some output y
(i.e. f(x) = y
).
In the context of the SP1 zkVM:
f
is written in normal Rust code.x
are bytes that can be serialized / deserialized into objectsy
are bytes that can be serialized / deserialized into objects
To make this more concrete, let's walk through a simple example of writing a Fibonacci program inside the zkVM.
Fibonacci
This program is from the examples
directory in the SP1 repo which contains several example programs of varying complexity.
//! A simple program that takes a number `n` as input, and writes the `n-1`th and `n`th fibonacci
//! number as an output.
// These two lines are necessary for the program to properly compile.
//
// Under the hood, we wrap your main function with some extra code so that it behaves properly
// inside the zkVM.
#![no_main]
sp1_zkvm::entrypoint!(main);
pub fn main() {
// Read an input to the program.
//
// Behind the scenes, this compiles down to a custom system call which handles reading inputs
// from the prover.
let n = sp1_zkvm::io::read::<u32>();
// Write n to public input
sp1_zkvm::io::commit(&n);
// Compute the n'th fibonacci number, using normal Rust code.
let mut a = 0;
let mut b = 1;
for _ in 0..n {
let mut c = a + b;
c %= 7919; // Modulus to prevent overflow.
a = b;
b = c;
}
// Write the output of the program.
//
// Behind the scenes, this also compiles down to a custom system call which handles writing
// outputs to the prover.
sp1_zkvm::io::commit(&a);
sp1_zkvm::io::commit(&b);
}
As you can see, writing programs is as simple as writing normal Rust. To read more about how inputs and outputs work, refer to the section on Inputs & Outputs.
Inputs and Outputs
In real world applications of zero-knowledge proofs, you almost always want to verify your proof in the context of some inputs and outputs. For example:
- Rollups: Given a list of transactions, prove the new state of the blockchain.
- Coprocessors: Given a block header, prove the historical state of some storage slot inside a smart contract.
- Attested Images: Given a signed image, prove that you made a restricted set of image transformations.
In this section, we cover how you pass inputs and outputs to the zkVM and create new types that support serialization.
Reading Data
Data that is read is not public to the verifier by default. Use the sp1_zkvm::io::read::<T>
method:
let a = sp1_zkvm::io::read::<u32>();
let b = sp1_zkvm::io::read::<u64>();
let c = sp1_zkvm::io::read::<String>();
Note that T
must implement the serde::Serialize
and serde::Deserialize
trait. If you want to read bytes directly, you can also use the sp1_zkvm::io::read_vec
method.
let my_vec = sp1_zkvm::io::read_vec();
Commiting Data
Committing to data makes the data public to the verifier. Use the sp1_zkvm::io::commit::<T>
method:
sp1_zkvm::io::commit::<u32>(&a);
sp1_zkvm::io::commit::<u64>(&b);
sp1_zkvm::io::commit::<String>(&c);
Note that T
must implement the Serialize
and Deserialize
trait. If you want to write bytes directly, you can also use sp1_zkvm::io::write_slice
method:
let mut my_slice = [0_u8; 32];
sp1_zkvm::io::commit_slice(&my_slice);
Creating Serializable Types
Typically, you can implement the Serialize
and Deserialize
traits using a simple derive macro on a struct.
use serde::{Serialize, de::Deserialize};
#[derive(Serialize, Deserialize)]
struct MyStruct {
a: u32,
b: u64,
c: String
}
For more complex usecases, refer to the Serde docs.
Example
Here is a basic example of using inputs and outputs with more complex types.
#![no_main]
sp1_zkvm::entrypoint!(main);
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug, PartialEq)]
struct MyPointUnaligned {
pub x: usize,
pub y: usize,
pub b: bool,
}
pub fn main() {
let p1 = sp1_zkvm::io::read::<MyPointUnaligned>();
println!("Read point: {:?}", p1);
let p2 = sp1_zkvm::io::read::<MyPointUnaligned>();
println!("Read point: {:?}", p2);
let p3: MyPointUnaligned = MyPointUnaligned {
x: p1.x + p2.x,
y: p1.y + p2.y,
b: p1.b && p2.b,
};
println!("Addition of 2 points: {:?}", p3);
sp1_zkvm::io::commit(&p3);
}
Precompiles
Precompiles are built into the SP1 zkVM and accelerate commonly used operations such as elliptic curve arithmetic and hashing. Under the hood, precompiles are implemented as custom tables dedicated to proving one or few operations. They typically improve the performance of executing expensive operations by a few order of magnitudes.
Inside the zkVM, precompiles are exposed as system calls executed through the ecall
RISC-V instruction.
Each precompile has a unique system call number and implements an interface for the computation.
SP1 also has been designed specifically to make it easy for external contributors to create and extend the zkVM with their own precompiles. To learn more about this, you can look at implementations of existing precompiles in the precompiles folder. More documentation on this will be coming soon.
Supported Precompiles
Typically, we recommend you interact with precompiles through patches, which are crates patched to use these precompiles under the hood. However, if you are an advanced user you can interact with the precompiles directly using extern system calls.
Here is a list of extern system calls that use precompiles.
SHA256 Extend
Executes the SHA256 extend operation on a word array.
pub extern "C" fn syscall_sha256_extend(w: *mut u32);
SHA256 Compress
Executes the SHA256 compress operation on a word array and a given state.
pub extern "C" fn syscall_sha256_compress(w: *mut u32, state: *mut u32);
Keccak256 Permute
Executes the Keccak256 permutation function on the given state.
pub extern "C" fn syscall_keccak_permute(state: *mut u64);
Ed25519 Add
Adds two points on the ed25519 curve. The result is stored in the first point.
pub extern "C" fn syscall_ed_add(p: *mut u32, q: *mut u32);
Ed25519 Decompress.
Decompresses a compressed Ed25519 point.
The second half of the input array should contain the compressed Y point with the final bit as the sign bit. The first half of the input array will be overwritten with the decompressed point, and the sign bit will be removed.
pub extern "C" fn syscall_ed_decompress(point: &mut [u8; 64])
Secp256k1 Add
Adds two Secp256k1 points. The result is stored in the first point.
pub extern "C" fn syscall_secp256k1_add(p: *mut u32, q: *mut u32)
Secp256k1 Double
Doubles a Secp256k1 point in place.
pub extern "C" fn syscall_secp256k1_double(p: *mut u32)
Secp256k1 Decompress
Decompess a Secp256k1 point.
The input array should be 32 bytes long, with the first 16 bytes containing the X coordinate in big-endian format. The second half of the input will be overwritten with the decompressed point.
pub extern "C" fn syscall_secp256k1_decompress(point: &mut [u8; 64], is_odd: bool);
Bn254 Add
Adds two Bn254 points. The result is stored in the first point.
pub extern "C" fn syscall_bn254_add(p: *mut u32, q: *mut u32)
Bn254 Double
Doubles a Bn254 point in place.
pub extern "C" fn syscall_bn254_double(p: *mut u32)
Bls12-381 Add
Adds two Bls12-381 points. The result is stored in the first point.
pub extern "C" fn syscall_bls12381_add(p: *mut u32, q: *mut u32)
Bls12-381 Double
Doubles a Bls12-381 point in place.
pub extern "C" fn syscall_bls12381_double(p: *mut u32)
Patched Crates
We maintain forks of commonly used libraries in blockchain infrastructure to significantly accelerate the execution of certain operations. Under the hood, we use precompiles to acheive tremendous performance improvements in proof generation time.
If you know of a library or library version that you think should be patched, please open an issue or a pull request!
Supported Libraries
Crate Name | Repository | Notes |
---|---|---|
sha2 | sp1-patches/RustCrypto-hashes | sha256 |
tiny-keccak | sp1-patches/tiny-keccak | keccak256 |
ed25519-consensus | sp1-patches/ed25519-consensus | ed25519 verify |
curve25519-dalek-ng | sp1-patches/curve25519-dalek-ng | ed25519 verify |
curve25519-dalek | sp1-patches/curve25519-dalek | ed25519 verify |
revm-precompile | sp1-patches/revm | ecrecover precompile |
reth-primitives | sp1-patches/reth | ecrecover transactions |
Using Patched Crates
To use the patched libraries, you can use corresponding patch entries in your program's Cargo.toml
such as:
[patch.crates-io]
sha2-v0-9-8 = { git = "https://github.com/sp1-patches/RustCrypto-hashes", package = "sha2", branch = "patch-v0.9.8" }
sha2-v0-10-6 = { git = "https://github.com/sp1-patches/RustCrypto-hashes", package = "sha2", branch = "patch-v0.10.6" }
sha2-v0-10-8 = { git = "https://github.com/sp1-patches/RustCrypto-hashes", package = "sha2", branch = "patch-v0.10.8" }
curve25519-dalek = { git = "https://github.com/sp1-patches/curve25519-dalek", branch = "patch-v4.1.1" }
curve25519-dalek-ng = { git = "https://github.com/sp1-patches/curve25519-dalek-ng", branch = "patch-v4.1.1" }
ed25519-consensus = { git = "https://github.com/sp1-patches/ed25519-consensus", branch = "patch-v2.1.0" }
tiny-keccak = { git = "https://github.com/sp1-patches/tiny-keccak", branch = "patch-v2.0.2" }
revm = { git = "https://github.com/sp1-patches/revm", branch = "patch-v5.0.0" }
reth-primitives = { git = "https://github.com/sp1-patches/reth", default-features = false, branch = "sp1-reth" }
You may also need to update your Cargo.lock
file. For example:
cargo update -p ed25519-consensus
If you encounter issues relating to cargo / git, you can try setting CARGO_NET_GIT_FETCH_WITH_CLI
:
CARGO_NET_GIT_FETCH_WITH_CLI=true cargo update -p ed25519-consensus
You can permanently set this value in ~/.cargo/config
:
[net]
git-fetch-with-cli = true
Sanity Checks
You must make sure your patch is in the workspace root, otherwise it will not be applied.
You can check if the patch was applied by running a command like the following:
cargo tree -p sha2
cargo tree -p sha2@0.9.8
Next to the package name, it should have a link to the Github repository that you patched with.
Checking whether a precompile is used
To check if a precompile is used by your program, when running the script to generate a proof, make sure to use the RUST_LOG=info
environment variable and set up utils::setup_logger()
in your script. Then, when you run the script, you should see a log message like the following:
2024-03-02T19:10:39.570244Z INFO runtime.run(...): ...
2024-03-02T19:10:39.570244Z INFO runtime.run(...): ...
2024-03-02T19:10:40.003907Z INFO runtime.prove(...): Sharding the execution record.
2024-03-02T19:10:40.003916Z INFO runtime.prove(...): Generating trace for each chip.
2024-03-02T19:10:40.003918Z INFO runtime.prove(...): Record stats before generate_trace (incomplete): ShardStats {
nb_cpu_events: 7476561,
nb_add_events: 2126546,
nb_mul_events: 11116,
nb_sub_events: 54075,
nb_bitwise_events: 646940,
nb_shift_left_events: 142595,
nb_shift_right_events: 274016,
nb_divrem_events: 0,
nb_lt_events: 81862,
nb_field_events: 0,
nb_sha_extend_events: 0,
nb_sha_compress_events: 0,
nb_keccak_permute_events: 2916,
nb_ed_add_events: 0,
nb_ed_decompress_events: 0,
nb_weierstrass_add_events: 0,
nb_weierstrass_double_events: 0,
nb_k256_decompress_events: 0,
}
The ShardStats
struct contains the number of events for each "table" from the execution of the program, including precompile tables. In the example above, the nb_keccak_permute_events
field is 2916
, indicating that the precompile for the Keccak permutation was used.
Cycle Tracking
When writing a program, it is useful to know how many RISC-V cycles a portion of the program takes to identify potential performance bottlenecks. SP1 provides a way to track the number of cycles spent in a portion of the program.
Tracking Cycles
To track the number of cycles spent in a portion of the program, you can either put println!("cycle-tracker-start: block name")
+ println!("cycle-tracker-end: block name")
statements (block name must be same between start and end) around the portion of your program you want to profile or use the #[sp1_derive::cycle_tracker]
macro on a function. An example is shown below:
#![no_main]
sp1_zkvm::entrypoint!(main);
#[sp1_derive::cycle_tracker]
pub fn expensive_function(x: usize) -> usize {
let mut y = 1;
for _ in 0..100 {
y *= x;
y %= 7919;
}
y
}
pub fn main() {
let mut nums = vec![1, 1];
// Setup a large vector with Fibonacci-esque numbers.
println!("cycle-tracker-start: setup");
for _ in 0..100 {
let mut c = nums[nums.len() - 1] + nums[nums.len() - 2];
c %= 7919;
nums.push(c);
}
println!("cycle-tracker-end: setup");
println!("cycle-tracker-start: main-body");
for i in 0..2 {
let result = expensive_function(nums[nums.len() - i - 1]);
println!("result: {}", result);
}
println!("cycle-tracker-end: main-body");
}
Note that to use the macro, you must add the sp1-derive
crate to your dependencies for your program.
[dependencies]
sp1-derive = { git = "https://github.com/succinctlabs/sp1.git" }
In the script for proof generation, setup the logger with utils::setup_logger()
and run the script with RUST_LOG=info cargo run --release
. You should see the following output:
$ RUST_LOG=info cargo run --release
Finished release [optimized] target(s) in 0.21s
Running `target/release/cycle-tracking-script`
2024-03-13T02:03:40.567500Z INFO execute: loading memory image
2024-03-13T02:03:40.567751Z INFO execute: starting execution
2024-03-13T02:03:40.567760Z INFO execute: clk = 0 pc = 0x2013b8
2024-03-13T02:03:40.567822Z INFO execute: ┌╴setup
2024-03-13T02:03:40.568095Z INFO execute: └╴4,398 cycles
2024-03-13T02:03:40.568122Z INFO execute: ┌╴main-body
2024-03-13T02:03:40.568149Z INFO execute: │ ┌╴expensive_function
2024-03-13T02:03:40.568250Z INFO execute: │ └╴1,368 cycles
stdout: result: 5561
2024-03-13T02:03:40.568373Z INFO execute: │ ┌╴expensive_function
2024-03-13T02:03:40.568470Z INFO execute: │ └╴1,368 cycles
stdout: result: 2940
2024-03-13T02:03:40.568556Z INFO execute: └╴5,766 cycles
2024-03-13T02:03:40.568566Z INFO execute: finished execution clk = 11127 pc = 0x0
2024-03-13T02:03:40.569251Z INFO execute: close time.busy=1.78ms time.idle=21.1µs
Note that we elegantly handle nested cycle tracking, as you can see above.
Generating Proofs: Setup
In this section, we will teach you how to setup a self-contained crate which can generate proofs of programs that have been compiled with the SP1 toolchain inside the SP1 zkVM.
CLI (Recommended)
The recommended way to setup your first program to prove inside the zkVM is using the method described in Quickstart which will create a script folder.
cargo prove new <name>
cd script
Manual
You can also manually setup a project. First create a new cargo project:
cargo new script
cd script
Cargo Manifest
Inside this crate, add the sp1-sdk
crate as a dependency. Your Cargo.toml
should look like as follows:
[workspace]
[package]
version = "0.1.0"
name = "script"
edition = "2021"
[dependencies]
sp1-sdk = { git = "https://github.com/succinctlabs/sp1.git" }
The sp1-sdk
crate includes necessary utilities to generate, save, and verify proofs.
Generating Proofs: Basics
An end-to-end flow of proving f(x) = y
with the SP1 zkVM involves the following steps:
- Define
f
using normal Rust code and compile it to an ELF (covered in the writing programs section). - Setup a proving key (
pk
) and verifying key (vk
) for the program given the ELF. The proving key contains all the information needed to generate a proof and includes some post-processing on top of the ELF, while the verifying key is a compact representation of the ELF that contains all the information needed to verify a proof and is much smaller than the ELF itself. - Generate a proof
π
using the SP1 zkVM thatf(x) = y
withprove(pk, x)
. - Verify the proof
π
usingverify(vk, x, y, π)
.
To make this more concrete, let's walk through a simple example of generating a proof for a Fiboancci program inside the zkVM.
Fibonacci
use sp1_sdk::{utils, ProverClient, SP1Stdin};
/// The ELF we want to execute inside the zkVM.
const ELF: &[u8] = include_bytes!("../../program/elf/riscv32im-succinct-zkvm-elf");
fn main() {
// Setup logging.
utils::setup_logger();
// Create an input stream and write '500' to it.
let n = 500u32;
let mut stdin = SP1Stdin::new();
stdin.write(&n);
// Generate the proof for the given program and input.
let client = ProverClient::new();
let (pk, vk) = client.setup(ELF);
let mut proof = client.prove(&pk, stdin).unwrap();
println!("generated proof");
// Read and verify the output.
let _ = proof.public_values.read::<u32>();
let a = proof.public_values.read::<u32>();
let b = proof.public_values.read::<u32>();
println!("a: {}", a);
println!("b: {}", b);
// Verify proof and public values
client.verify(&proof, &vk).expect("verification failed");
// Save the proof.
proof
.save("proof-with-pis.json")
.expect("saving proof failed");
println!("successfully generated and verified proof for the program!")
}
You can run the above script in the script
directory with RUST_LOG=info cargo run --release
.
Build Script
If you want your program crate to be built automatically whenever you build/run your script crate, you can add a build.rs
file inside of script/
(at the same level as Cargo.toml
):
fn main() {
sp1_helper::build_program(&format!("{}/../program", env!("CARGO_MANIFEST_DIR")));
}
Make sure to also add sp1-helper
as a build dependency in script/Cargo.toml
:
[build-dependencies]
sp1-helper = { git = "https://github.com/succinctlabs/sp1.git" }
If you run RUST_LOG=info cargo run --release -vv
, you will see the following output from the build script if the program has changed, indicating that the program was rebuilt:
[fibonacci-script 0.1.0] cargo:rerun-if-changed=../program/src
[fibonacci-script 0.1.0] cargo:rerun-if-changed=../program/Cargo.toml
[fibonacci-script 0.1.0] cargo:rerun-if-changed=../program/Cargo.lock
[fibonacci-script 0.1.0] cargo:warning=fibonacci-program built at 2024-03-02 22:01:26
[fibonacci-script 0.1.0] [sp1] Compiling fibonacci-program v0.1.0 (/Users/umaroy/Documents/fibonacci/program)
[fibonacci-script 0.1.0] [sp1] Finished release [optimized] target(s) in 0.15s
warning: fibonacci-script@0.1.0: fibonacci-program built at 2024-03-02 22:01:26```
Advanced Usage
Execution Only
We recommend that during development of large programs (> 1 million cycles) that you do not generate proofs each time.
Instead, you should have your script only execute the program with the RISC-V runtime and read public_values
. Here is an example:
use sp1_sdk::{utils, ProverClient, SP1Stdin};
/// The ELF we want to execute inside the zkVM.
const ELF: &[u8] = include_bytes!("../../program/elf/riscv32im-succinct-zkvm-elf");
fn main() {
// Setup logging.
utils::setup_logger();
// Create an input stream and write '500' to it.
let n = 500u32;
let mut stdin = SP1Stdin::new();
stdin.write(&n);
// Only execute the program and get a `SP1PublicValues` object.
let client = ProverClient::new();
let mut public_values = client.execute(&ELF, stdin).unwrap();
println!("generated proof");
// Read and verify the output.
let _ = public_values.read::<u32>();
let a = public_values.read::<u32>();
let b = public_values.read::<u32>();
println!("a: {}", a);
println!("b: {}", b);
}
If execution of your program succeeds, then proof generation should succeed as well! (Unless there is a bug in our zkVM implementation.)
Compressed Proofs
With the ProverClient
, the default prove
function generates a proof that is succinct, but can have size that scales with the number of cycles of the program. To generate a compressed proof of constant size, you can use the prove_compressed
function instead. This will use STARK recursion to generate a proof that is constant size (around 7Kb), but will be slower than just calling prove
, as it will use recursion to combine the core SP1 proof into a single constant-sized proof.
use sp1_sdk::{utils, ProverClient, SP1Stdin};
/// The ELF we want to execute inside the zkVM.
const ELF: &[u8] = include_bytes!("../../program/elf/riscv32im-succinct-zkvm-elf");
fn main() {
// Setup logging.
utils::setup_logger();
// Create an input stream and write '500' to it.
let n = 500u32;
let mut stdin = SP1Stdin::new();
stdin.write(&n);
// Generate the constant-sized proof for the given program and input.
let client = ProverClient::new();
let (pk, vk) = client.setup(ELF);
let mut proof = client.prove_compressed(&pk, stdin).unwrap();
println!("generated proof");
// Read and verify the output.
let a = proof.public_values.read::<u32>();
let b = proof.public_values.read::<u32>();
println!("a: {}, b: {}", a, b);
// Verify proof and public values
client
.verify_compressed(&proof, &vk)
.expect("verification failed");
// Save the proof.
proof
.save("compressed-proof-with-pis.json")
.expect("saving proof failed");
println!("successfully generated and verified proof for the program!")
}
You can run the above script with RUST_LOG=info cargo run --bin compressed --release
from examples/fibonacci/script
.
Logging and Tracing Information
You can use utils::setup_logger()
to enable logging information respectively. You should only use one or the other of these functions.
Logging:
utils::setup_logger();
You must run your command with:
RUST_LOG=info cargo run --release
CPU Acceleration
To enable CPU acceleration, you can use the RUSTFLAGS
environment variable to enable the target-cpu=native
flag when running your script. This will enable the compiler to generate code that is optimized for your CPU.
RUSTFLAGS='-C target-cpu=native' cargo run --release
Currently there is support for AVX512 and NEON SIMD instructions. For NEON, you must also enable the sp1-sdk
feature neon
in your script crate's Cargo.toml
file.
sp1-sdk = { git = "https://github.com/succinctlabs/sp1", features = ["neon"] }
Performance
For maximal performance, you should run proof generation with the following command and vary your shard_size
depending on your program's number of cycles.
SHARD_SIZE=4194304 RUST_LOG=info RUSTFLAGS='-C target-cpu=native' cargo run --release
Memory Usage
To reduce memory usage, set the SHARD_BATCH_SIZE
enviroment variable depending on how much RAM
your machine has. A higher number will use more memory, but will be faster.
SHARD_BATCH_SIZE=1 SHARD_SIZE=2097152 RUST_LOG=info RUSTFLAGS='-C target-cpu=native' cargo run --release
Verifying Proofs: Solidity & EVM
SP1 recently added support for verifying proofs for onchain usecases. To see an end-to-end example of using SP1 for on-chain usecases, refer to the SP1 Project Template.
Generating a Groth16 Proof
By default, the proofs generated by SP1 are not verifiable onchain, as they are non-constant size and STARK verification on Ethereum is very expensive. To generate a proof that can be verified onchain, we use performant STARK recursion to combine SP1 shard proofs into a single STARK proof and then wrap that in a SNARK proof. Our ProverClient
has a function for this called prove_groth16
. Behind the scenes, this function will first generate a normal SP1 proof, then recursively combine all of them into a single proof using the STARK recursion protocol. Finally, the proof is wrapped in a SNARK proof using Groth16.
Due to the fact that Groth16 proofs require a trusted setup, the Groth16 prover is only guaranteed to work on official releases of SP1 (i.e., v.1.0.0-testnet
).
Example
use sp1_sdk::{utils, ProverClient, SP1Stdin};
/// The ELF we want to execute inside the zkVM.
const ELF: &[u8] = include_bytes!("../../program/elf/riscv32im-succinct-zkvm-elf");
fn main() {
// Setup logging.
utils::setup_logger();
// Create an input stream and write '500' to it.
let n = 500u32;
let mut stdin = SP1Stdin::new();
stdin.write(&n);
// Generate the proof for the given program and input.
let client = ProverClient::new();
let (pk, vk) = client.setup(ELF);
let mut proof = client.prove_groth16(&pk, stdin).unwrap();
println!("generated proof");
// Read and verify the output.
let _ = proof.public_values.read::<u32>();
let a = proof.public_values.read::<u32>();
let b = proof.public_values.read::<u32>();
println!("a: {}", a);
println!("b: {}", b);
// Verify proof and public values
client
.verify_groth16(&proof, &vk)
.expect("verification failed");
// Save the proof.
proof
.save("proof-with-pis.json")
.expect("saving proof failed");
println!("successfully generated and verified proof for the program!")
}
You can run the above script with RUST_LOG=info cargo run --bin groth16 --release
in examples/fibonacci/script
.
Exporting the Verifier Contract
To export the verifier contract, you can use the export function in the sp1_sdk
crate.
Example
//! Builds the proving artifacts and exports the solidity verifier.
//!
//! You can run this script using the following command:
//! ```shell
//! RUST_LOG=info cargo run --package fibonacci-script --bin artifacts --release
//! ```
use std::path::PathBuf;
fn main() {
let contracts_src_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../contracts/src");
sp1_sdk::artifacts::export_solidity_groth16_verifier(contracts_src_dir)
.expect("failed to export verifier");
}
Recommended Settings
For developers contributing to the SP1 project, we recommend the following settings:
FRI_QUERIES=1
: Makes the prover use less bits of security to generate proofs more quickly.SP1_DEV=1
: This will rebuild the Groth16 artifacts everytime they are necessary.
Building Groth16 Artifacts
To build the Groth16 artifacts from scratch, you can use the Makefile
inside the prover
directory.
RUST_LOG=info make groth16