Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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
188 changes: 188 additions & 0 deletions crates/examples/benches/hashsign.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
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};

/// Generate a feature suffix for benchmark names based on platform diagnostics
fn get_feature_suffix(_diagnostics: &PlatformDiagnostics) -> String {
let mut suffix_parts = Vec::new();
Comment thread
GraDKh marked this conversation as resolved.
Outdated

// 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_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 = get_feature_suffix(&diagnostics);
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_with_input(BenchmarkId::from_parameter(&bench_name), &bench_name, |b, _| {
Comment thread
GraDKh marked this conversation as resolved.
Outdated
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_with_input(BenchmarkId::from_parameter(&bench_name), &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_with_input(BenchmarkId::from_parameter(&bench_name), &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);
10 changes: 10 additions & 0 deletions crates/examples/examples/hashsign.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
use anyhow::Result;
use binius_examples::{Cli, circuits::hashsign::HashBasedSigExample};

fn main() -> Result<()> {
let _tracing_guard = tracing_profile::init_tracing()?;

Cli::<HashBasedSigExample>::new("hashsign")
.about("Hash-based multi-signature (XMSS) verification example")
.run()
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
hash_based_sig circuit
hashsign circuit
--
Number of gates: 3986883
Number of evaluation instructions: 4076982
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ use std::array;

use anyhow::Result;
use binius_core::Word;
use binius_examples::{Cli, ExampleCircuit};
use binius_frontend::{
circuits::hash_based_sig::{
winternitz_ots::WinternitzSpec,
Expand All @@ -16,8 +15,10 @@ use binius_frontend::{
use clap::Args;
use rand::{RngCore, SeedableRng, rngs::StdRng};

use crate::ExampleCircuit;

/// Hash-based multi-signature verification example circuit
struct HashBasedSigExample {
pub struct HashBasedSigExample {
spec: WinternitzSpec,
tree_height: usize,
num_validators: usize,
Expand All @@ -29,38 +30,48 @@ struct HashBasedSigExample {
hashers: XmssMultisigHashers,
}

#[derive(Args, Debug)]
struct Params {
#[derive(Args, Debug, Clone)]
pub struct Params {
/// Number of validators in the multi-signature
#[arg(short = 'n', long, default_value_t = 3)]
num_validators: usize,
pub num_validators: usize,

/// Height of the Merkle tree (2^height slots)
#[arg(short = 't', long, default_value_t = 3)]
tree_height: usize,
pub tree_height: usize,

/// Winternitz spec: 1 or 2
#[arg(short = 's', long, default_value_t = 1)]
spec: u8,
pub spec: u8,
}

#[derive(Args, Debug)]
struct Instance {}
#[derive(Args, Debug, Clone)]
pub struct Instance {}

impl ExampleCircuit for HashBasedSigExample {
type Params = Params;
type Instance = Instance;

fn build(params: Params, builder: &mut CircuitBuilder) -> Result<Self> {
println!("Building HashBasedSigExample with parameters:");
println!(" num_validators: {}", params.num_validators);
println!(
" tree_height: {} (2^{} = {} slots)",
params.tree_height,
params.tree_height,
1 << params.tree_height
);
println!(" spec: {}", params.spec);

let spec = match params.spec {
1 => WinternitzSpec::spec_1(),
2 => WinternitzSpec::spec_2(),
_ => anyhow::bail!("Invalid spec: must be 1 or 2"),
};

let tree_height = params.tree_height;
if tree_height >= 10 {
anyhow::bail!("tree_height {} exceeds the maximum supported height of 10", tree_height);
if tree_height > 31 {
anyhow::bail!("tree_height {} exceeds the maximum supported height of 31", tree_height);
}
let num_validators = params.num_validators;

Expand Down Expand Up @@ -113,15 +124,16 @@ impl ExampleCircuit for HashBasedSigExample {
}

fn populate_witness(&self, _instance: Instance, w: &mut WitnessFiller) -> Result<()> {
let mut rng = StdRng::seed_from_u64(0);
let mut rng = StdRng::seed_from_u64(42); // Fixed seed for benchmarking consistency

let mut param_bytes = vec![0u8; self.spec.domain_param_len];
rng.fill_bytes(&mut param_bytes);

// Fixed 32-byte message
let mut message_bytes = [0u8; 32];
rng.fill_bytes(&mut message_bytes);

// Safe because tree_height is validated to be < 10 in build()
// Safe because tree_height is validated to be <= 31 in build()
let epoch = rng.next_u32() % (1u32 << self.tree_height);

// Pack param_bytes (pad to match wire count)
Expand Down Expand Up @@ -194,11 +206,3 @@ impl ExampleCircuit for HashBasedSigExample {
Ok(())
}
}

fn main() -> Result<()> {
let _tracing_guard = tracing_profile::init_tracing()?;

Cli::<HashBasedSigExample>::new("hash_based_sig")
.about("Hash-based multi-signature (XMSS) verification example")
.run()
}
1 change: 1 addition & 0 deletions crates/examples/src/circuits/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
pub mod blake2s;
pub mod ethsign;
pub mod hashsign;
pub mod keccak;
pub mod sha256;
pub mod sha512;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,8 @@ impl ValidatorSignatureData {
tree_height: usize,
) -> Self {
assert!(
tree_height <= 10,
"Tree height {} exceeds maximum supported height of 10",
tree_height <= 31,
"Tree height {} exceeds maximum supported height of 31",
tree_height,
);

Expand Down
Loading