feat: upgrade sp1 to v6.0.0

This commit is contained in:
han0110
2026-02-16 07:31:55 +00:00
parent ffc0e230cf
commit 206c19ac55
8 changed files with 1237 additions and 567 deletions

1393
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -137,8 +137,12 @@ risc0-zkvm = { version = "3.0.4", default-features = false }
risc0-zkvm-platform = { version = "2.2.1", default-features = false }
# SP1 dependencies
sp1-sdk = "5.2.4"
sp1-zkvm = { version = "5.2.4", default-features = false }
sp1-cuda = "6.0.0"
sp1-hypercube = "6.0.0"
sp1-recursion-executor = "6.0.0"
sp1-sdk = "6.0.0"
sp1-p3-field = { version = "0.3.1-succinct", package = "p3-field" }
sp1-zkvm = { version = "6.0.0", default-features = false }
# Ziren dependencies
zkm-sdk = { git = "https://github.com/ProjectZKM/Ziren.git", tag = "v1.2.3" }

View File

@@ -11,10 +11,15 @@ bincode = { workspace = true, features = ["alloc", "serde"] }
serde.workspace = true
tempfile.workspace = true
thiserror.workspace = true
tokio = { workspace = true, features = ["rt-multi-thread"], optional = true }
tracing.workspace = true
# SP1 dependencies
sp1-sdk = { workspace = true, optional = true }
sp1-cuda = { workspace = true, optional = true }
sp1-hypercube = { workspace = true, optional = true }
sp1-p3-field = { workspace = true, optional = true }
sp1-recursion-executor = { workspace = true, optional = true }
sp1-sdk = { workspace = true, features = ["network"], optional = true }
# Local dependencies
ere-compile-utils = { workspace = true, optional = true }
@@ -29,7 +34,7 @@ ere-build-utils.workspace = true
[features]
default = ["compiler", "zkvm"]
compiler = ["dep:ere-compile-utils"]
zkvm = ["dep:sp1-sdk"]
zkvm = ["dep:tokio", "dep:sp1-cuda", "dep:sp1-hypercube", "dep:sp1-p3-field", "dep:sp1-recursion-executor", "dep:sp1-sdk"]
[lints]
workspace = true

View File

@@ -15,10 +15,10 @@ ere-platform-trait.workspace = true
[features]
default = ["lib", "libm"]
blake3 = ["sp1-zkvm/blake3"]
embedded = ["sp1-zkvm/embedded"]
lib = ["sp1-zkvm/lib"]
libm = ["sp1-zkvm/libm"]
verify = ["sp1-zkvm/verify"]
untrusted_programs = ["sp1-zkvm/untrusted_programs"]
[lints]
workspace = true

View File

