Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ jobs:
strategy:
fail-fast: false
matrix:
example: [ethsign, zklogin, sha256, sha512, keccak, blake2s, hash_based_sig]
example: [ethsign, zklogin, sha256, sha512, keccak, blake2s, hashsign]
steps:
- name: Checkout Repository
uses: actions/checkout@v4
Expand Down
4 changes: 4 additions & 0 deletions crates/examples/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ harness = false
name = "blake2s"
harness = false

[[bench]]
name = "hashsign"
harness = false

[features]
default = ["rayon"]
perfetto = ["tracing-profile/perfetto"]
Expand Down
48 changes: 13 additions & 35 deletions crates/examples/benches/blake2s.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,35 +17,6 @@ use criterion::{BenchmarkId, Criterion, Throughput, criterion_group, criterion_m

const DEFAULT_MAX_BYTES: usize = 2048 * 64; // 2048 blocks × 64 bytes/block = 131,072 bytes

fn get_feature_suffix(_diagnostics: &PlatformDiagnostics) -> String {
let mut suffix_parts = vec![];

// Add architecture
#[cfg(target_arch = "x86_64")]
{
suffix_parts.push("x86_64");
// Add key features based on compile-time features
#[cfg(target_feature = "gfni")]
suffix_parts.push("gfni");
#[cfg(target_feature = "avx512f")]
suffix_parts.push("avx512");
#[cfg(all(not(target_feature = "avx512f"), target_feature = "avx2"))]
suffix_parts.push("avx2");
}

#[cfg(target_arch = "aarch64")]
{
suffix_parts.push("arm64");
// Check for NEON and AES
#[cfg(all(target_feature = "neon", target_feature = "aes"))]
suffix_parts.push("neon_aes");
#[cfg(all(target_feature = "neon", not(target_feature = "aes")))]
suffix_parts.push("neon");
}

suffix_parts.join("_")
}

/// Benchmark Blake2s hash circuit
fn bench_blake2s_hash(c: &mut Criterion) {
// Get maximum message size from environment or use default
Expand Down Expand Up @@ -84,15 +55,18 @@ fn bench_blake2s_hash(c: &mut Criterion) {
circuit.populate_wire_witness(&mut filler).unwrap();
let witness = filler.into_value_vec();

let feature_suffix = get_feature_suffix(&diagnostics);
let feature_suffix = diagnostics.get_feature_suffix();

// Benchmark 1: Witness generation
{
let mut group = c.benchmark_group("blake2s_witness_generation");
group.throughput(Throughput::Bytes(max_bytes as u64));
group.warm_up_time(std::time::Duration::from_secs(2));
group.measurement_time(std::time::Duration::from_secs(120));
group.sample_size(50);

let bench_name = format!("bytes_{}_{}", max_bytes, feature_suffix);
group.bench_with_input(BenchmarkId::from_parameter(&bench_name), &max_bytes, |b, _| {
group.bench_function(BenchmarkId::from_parameter(&bench_name), |b| {
b.iter(|| {
let mut filler = circuit.new_witness_filler();
example
Expand All @@ -110,11 +84,12 @@ fn bench_blake2s_hash(c: &mut Criterion) {
{
let mut group = c.benchmark_group("blake2s_proof_generation");
group.throughput(Throughput::Bytes(max_bytes as u64));
group.measurement_time(std::time::Duration::from_secs(120)); // 120 seconds measurement time
group.sample_size(100); // Keep 100 samples
group.warm_up_time(std::time::Duration::from_secs(2));
group.measurement_time(std::time::Duration::from_secs(120));
group.sample_size(50);

let bench_name = format!("bytes_{}_{}", max_bytes, feature_suffix);
group.bench_with_input(BenchmarkId::from_parameter(&bench_name), &max_bytes, |b, _| {
group.bench_function(BenchmarkId::from_parameter(&bench_name), |b| {
b.iter(|| {
let mut prover_transcript = ProverTranscript::new(StdChallenger::default());
prover
Expand All @@ -137,9 +112,12 @@ fn bench_blake2s_hash(c: &mut Criterion) {
{
let mut group = c.benchmark_group("blake2s_proof_verification");
group.throughput(Throughput::Bytes(max_bytes as u64));
group.warm_up_time(std::time::Duration::from_secs(2));
group.measurement_time(std::time::Duration::from_secs(120));
group.sample_size(50);

let bench_name = format!("bytes_{}_{}", max_bytes, feature_suffix);
group.bench_with_input(BenchmarkId::from_parameter(&bench_name), &max_bytes, |b, _| {
group.bench_function(BenchmarkId::from_parameter(&bench_name), |b| {
b.iter(|| {
let mut verifier_transcript =
VerifierTranscript::new(StdChallenger::default(), proof_bytes.clone());
Expand Down
53 changes: 13 additions & 40 deletions crates/examples/benches/ethsign.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,42 +13,6 @@ use binius_verifier::{
};
use criterion::{BenchmarkId, Criterion, Throughput, criterion_group, criterion_main};

/// Generate a feature suffix for benchmark names based on platform diagnostics
fn get_feature_suffix(_diagnostics: &PlatformDiagnostics) -> String {
let mut suffix_parts = Vec::new();

// Threading - check if rayon feature is enabled
#[cfg(feature = "rayon")]
suffix_parts.push("mt");
#[cfg(not(feature = "rayon"))]
suffix_parts.push("st");

// Architecture
#[cfg(target_arch = "x86_64")]
{
suffix_parts.push("x86");
// Add key features based on compile-time features
#[cfg(target_feature = "gfni")]
suffix_parts.push("gfni");
#[cfg(target_feature = "avx512f")]
suffix_parts.push("avx512");
#[cfg(all(not(target_feature = "avx512f"), target_feature = "avx2"))]
suffix_parts.push("avx2");
}

#[cfg(target_arch = "aarch64")]
{
suffix_parts.push("arm64");
// Check for NEON and AES
#[cfg(all(target_feature = "neon", target_feature = "aes"))]
suffix_parts.push("neon_aes");
#[cfg(all(target_feature = "neon", not(target_feature = "aes")))]
suffix_parts.push("neon");
}

suffix_parts.join("_")
}

fn bench_ethsign_signatures(c: &mut Criterion) {
// Parse parameters from environment variables or use defaults
let n_signatures = env::var("ETHSIGN_SIGNATURES")
Expand Down Expand Up @@ -92,15 +56,18 @@ fn bench_ethsign_signatures(c: &mut Criterion) {
circuit.populate_wire_witness(&mut filler).unwrap();
let witness = filler.into_value_vec();

let feature_suffix = get_feature_suffix(&diagnostics);
let feature_suffix = diagnostics.get_feature_suffix();
let bench_name = format!("sig_{}_msg_{}_{}", n_signatures, max_msg_len_bytes, feature_suffix);

// Measure witness generation time
{
let mut group = c.benchmark_group("ethsign_witness_generation");
group.throughput(Throughput::Elements(n_signatures as u64));
group.warm_up_time(std::time::Duration::from_secs(2));
group.measurement_time(std::time::Duration::from_secs(120));
group.sample_size(50);

group.bench_with_input(BenchmarkId::from_parameter(&bench_name), &bench_name, |b, _| {
group.bench_function(BenchmarkId::from_parameter(&bench_name), |b| {
b.iter(|| {
let mut filler = circuit.new_witness_filler();
example
Expand All @@ -118,8 +85,11 @@ fn bench_ethsign_signatures(c: &mut Criterion) {
{
let mut group = c.benchmark_group("ethsign_proof_generation");
group.throughput(Throughput::Elements(n_signatures as u64));
group.warm_up_time(std::time::Duration::from_secs(2));
group.measurement_time(std::time::Duration::from_secs(120));
group.sample_size(50);

group.bench_with_input(BenchmarkId::from_parameter(&bench_name), &bench_name, |b, _| {
group.bench_function(BenchmarkId::from_parameter(&bench_name), |b| {
b.iter(|| {
let mut prover_transcript = ProverTranscript::new(StdChallenger::default());
prover
Expand All @@ -144,8 +114,11 @@ fn bench_ethsign_signatures(c: &mut Criterion) {
{
let mut group = c.benchmark_group("ethsign_proof_verification");
group.throughput(Throughput::Elements(n_signatures as u64));
group.warm_up_time(std::time::Duration::from_secs(2));
group.measurement_time(std::time::Duration::from_secs(120));
group.sample_size(50);

group.bench_with_input(BenchmarkId::from_parameter(&bench_name), &bench_name, |b, _| {
group.bench_function(BenchmarkId::from_parameter(&bench_name), |b| {
b.iter(|| {
let mut verifier_transcript =
VerifierTranscript::new(StdChallenger::default(), proof_bytes.clone());
Expand Down
152 changes: 152 additions & 0 deletions crates/examples/benches/hashsign.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
use std::env;

use binius_examples::{
ExampleCircuit,
circuits::hashsign::{HashBasedSigExample, Instance, Params},
setup,
};
use binius_frontend::compiler::CircuitBuilder;
use binius_utils::platform_diagnostics::PlatformDiagnostics;
use binius_verifier::{
config::StdChallenger,
transcript::{ProverTranscript, VerifierTranscript},
};
use criterion::{BenchmarkId, Criterion, Throughput, criterion_group, criterion_main};

fn bench_hashsign(c: &mut Criterion) {
// Parse parameters from environment variables or use defaults
let num_validators = env::var("HASHSIGN_VALIDATORS")
.ok()
.and_then(|s| s.parse::<usize>().ok())
.unwrap_or(4);

let tree_height = env::var("HASHSIGN_TREE_HEIGHT")
.ok()
.and_then(|s| s.parse::<usize>().ok())
.unwrap_or(13);

let spec = env::var("HASHSIGN_SPEC")
.ok()
.and_then(|s| s.parse::<u8>().ok())
.unwrap_or(2);

// Gather and print comprehensive platform diagnostics
let diagnostics = PlatformDiagnostics::gather();
diagnostics.print();

// Print benchmark-specific parameters
println!("\nHashsign Benchmark Parameters:");
println!(" Validators: {}", num_validators);
println!(" Tree height: {} (2^{} = {} slots)", tree_height, tree_height, 1 << tree_height);
println!(" Winternitz spec: {}", spec);
println!(" Message size: 32 bytes (fixed)");
println!("=========================================\n");

let params = Params {
num_validators,
tree_height,
spec,
};
let instance = Instance {};

// Setup phase - do this once outside the benchmark loop
let mut builder = CircuitBuilder::new();
let example = HashBasedSigExample::build(params.clone(), &mut builder).unwrap();
let circuit = builder.build();
let cs = circuit.constraint_system().clone();
let (verifier, prover) = setup(cs, 1).unwrap();

// Create a witness once for proof size measurement
let mut filler = circuit.new_witness_filler();
example
.populate_witness(instance.clone(), &mut filler)
.unwrap();
circuit.populate_wire_witness(&mut filler).unwrap();
let witness = filler.into_value_vec();

let feature_suffix = diagnostics.get_feature_suffix();
let bench_name =
format!("validators_{}_tree_{}_{}", num_validators, tree_height, feature_suffix);

// Measure witness generation time
{
let mut group = c.benchmark_group("hashsign_witness_generation");
group.throughput(Throughput::Elements(num_validators as u64));
group.warm_up_time(std::time::Duration::from_millis(100));
group.measurement_time(std::time::Duration::from_secs(10));
group.sample_size(10);

group.bench_function(BenchmarkId::from_parameter(&bench_name), |b| {
b.iter(|| {
let mut filler = circuit.new_witness_filler();
example
.populate_witness(instance.clone(), &mut filler)
.unwrap();
circuit.populate_wire_witness(&mut filler).unwrap();
filler.into_value_vec()
})
});

group.finish();
}

// Measure proof generation time
{
let mut group = c.benchmark_group("hashsign_proof_generation");
group.throughput(Throughput::Elements(num_validators as u64));
group.warm_up_time(std::time::Duration::from_millis(100));
group.measurement_time(std::time::Duration::from_secs(10));
group.sample_size(10);

group.bench_function(BenchmarkId::from_parameter(&bench_name), |b| {
b.iter(|| {
let mut prover_transcript = ProverTranscript::new(StdChallenger::default());
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Pseudo-Random Testing rule requires benchmarks to use rand::rng() instead of seeded RNGs. StdChallenger::default() likely uses a seeded RNG rather than thread RNG. Replace with rand::rng() for proper randomness in benchmarks, reserving StdRng::seed_from_u64 for reproducible tests only.

Suggested change
let mut prover_transcript = ProverTranscript::new(StdChallenger::default());
let mut prover_transcript = ProverTranscript::new(StdChallenger::new(rand::thread_rng()));

Spotted by Diamond (based on custom rule: Monbijou Testing Patterns)

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

prover
.prove(witness.clone(), &mut prover_transcript)
.unwrap();
prover_transcript
})
});

group.finish();
}

// Generate a proof for verification benchmarking and size measurement
let mut prover_transcript = ProverTranscript::new(StdChallenger::default());
prover
.prove(witness.clone(), &mut prover_transcript)
.unwrap();
let proof_bytes = prover_transcript.finalize();
let proof_size = proof_bytes.len();

// Measure proof verification time
{
let mut group = c.benchmark_group("hashsign_proof_verification");
group.throughput(Throughput::Elements(num_validators as u64));
group.warm_up_time(std::time::Duration::from_millis(100));
group.measurement_time(std::time::Duration::from_secs(10));
group.sample_size(10);

group.bench_function(BenchmarkId::from_parameter(&bench_name), |b| {
b.iter(|| {
let mut verifier_transcript =
VerifierTranscript::new(StdChallenger::default(), proof_bytes.clone());
verifier
.verify(witness.public(), &mut verifier_transcript)
.unwrap();
verifier_transcript.finalize().unwrap()
})
});

group.finish();
}

// Report proof size
println!(
"\nHashsign proof size for {} validators (tree height {}): {} bytes",
num_validators, tree_height, proof_size
);
}

criterion_group!(hashsign, bench_hashsign);
criterion_main!(hashsign);
Loading