Skip to content

Commit 0d6f081

Browse files
authored
Add SP1 hash-based signature benchmark (#14)
This PR adds an SP1 benchmark suite for hashed-based signature aggregation benchmark using the same structure as the risc0 benchmarks. The `sp1/guest` crate contains the SP1 program that verifies the aggregated signature. The `sp1/host` crate contains the benchmark that generates the test, and drives the SP1 guest program. Scripts `benchmark-sp1-{cpu, cuda}.sh` are added to run the benchmarks. NB: The existing `benchmark-{cpu, coda}.sh` scripts are renamed to `benchmark-risc0-{cpu, cuda}.sh`.
2 parents 393a619 + c8fb0ca commit 0d6f081

File tree

10 files changed

+401
-1
lines changed

10 files changed

+401
-1
lines changed

Cargo.toml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
[workspace]
22
resolver = "2"
3-
members = ["crates/risc0/host", "crates/risc0/methods", "crates/shared", "crates/core"]
3+
members = [
4+
"crates/shared",
5+
"crates/core",
6+
"crates/risc0/host",
7+
"crates/risc0/methods",
8+
"crates/sp1/host",
9+
"crates/sp1/guest"
10+
]
411

512
# Always optimize; building and running the guest takes much longer without optimization.
613
[profile.dev]
File renamed without changes.

benchmark-sp1-cpu.sh

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
#!/bin/bash
2+
3+
# SP1 XMSS Benchmark Script (CPU Only)
4+
# This script runs benchmarks without CUDA acceleration
5+
6+
set -e
7+
8+
echo "╔══════════════════════════════════════════════════════╗"
9+
echo "║ SP1 XMSS Aggregate Benchmark Suite (CPU) ║"
10+
echo "╚══════════════════════════════════════════════════════╝"
11+
echo
12+
13+
# Set environment variables for optimal SP1 performance
14+
export RUST_LOG=info
15+
16+
echo "🔨 Building SP1 guest program..."
17+
cd crates/sp1/guest
18+
cargo prove build
19+
cd ../../..
20+
21+
echo "📦 Building SP1 host..."
22+
cargo build --release -p sp1-host
23+
24+
echo
25+
echo "💻 Running benchmarks on CPU..."
26+
echo " Sample size: 10 iterations per benchmark"
27+
echo
28+
29+
# Run the Criterion benchmark
30+
cargo bench -p sp1-host
31+
32+
echo
33+
echo "📊 Benchmark results saved to:"
34+
echo " target/criterion/index.html"
35+
echo
36+
echo "✅ Benchmark complete!"

benchmark-sp1-cuda.sh

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
#!/bin/bash
2+
3+
# SP1 XMSS Benchmark Script with CUDA Support
4+
# This script runs benchmarks with CUDA acceleration if available
5+
6+
set -e
7+
8+
echo "╔══════════════════════════════════════════════════════╗"
9+
echo "║ SP1 XMSS Aggregate Benchmark Suite (CUDA) ║"
10+
echo "╚══════════════════════════════════════════════════════╝"
11+
echo
12+
13+
# Check if CUDA is available
14+
if command -v nvidia-smi &> /dev/null; then
15+
echo "✅ NVIDIA GPU detected:"
16+
nvidia-smi --query-gpu=name,memory.total --format=csv,noheader
17+
echo
18+
else
19+
echo "⚠️ No NVIDIA GPU detected. Will run on CPU."
20+
echo
21+
fi
22+
23+
# Set environment variables for optimal SP1 performance
24+
export RUST_LOG=info
25+
26+
echo "🔨 Building SP1 guest program..."
27+
cd crates/sp1/guest
28+
cargo prove build
29+
cd ../../..
30+
31+
echo "📦 Building SP1 host with CUDA support..."
32+
cargo build --release --features cuda -p sp1-host
33+
34+
echo
35+
echo "🚀 Running benchmarks with CUDA acceleration..."
36+
echo " Sample size: 10 iterations per benchmark"
37+
echo
38+
39+
# Run the Criterion benchmark
40+
cargo bench --features cuda -p sp1-host
41+
42+
echo
43+
echo "📊 Benchmark results saved to:"
44+
echo " target/criterion/index.html"
45+
echo
46+
echo "✅ Benchmark complete!"

crates/sp1/guest/Cargo.toml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
[package]
2+
name = "sp1-guest"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[dependencies]
7+
sp1-zkvm = "4.0.0"
8+
leansig-core = { path = "../../core" }
9+
leansig-shared = { path = "../../shared" }

crates/sp1/guest/src/main.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
#![no_main]
2+
sp1_zkvm::entrypoint!(main);
3+
4+
use leansig_core::AggregatedVerifier;
5+
use leansig_shared::XmssTestData;
6+
7+
pub fn main() {
8+
// Read the test data containing both public inputs and aggregated signature
9+
let test_data = sp1_zkvm::io::read::<XmssTestData>();
10+
11+
// Extract the components
12+
let public_inputs = test_data.public_inputs;
13+
let aggregated_signature = test_data.aggregated_signature;
14+
15+
// Create the aggregated verifier with the validator roots
16+
let verifier = AggregatedVerifier::new(
17+
public_inputs.validator_roots.clone(),
18+
public_inputs.spec.clone(),
19+
);
20+
21+
// Verify the aggregated signature
22+
let verification_result = verifier.verify(&public_inputs.message, &aggregated_signature);
23+
24+
// The verification must succeed, otherwise the proof generation will fail
25+
assert!(verification_result, "XMSS signature verification failed");
26+
27+
// Commit the public inputs to the journal for the host to verify
28+
// This ensures the proof is bound to specific inputs
29+
sp1_zkvm::io::commit(&public_inputs);
30+
31+
// Optionally commit a success flag
32+
sp1_zkvm::io::commit(&verification_result);
33+
}

crates/sp1/host/Cargo.toml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
[package]
2+
name = "sp1-host"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[dependencies]
7+
sp1-sdk = "4.0.0"
8+
leansig-shared = { path = "../../shared" }
9+
leansig-core = { path = "../../core" }
10+
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
11+
serde = "1.0"
12+
bincode = "1.3"
13+
rand = "0.9"
14+
15+
[dev-dependencies]
16+
criterion = { version = "0.5", features = ["html_reports"] }
17+
leansig-shared = { path = "../../shared" }
18+
19+
[[bench]]
20+
name = "xmss_benchmark"
21+
harness = false
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
use criterion::{Criterion, black_box, criterion_group, criterion_main};
2+
use leansig_core::spec::{SPEC_1, SPEC_2, Spec};
3+
use sp1_sdk::{ProverClient, SP1Stdin, SP1ProofWithPublicValues};
4+
use leansig_shared::{create_test_data, XmssTestData};
5+
use std::time::{Duration, Instant};
6+
7+
const ELF: &[u8] = include_bytes!("../../../../target/elf-compilation/riscv32im-succinct-zkvm-elf/release/sp1-guest");
8+
9+
/// Configuration parameters for benchmarking
10+
struct BenchmarkConfig {
11+
num_validators: usize,
12+
tree_height: usize,
13+
spec: Spec,
14+
}
15+
16+
impl Default for BenchmarkConfig {
17+
fn default() -> Self {
18+
Self {
19+
num_validators: 16,
20+
tree_height: 13,
21+
spec: SPEC_2,
22+
}
23+
}
24+
}
25+
26+
impl BenchmarkConfig {
27+
fn from_env() -> Self {
28+
let mut config = Self::default();
29+
30+
if let Ok(val) = std::env::var("BENCH_VALIDATORS") {
31+
if let Ok(n) = val.parse() {
32+
config.num_validators = n;
33+
}
34+
}
35+
36+
if let Ok(val) = std::env::var("BENCH_TREE_HEIGHT") {
37+
if let Ok(h) = val.parse() {
38+
config.tree_height = h;
39+
}
40+
}
41+
42+
if let Ok(val) = std::env::var("BENCH_SPEC") {
43+
config.spec = match val.as_str() {
44+
"1" | "SPEC_1" => SPEC_1,
45+
"2" | "SPEC_2" => SPEC_2,
46+
_ => SPEC_2,
47+
};
48+
}
49+
50+
config
51+
}
52+
}
53+
54+
/// Job structure for benchmarking XMSS signatures with SP1
55+
struct Job {
56+
test_data: XmssTestData,
57+
}
58+
59+
impl Job {
60+
fn new(config: BenchmarkConfig) -> Self {
61+
// Create test data with specified parameters
62+
let test_data = create_test_data(
63+
config.num_validators,
64+
config.spec.clone(),
65+
config.tree_height,
66+
10000, // max_retries for nonce grinding
67+
None, // use default message [42; 32]
68+
None, // use default epoch 0
69+
);
70+
71+
Self {
72+
test_data,
73+
}
74+
}
75+
76+
/// Execute witness generation phase (SP1 setup + stdin preparation)
77+
fn exec_compute(&self) -> (SP1Stdin, Duration) {
78+
let start = Instant::now();
79+
80+
let mut stdin = SP1Stdin::new();
81+
stdin.write(&self.test_data);
82+
83+
let elapsed = start.elapsed();
84+
85+
(stdin, elapsed)
86+
}
87+
88+
/// Execute proving phase
89+
fn prove_stdin(
90+
&self,
91+
stdin: &SP1Stdin,
92+
) -> (SP1ProofWithPublicValues, Duration) {
93+
let client = ProverClient::from_env();
94+
let (pk, _vk) = client.setup(ELF);
95+
96+
let start = Instant::now();
97+
let proof = client.prove(&pk, stdin).run().unwrap();
98+
let elapsed = start.elapsed();
99+
100+
(proof, elapsed)
101+
}
102+
}
103+
104+
/// Main benchmarking function
105+
fn xmss_benchmarks(c: &mut Criterion) {
106+
let config = BenchmarkConfig::from_env();
107+
108+
println!("\n════════════════════════════════════════════════");
109+
println!("SP1 XMSS Signature Benchmark Configuration:");
110+
println!(" Validators: {}", config.num_validators);
111+
println!(
112+
" Tree Height: {} (max {} signatures)",
113+
config.tree_height,
114+
1 << config.tree_height
115+
);
116+
println!(
117+
" Spec: {}",
118+
if config.spec.target_sum == SPEC_1.target_sum {
119+
"SPEC_1"
120+
} else {
121+
"SPEC_2"
122+
}
123+
);
124+
println!("════════════════════════════════════════════════\n");
125+
126+
let mut group = c.benchmark_group("sp1_xmss_signature");
127+
128+
// Configure the benchmark group
129+
group.sample_size(10);
130+
group.measurement_time(Duration::from_secs(10));
131+
132+
let job = Job::new(config);
133+
134+
// Benchmark 1: Witness Generation (setup + stdin preparation)
135+
group.bench_function("witness_generation", |b| {
136+
b.iter(|| {
137+
let (stdin, _duration) = job.exec_compute();
138+
black_box(stdin);
139+
});
140+
});
141+
142+
// Pre-compute stdin for proving/verification benchmarks
143+
let (stdin, _) = job.exec_compute();
144+
145+
// Reset group configuration for proof generation
146+
group.finish();
147+
148+
// Create new group for proof generation benchmarks
149+
let mut group = c.benchmark_group("sp1_xmss_signature_proving");
150+
group.sample_size(10);
151+
152+
// Benchmark 2: Proof Generation
153+
group.bench_function("proof_generation", |b| {
154+
b.iter(|| {
155+
// We need to recreate stdin for each iteration since it gets consumed
156+
let mut fresh_stdin = SP1Stdin::new();
157+
fresh_stdin.write(&job.test_data);
158+
159+
let (proof, _duration) = job.prove_stdin(&fresh_stdin);
160+
black_box(proof);
161+
});
162+
});
163+
164+
// Generate proof for verification benchmark
165+
let mut fresh_stdin = SP1Stdin::new();
166+
fresh_stdin.write(&job.test_data);
167+
let (proof, _) = job.prove_stdin(&fresh_stdin);
168+
169+
group.finish();
170+
171+
// Create new group for verification benchmarks
172+
let mut group = c.benchmark_group("sp1_xmss_signature_verification");
173+
group.sample_size(100); // Many samples for quick operation
174+
group.measurement_time(Duration::from_secs(5));
175+
176+
group.bench_function("proof_verification", |b| {
177+
let client = ProverClient::from_env();
178+
let (_pk, vk) = client.setup(ELF);
179+
180+
b.iter(|| {
181+
client.verify(&proof, &vk).unwrap();
182+
});
183+
});
184+
185+
// Print additional metrics
186+
println!("\nSP1 Additional Metrics:");
187+
let proof_size_bytes = bincode::serialize(&proof).unwrap().len();
188+
println!(" Proof Size: {:.2} KiB ({} bytes)", proof_size_bytes as f64 / 1024.0, proof_size_bytes);
189+
190+
group.finish();
191+
}
192+
193+
criterion_group!(sp1_xmss_signature, xmss_benchmarks);
194+
criterion_main!(sp1_xmss_signature);

0 commit comments

Comments
 (0)