@@ -4,15 +4,13 @@ use ere_zkvm_interface::compiler::Compiler;
use std::{env, path::Path};
const TARGET_TRIPLE: &str = "riscv32ima-unknown-none-elf";
/// According to https://github.com/succinctlabs/sp1/blob/v6.0.0/crates/build/src/command/utils.rs#L49.
const RUSTFLAGS: &[&str] = &[
"-C",
"passes=lower-atomic", // Only for rustc > 1.81
"-C",
// Start of the code section
"link-arg=-Ttext=0x00201000",
"-C",
// The lowest memory location that will be used when your program is loaded
"link-arg=--image-base=0x00200800",
"-C",
"link-arg=--image-base=0x78000000",
"-C",
"panic=abort",
"--cfg",

View File

@@ -1,16 +1,11 @@
use crate::{program::SP1Program, zkvm::sdk::Prover};
use crate::{program::SP1Program, zkvm::sdk::SP1Sdk};
use anyhow::bail;
use ere_zkvm_interface::zkvm::{
CommonError, Input, ProgramExecutionReport, ProgramProvingReport, Proof, ProofKind,
ProverResource, PublicValues, zkVM, zkVMProgramDigest,
};
use sp1_sdk::{SP1ProofMode, SP1ProofWithPublicValues, SP1ProvingKey, SP1Stdin, SP1VerifyingKey};
use std::{
mem::take,
panic,
sync::{RwLock, RwLockReadGuard, RwLockWriteGuard},
time::Instant,
};
use sp1_sdk::{SP1ProofMode, SP1ProofWithPublicValues, SP1Stdin, SP1VerifyingKey};
use std::{future::Future, sync::OnceLock, time::Instant};
use tracing::info;
mod error;
@@ -21,42 +16,13 @@ pub use error::Error;
include!(concat!(env!("OUT_DIR"), "/name_and_sdk_version.rs"));
pub struct EreSP1 {
program: SP1Program,
/// Prover resource configuration for creating clients
resource: ProverResource,
/// Proving key
pk: SP1ProvingKey,
/// Verification key
vk: SP1VerifyingKey,
// The current version of SP1 (v5.2.4) has a problem where if GPU proving
// the program crashes in the Moongate container, it leaves an internal
// mutex poisoned, which prevents further proving attempts.
// This is a workaround to avoid the poisoned mutex issue by creating a new
// prover if the proving panics.
// Eventually, this should be fixed in the SP1 SDK.
// For more context see: https://github.com/eth-act/zkevm-benchmark-workload/issues/54
prover: RwLock<Prover>,
sdk: SP1Sdk,
}
impl EreSP1 {
pub fn new(program: SP1Program, resource: ProverResource) -> Result<Self, Error> {
let prover = Prover::new(&resource)?;
let (pk, vk) = prover.setup(&program.elf)?;
Ok(Self {
program,
resource,
pk,
vk,
prover: RwLock::new(prover),
})
}
fn prover(&'_ self) -> Result<RwLockReadGuard<'_, Prover>, Error> {
self.prover.read().map_err(|_| Error::RwLockPosioned)
}
fn prover_mut(&'_ self) -> Result<RwLockWriteGuard<'_, Prover>, Error> {
self.prover.write().map_err(|_| Error::RwLockPosioned)
let sdk = block_on(SP1Sdk::new(program.elf, &resource))?;
Ok(Self { sdk })
}
}
@@ -64,10 +30,8 @@ impl zkVM for EreSP1 {
fn execute(&self, input: &Input) -> anyhow::Result<(PublicValues, ProgramExecutionReport)> {
let stdin = input_to_stdin(input)?;
let prover = self.prover()?;
let start = Instant::now();
let (public_values, exec_report) = prover.execute(self.program.elf(), &stdin)?;
let (public_values, exec_report) = block_on(self.sdk.execute(stdin))?;
let execution_duration = start.elapsed();
Ok((
@@ -85,7 +49,7 @@ impl zkVM for EreSP1 {
input: &Input,
proof_kind: ProofKind,
) -> anyhow::Result<(PublicValues, Proof, ProgramProvingReport)> {
info!("Generating proof");
info!("Generating proof...");
let stdin = input_to_stdin(input)?;
@@ -94,27 +58,8 @@ impl zkVM for EreSP1 {
ProofKind::Groth16 => SP1ProofMode::Groth16,
};
let mut prover = self.prover_mut()?;
// Restart GPU prover if the prover is dropped before.
if matches!(self.resource, ProverResource::Gpu) && matches!(&*prover, Prover::Cpu(_)) {
*prover = Prover::new(&self.resource).and_then(|prover| {
prover.setup(&self.program.elf)?;
Ok(prover)
})?;
}
let start = Instant::now();
let proof =
panic::catch_unwind(|| prover.prove(&self.pk, &stdin, mode)).map_err(|err| {
if matches!(self.resource, ProverResource::Gpu) {
// Drop the panicked GPU prover and replace it with CPU one,
// next prove call will try to restart it.
take(&mut *prover);
}
Error::Panic(panic_msg(err))
})??;
let proof = block_on(self.sdk.prove(stdin, mode))?;
let proving_time = start.elapsed();
let public_values = proof.public_values.to_vec();
@@ -132,7 +77,7 @@ impl zkVM for EreSP1 {
}
fn verify(&self, proof: &Proof) -> anyhow::Result<PublicValues> {
info!("Verifying proof");
info!("Verifying proof...");
let proof_kind = proof.kind();
@@ -149,7 +94,7 @@ impl zkVM for EreSP1 {
bail!(Error::InvalidProofKind(proof_kind, inner_proof_kind));
}
self.prover()?.verify(&proof, &self.vk)?;
self.sdk.verify(&proof)?;
let public_values_bytes = proof.public_values.as_slice().to_vec();
@@ -169,7 +114,7 @@ impl zkVMProgramDigest for EreSP1 {
type ProgramDigest = SP1VerifyingKey;
fn program_digest(&self) -> anyhow::Result<Self::ProgramDigest> {
Ok(self.vk.clone())
Ok(self.sdk.verifying_key().clone())
}
}
@@ -184,10 +129,16 @@ fn input_to_stdin(input: &Input) -> Result<SP1Stdin, Error> {
Ok(stdin)
}
fn panic_msg(err: Box<dyn std::any::Any + Send + 'static>) -> String {
None.or_else(|| err.downcast_ref::<String>().cloned())
.or_else(|| err.downcast_ref::<&'static str>().map(ToString::to_string))
.unwrap_or_else(|| "unknown panic msg".to_string())
fn block_on<T>(future: impl Future<Output = T>) -> T {
match tokio::runtime::Handle::try_current() {
Ok(handle) => tokio::task::block_in_place(|| handle.block_on(future)),
Err(_) => {
static FALLBACK_RT: OnceLock<tokio::runtime::Runtime> = OnceLock::new();
FALLBACK_RT
.get_or_init(|| tokio::runtime::Runtime::new().expect("Failed to create runtime"))
.block_on(future)
}
}
}
#[cfg(test)]

View File

@@ -7,14 +7,8 @@ pub enum Error {
#[error(transparent)]
CommonError(#[from] CommonError),
#[error("Prover RwLock posioned, panic not catched properly")]
RwLockPosioned,
#[error("Failed to setup ELF: {0}")]
SetupElfFailed(String),
#[error("Failed to initialize cuda prover: {0}")]
InitCudaProverFailed(String),
Setup(#[source] anyhow::Error),
#[error("Deserialize proofs in Input failed: {0:?}")]
DeserializeInputProofs(bincode::error::DecodeError),
@@ -26,12 +20,15 @@ pub enum Error {
#[error("SP1 execution failed: {0}")]
Execute(#[source] anyhow::Error),
#[error("SP1 execution completed with non-success exit code: {0}")]
ExecutionFailed(u32),
// Prove
#[error("SP1 SDK proving failed: {0}")]
Prove(#[source] anyhow::Error),
#[error("SP1 proving panicked: {0}")]
Panic(String),
#[error("Failed to extract exit code from proof")]
ExitCodeExtractionFailed,
// Verify
#[error("Invalid proof kind, expected: {0:?}, got: {1:?}")]
@@ -40,3 +37,13 @@ pub enum Error {
#[error("SP1 SDK verification failed: {0}")]
Verify(#[source] SP1VerificationError),
}
impl Error {
pub fn setup(err: impl Into<anyhow::Error>) -> Self {
Self::Setup(err.into())
}
pub fn prove(err: impl Into<anyhow::Error>) -> Self {
Self::Prove(err.into())
}
}

View File

@@ -1,42 +1,57 @@
use crate::zkvm::{Error, panic_msg};
use crate::zkvm::Error;
use ere_zkvm_interface::{
CommonError, RemoteProverConfig,
zkvm::{ProverResource, ProverResourceKind},
};
use sp1_cuda::CudaProvingKey;
use sp1_hypercube::air::PublicValues;
use sp1_p3_field::PrimeField32;
use sp1_recursion_executor::RecursionPublicValues;
use sp1_sdk::{
CpuProver, NetworkProver, Prover as _, ProverClient, SP1ProofMode, SP1ProofWithPublicValues,
SP1ProvingKey, SP1Stdin, SP1VerifyingKey,
CpuProver, CudaProver, Elf, ExecutionReport, NetworkProver, ProveRequest, Prover as SP1Prover,
ProverClient, ProvingKey as SP1ProvingKeyTrait, SP1Proof, SP1ProofMode,
SP1ProofWithPublicValues, SP1ProvingKey as CpuProvingKey, SP1PublicValues, SP1Stdin,
SP1VerifyingKey, StatusCode,
};
use std::{
env,
ops::Deref,
panic::{self, AssertUnwindSafe},
process::Command,
};
use tracing::error;
use std::{borrow::Borrow, env, sync::Arc};
// https://github.com/succinctlabs/sp1/blob/v5.2.4/crates/cuda/src/lib.rs#L207C34-L207C78.
const SP1_CUDA_IMAGE: &str = "public.ecr.aws/succinct-labs/sp1-gpu:8fd1ef7";
#[allow(clippy::large_enum_variant)]
pub enum Prover {
Cpu(CpuProver),
Gpu(CudaProver),
Network(NetworkProver),
pub enum SP1Sdk {
Cpu {
prover: CpuProver,
pk: CpuProvingKey,
},
Gpu {
prover: CudaProver,
pk: CudaProvingKey,
},
Network {
prover: Box<NetworkProver>,
pk: CpuProvingKey,
},
}
impl Default for Prover {
fn default() -> Self {
Self::new(&ProverResource::Cpu).unwrap()
}
}
impl Prover {
pub fn new(resource: &ProverResource) -> Result<Self, Error> {
impl SP1Sdk {
pub async fn new(elf: Vec<u8>, resource: &ProverResource) -> Result<Self, Error> {
let elf = Elf::Dynamic(Arc::from(elf));
Ok(match resource {
ProverResource::Cpu => Self::Cpu(ProverClient::builder().cpu().build()),
ProverResource::Gpu => Self::Gpu(CudaProver::new()?),
ProverResource::Network(config) => Self::Network(build_network_prover(config)?),
ProverResource::Cpu => {
let prover = ProverClient::builder().cpu().build().await;
let pk = prover.setup(elf).await.map_err(Error::setup)?;
Self::Cpu { prover, pk }
}
ProverResource::Gpu => {
let prover = ProverClient::builder().cuda().build().await;
let pk = prover.setup(elf).await.map_err(Error::setup)?;
Self::Gpu { prover, pk }
}
ProverResource::Network(config) => {
let prover = build_network_prover(config).await?;
let pk = prover.setup(elf).await.map_err(Error::setup)?;
Self::Network {
prover: Box::new(prover),
pk,
}
}
_ => Err(CommonError::unsupported_prover_resource_kind(
resource.kind(),
[
@@ -48,135 +63,73 @@ impl Prover {
})
}
pub fn setup(&self, elf: &[u8]) -> Result<(SP1ProvingKey, SP1VerifyingKey), Error> {
panic::catch_unwind(AssertUnwindSafe(|| match self {
Self::Cpu(cpu_prover) => cpu_prover.setup(elf),
Self::Gpu(cuda_prover) => cuda_prover.setup(elf),
Self::Network(network_prover) => network_prover.setup(elf),
}))
.map_err(|err| Error::SetupElfFailed(panic_msg(err)))
}
pub fn execute(
&self,
elf: &[u8],
input: &SP1Stdin,
) -> Result<(sp1_sdk::SP1PublicValues, sp1_sdk::ExecutionReport), Error> {
pub fn verifying_key(&self) -> &SP1VerifyingKey {
match self {
Self::Cpu(cpu_prover) => cpu_prover.execute(elf, input).run(),
Self::Gpu(cuda_prover) => cuda_prover.execute(elf, input).run(),
Self::Network(network_prover) => network_prover.execute(elf, input).run(),
Self::Cpu { pk, .. } => pk.verifying_key(),
Self::Gpu { pk, .. } => pk.verifying_key(),
Self::Network { pk, .. } => pk.verifying_key(),
}
.map_err(Error::Execute)
}
pub fn prove(
pub async fn execute(
&self,
pk: &SP1ProvingKey,
input: &SP1Stdin,
input: SP1Stdin,
) -> Result<(SP1PublicValues, ExecutionReport), Error> {
let (public_values, exec_report) = match self {
Self::Cpu { prover, pk } => prover.execute(pk.elf().clone(), input).await,
Self::Gpu { prover, pk } => prover.execute(pk.elf().clone(), input).await,
Self::Network { prover, pk } => prover.execute(pk.elf().clone(), input).await,
}
.map_err(|e| Error::Execute(e.into()))?;
let exit_code = exec_report.exit_code as u32;
if exit_code != StatusCode::SUCCESS.as_u32() {
return Err(Error::ExecutionFailed(exit_code));
}
Ok((public_values, exec_report))
}
pub async fn prove(
&self,
input: SP1Stdin,
mode: SP1ProofMode,
) -> Result<SP1ProofWithPublicValues, Error> {
match self {
Self::Cpu(cpu_prover) => cpu_prover.prove(pk, input).mode(mode).run(),
Self::Gpu(cuda_prover) => cuda_prover.prove(pk, input).mode(mode).run(),
Self::Network(network_prover) => network_prover.prove(pk, input).mode(mode).run(),
let proof = match self {
Self::Cpu { prover, pk } => {
let req = prover.prove(pk, input).mode(mode);
req.await.map_err(Error::prove)
}
Self::Gpu { prover, pk } => {
let req = prover.prove(pk, input).mode(mode);
req.await.map_err(Error::prove)
}
Self::Network { prover, pk } => {
let req = prover.prove(pk, input).mode(mode);
req.await.map_err(Error::prove)
}
}?;
let exit_code = extract_exit_code(&proof)?;
if exit_code != StatusCode::SUCCESS.as_u32() {
return Err(Error::ExecutionFailed(exit_code));
}
.map_err(Error::Prove)
Ok(proof)
}
pub fn verify(
&self,
proof: &SP1ProofWithPublicValues,
vk: &SP1VerifyingKey,
) -> Result<(), Error> {
pub fn verify(&self, proof: &SP1ProofWithPublicValues) -> Result<(), Error> {
let vk = self.verifying_key();
match self {
Self::Cpu(cpu_prover) => cpu_prover.verify(proof, vk),
Self::Gpu(cuda_prover) => cuda_prover.verify(proof, vk),
Self::Network(network_prover) => network_prover.verify(proof, vk),
Self::Cpu { prover, .. } => prover.verify(proof, vk, Some(StatusCode::SUCCESS)),
Self::Gpu { prover, .. } => prover.verify(proof, vk, Some(StatusCode::SUCCESS)),
Self::Network { prover, .. } => prover.verify(proof, vk, Some(StatusCode::SUCCESS)),
}
.map_err(Error::Verify)
}
}
pub struct CudaProver {
container_name: String,
prover: sp1_sdk::CudaProver,
}
impl Deref for CudaProver {
type Target = sp1_sdk::CudaProver;
fn deref(&self) -> &Self::Target {
&self.prover
}
}
impl Drop for CudaProver {
fn drop(&mut self) {
let mut cmd = Command::new("docker");
cmd.args(["container", "rm", "--force", self.container_name.as_ref()]);
if let Err(err) = cmd
.output()
.map_err(|err| CommonError::command(&cmd, err))
.and_then(|output| {
(!output.status.success()).then_some(()).ok_or_else(|| {
CommonError::command_exit_non_zero(&cmd, output.status, Some(&output))
})
})
{
error!(
"Failed to remove docker container {}: {err}",
self.container_name
);
}
}
}
impl CudaProver {
fn new() -> Result<Self, Error> {
// Ported from https://github.com/succinctlabs/sp1/blob/v5.2.4/crates/cuda/src/lib.rs#L199.
let container_name = "sp1-gpu".to_string();
let image_name = env::var("SP1_GPU_IMAGE").unwrap_or_else(|_| SP1_CUDA_IMAGE.to_string());
let rust_log = env::var("RUST_LOG").unwrap_or_else(|_| "none".to_string());
let gpus = env::var("ERE_GPU_DEVICES").unwrap_or_else(|_| "all".to_string());
let mut cmd = Command::new("docker");
cmd.args([
"run",
"--rm",
"--env",
&format!("RUST_LOG={rust_log}"),
"--publish",
"3000:3000",
"--gpus",
&gpus,
"--name",
&container_name,
&image_name,
]);
let host = if let Ok(network) = env::var("ERE_DOCKER_NETWORK") {
cmd.args(["--network", network.as_str()]);
container_name.as_str()
} else {
"127.0.0.1"
};
let endpoint = format!("http://{host}:3000/twirp/");
cmd.spawn().map_err(|err| CommonError::command(&cmd, err))?;
let prover =
panic::catch_unwind(|| ProverClient::builder().cuda().server(&endpoint).build())
.map_err(|err| Error::InitCudaProverFailed(panic_msg(err)))?;
Ok(Self {
container_name,
prover,
})
}
}
fn build_network_prover(config: &RemoteProverConfig) -> Result<NetworkProver, Error> {
async fn build_network_prover(config: &RemoteProverConfig) -> Result<NetworkProver, Error> {
let mut builder = ProverClient::builder().network();
// Check if we have a private key in the config or environment
if let Some(api_key) = &config.api_key {
@@ -193,5 +146,32 @@ fn build_network_prover(config: &RemoteProverConfig) -> Result<NetworkProver, Er
builder = builder.rpc_url(&rpc_url);
}
// Otherwise SP1 SDK will use its default RPC URL
Ok(builder.build())
Ok(builder.build().await)
}
/// Extracts the exit code from an public values of proof.
///
/// The `exit_code` field is extracted from the public values struct of proof,
/// mirroring the approach used in `verify_proof` of `sp1_sdk`.
fn extract_exit_code(proof: &SP1ProofWithPublicValues) -> Result<u32, Error> {
match &proof.proof {
SP1Proof::Core(shard_proofs) => shard_proofs.last().map(|proof| {
let pv: &PublicValues<[_; 4], [_; 3], [_; 4], _> =
proof.public_values.as_slice().borrow();
pv.exit_code.as_canonical_u32()
}),
SP1Proof::Compressed(proof) => {
let pv: &RecursionPublicValues<_> = proof.proof.public_values.as_slice().borrow();
Some(pv.exit_code.as_canonical_u32())
}
SP1Proof::Plonk(proof) => proof
.public_inputs
.get(2)
.and_then(|value| value.parse::<u32>().ok()),
SP1Proof::Groth16(proof) => proof
.public_inputs
.get(2)
.and_then(|value| value.parse::<u32>().ok()),
}
.ok_or(Error::ExitCodeExtractionFailed)
}