Skip to main content
Version: v5 (Turbo)

Migrating from V5 to V6

There are many small interface tweaks between V5 and V6. These are some of the major ones that may require changes to your code:

64 bit Toolchain

The default behavior of sp1up now installs a V6-compatible 64 bit toolchain. For backwards compatibility, install a V5-compatible 32 bit toolchain using sp1up -v 5.2.4. 32 and 64 bit toolchains are incompatible.

Async API

Expensive operations, like execution, setup, and proving, are now async. They are meant for use specifically in the tokio runtime. For example:

use sp1_sdk::{
include_elf, utils, Elf, ProveRequest, Prover, ProverClient, ProvingKey,
SP1ProofWithPublicValues, SP1Stdin,
};

/// The ELF we want to execute inside the zkVM.
const ELF: Elf = include_elf!("my-program");

#[tokio::main]
async fn main() {
// Asynchronously create a ProverClient.
let client = ProverClient::builder().cpu().build().await;

let stdin = SP1Stdin::default();

// ... configure stdin ...

// Asynchronously execute the program.
let (_, report) = client.execute(ELF, stdin.clone()).await.unwrap();

// Asynchronously set up and generate a proof.
let pk = client.setup(ELF).await.unwrap();
let proof = client.prove(ELF, pk, stdin.clone()).core().await.unwrap();
}

Blocking API

Alternatively, you can use the synchronous, blocking API. This requires the blocking feature in sp1-sdk.

NOTE: This will fail if called from inside an existing tokio runtime.

use sp1_sdk::{
blocking::{ProveRequest, Prover, ProverClient},
include_elf, Elf, HashableKey, ProvingKey, SP1ProofWithPublicValues, SP1Stdin, SP1VerifyingKey,
};

const ELF: Elf = include_elf!("my-program");

fn main() {
// Synchronously create a ProverClient.
let client = ProverClient::builder().cpu().build();

let stdin = SP1Stdin::default();

// ... configure stdin ...

// Synchronously execute the program.
let (_, report) = client.execute(ELF, stdin.clone()).unwrap();

// Synchronously set up and generate a proof.
let pk = client.setup(ELF).unwrap();
let proof = client.prove(ELF, pk, stdin.clone()).core().unwrap();
}

Light Prover

ProverClient::builder().cpu().build() can be slow. For example, on a MacBook M3 Pro, this operation should take around 10 seconds. This time is used to cache proving artifacts, which speeds up subsequent proving operations.

If you only need to execute and verify pre-generated proofs, you can use the LightProver. See the following example.

use sp1_sdk::{
blocking::{ProveRequest, Prover, ProverClient},
include_elf, Elf, HashableKey, ProvingKey, SP1ProofWithPublicValues, SP1Stdin, SP1VerifyingKey,
};

const ELF: Elf = include_elf!("my-program");
const PROOF_BYTES: &[u8] = include_bytes!("my-proof.bin");

fn main() {
// Synchronously create a ProverClient.
let client = ProverClient::builder().light().build();

let stdin = SP1Stdin::default();

// ... configure stdin ...

// Synchronously execute the program.
let (_, report) = client.execute(ELF, stdin.clone()).unwrap();

// Synchronously set up and generate a proof.
let proof: SP1ProofWithPublicValues = bincode::deserialize(PROOF_BYTES).unwrap();
client.verify(proof).unwrap();
}

Embedded allocator

The embedded allocator is now the default. Instead, we have a bump feature to use the bump allocator.

The embedded allocator can use more cycles than the bump allocator, but it re-uses addresses more efficiently. Note that there is a proving cost associated with the total number of touched addresses in the program. Even if the bump allocator saves cycles it might not be faster to prove than the embedded allocator.