Skip to content

Commit 12c7b2d

Browse files
committed
[frontend] Add Blake2s circuit (#757)
1 parent 68444ab commit 12c7b2d

File tree

12 files changed

+2450
-1
lines changed

12 files changed

+2450
-1
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ jobs:
144144
strategy:
145145
fail-fast: false
146146
matrix:
147-
example: [ethsign, zklogin, sha256, sha512, keccak, hash_based_sig]
147+
example: [ethsign, zklogin, sha256, sha512, keccak, blake2s, hash_based_sig]
148148
steps:
149149
- name: Checkout Repository
150150
uses: actions/checkout@v4

crates/examples/Cargo.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ ethsign = "0.9.0"
2121
tiny-keccak = "2.0.2"
2222
rand.workspace = true
2323
base64.workspace = true
24+
blake2.workspace = true
2425
jwt-simple.workspace = true
2526
sha2.workspace = true
2627

@@ -35,6 +36,10 @@ harness = false
3536
name = "ethsign"
3637
harness = false
3738

39+
[[bench]]
40+
name = "blake2s"
41+
harness = false
42+
3843
[features]
3944
default = ["rayon"]
4045
perfetto = ["tracing-profile/perfetto"]

crates/examples/benches/blake2s.rs

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
//! Blake2s hash benchmark
2+
3+
use std::env;
4+
5+
use binius_examples::{
6+
ExampleCircuit,
7+
circuits::blake2s::{Blake2sExample, Instance, Params},
8+
setup,
9+
};
10+
use binius_frontend::compiler::CircuitBuilder;
11+
use binius_utils::platform_diagnostics::PlatformDiagnostics;
12+
use binius_verifier::{
13+
config::StdChallenger,
14+
transcript::{ProverTranscript, VerifierTranscript},
15+
};
16+
use criterion::{BenchmarkId, Criterion, Throughput, criterion_group, criterion_main};
17+
18+
const DEFAULT_MAX_BYTES: usize = 2048 * 64; // 2048 blocks × 64 bytes/block = 131,072 bytes
19+
20+
fn get_feature_suffix(_diagnostics: &PlatformDiagnostics) -> String {
21+
let mut suffix_parts = vec![];
22+
23+
// Add architecture
24+
#[cfg(target_arch = "x86_64")]
25+
{
26+
suffix_parts.push("x86_64");
27+
// Add key features based on compile-time features
28+
#[cfg(target_feature = "gfni")]
29+
suffix_parts.push("gfni");
30+
#[cfg(target_feature = "avx512f")]
31+
suffix_parts.push("avx512");
32+
#[cfg(all(not(target_feature = "avx512f"), target_feature = "avx2"))]
33+
suffix_parts.push("avx2");
34+
}
35+
36+
#[cfg(target_arch = "aarch64")]
37+
{
38+
suffix_parts.push("arm64");
39+
// Check for NEON and AES
40+
#[cfg(all(target_feature = "neon", target_feature = "aes"))]
41+
suffix_parts.push("neon_aes");
42+
#[cfg(all(target_feature = "neon", not(target_feature = "aes")))]
43+
suffix_parts.push("neon");
44+
}
45+
46+
suffix_parts.join("_")
47+
}
48+
49+
/// Benchmark Blake2s hash circuit
50+
fn bench_blake2s_hash(c: &mut Criterion) {
51+
// Get maximum message size from environment or use default
52+
let max_bytes = env::var("BLAKE2S_MAX_BYTES")
53+
.ok()
54+
.and_then(|s| s.parse::<usize>().ok())
55+
.unwrap_or(DEFAULT_MAX_BYTES);
56+
57+
// Gather and print comprehensive platform diagnostics
58+
let diagnostics = PlatformDiagnostics::gather();
59+
diagnostics.print();
60+
61+
// Print benchmark-specific parameters
62+
let blocks = max_bytes.div_ceil(64);
63+
println!("\nBlake2s Benchmark Parameters:");
64+
println!(" Circuit capacity: {} bytes ({} blocks × 64 bytes/block)", max_bytes, blocks);
65+
println!(" Message length: {} bytes (using full capacity)", max_bytes);
66+
println!(" Note: Circuit size is dynamic based on max_bytes parameter");
67+
println!("=======================================\n");
68+
69+
let params = Params { max_bytes };
70+
let instance = Instance {};
71+
72+
// Setup phase - do this once outside the benchmark loop
73+
let mut builder = CircuitBuilder::new();
74+
let example = Blake2sExample::build(params.clone(), &mut builder).unwrap();
75+
let circuit = builder.build();
76+
let cs = circuit.constraint_system().clone();
77+
let (verifier, prover) = setup(cs, 1).unwrap();
78+
79+
// Create a witness once for proof size measurement
80+
let mut filler = circuit.new_witness_filler();
81+
example
82+
.populate_witness(instance.clone(), &mut filler)
83+
.unwrap();
84+
circuit.populate_wire_witness(&mut filler).unwrap();
85+
let witness = filler.into_value_vec();
86+
87+
let feature_suffix = get_feature_suffix(&diagnostics);
88+
89+
// Benchmark 1: Witness generation
90+
{
91+
let mut group = c.benchmark_group("blake2s_witness_generation");
92+
group.throughput(Throughput::Bytes(max_bytes as u64));
93+
94+
let bench_name = format!("bytes_{}_{}", max_bytes, feature_suffix);
95+
group.bench_with_input(BenchmarkId::from_parameter(&bench_name), &max_bytes, |b, _| {
96+
b.iter(|| {
97+
let mut filler = circuit.new_witness_filler();
98+
example
99+
.populate_witness(instance.clone(), &mut filler)
100+
.unwrap();
101+
circuit.populate_wire_witness(&mut filler).unwrap();
102+
filler.into_value_vec()
103+
})
104+
});
105+
106+
group.finish();
107+
}
108+
109+
// Benchmark 2: Proof generation
110+
{
111+
let mut group = c.benchmark_group("blake2s_proof_generation");
112+
group.throughput(Throughput::Bytes(max_bytes as u64));
113+
group.measurement_time(std::time::Duration::from_secs(120)); // 120 seconds measurement time
114+
group.sample_size(100); // Keep 100 samples
115+
116+
let bench_name = format!("bytes_{}_{}", max_bytes, feature_suffix);
117+
group.bench_with_input(BenchmarkId::from_parameter(&bench_name), &max_bytes, |b, _| {
118+
b.iter(|| {
119+
let mut prover_transcript = ProverTranscript::new(StdChallenger::default());
120+
prover
121+
.prove(witness.clone(), &mut prover_transcript)
122+
.unwrap()
123+
})
124+
});
125+
126+
group.finish();
127+
}
128+
129+
// Generate one proof for verification benchmark and size reporting
130+
let mut prover_transcript = ProverTranscript::new(StdChallenger::default());
131+
prover
132+
.prove(witness.clone(), &mut prover_transcript)
133+
.unwrap();
134+
let proof_bytes = prover_transcript.finalize();
135+
136+
// Benchmark 3: Proof verification
137+
{
138+
let mut group = c.benchmark_group("blake2s_proof_verification");
139+
group.throughput(Throughput::Bytes(max_bytes as u64));
140+
141+
let bench_name = format!("bytes_{}_{}", max_bytes, feature_suffix);
142+
group.bench_with_input(BenchmarkId::from_parameter(&bench_name), &max_bytes, |b, _| {
143+
b.iter(|| {
144+
let mut verifier_transcript =
145+
VerifierTranscript::new(StdChallenger::default(), proof_bytes.clone());
146+
verifier
147+
.verify(witness.public(), &mut verifier_transcript)
148+
.expect("Proof verification failed")
149+
})
150+
});
151+
152+
group.finish();
153+
}
154+
155+
// Print proof size
156+
println!("\n\nBlake2s proof size for {} bytes message: {} bytes", max_bytes, proof_bytes.len());
157+
}
158+
159+
criterion_group!(benches, bench_blake2s_hash);
160+
criterion_main!(benches);
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
use anyhow::{Result, ensure};
2+
use binius_examples::{Cli, ExampleCircuit};
3+
use binius_frontend::{
4+
circuits::blake2s::Blake2s,
5+
compiler::{CircuitBuilder, circuit::WitnessFiller},
6+
};
7+
use blake2::{Blake2s256, Digest};
8+
use clap::Args;
9+
use rand::prelude::*;
10+
11+
/// Blake2s circuit example demonstrating the Blake2s hash function implementation
12+
struct Blake2sExample {
13+
blake2s_gadget: Blake2s,
14+
}
15+
16+
/// Circuit parameters that affect structure (compile-time configuration)
17+
#[derive(Args, Debug)]
18+
struct Params {
19+
/// Maximum message length in bytes that the circuit can handle.
20+
#[arg(long, default_value_t = 128)]
21+
max_bytes: usize,
22+
}
23+
24+
/// Instance data for witness population (runtime values)
25+
#[derive(Args, Debug)]
26+
#[group(multiple = false)]
27+
struct Instance {
28+
/// Length of the randomly generated message, in bytes (defaults to half of --max-message-len).
29+
#[arg(long)]
30+
message_len: Option<usize>,
31+
32+
/// UTF-8 string to hash (if not provided, random bytes are generated)
33+
#[arg(long)]
34+
message_string: Option<String>,
35+
}
36+
37+
impl ExampleCircuit for Blake2sExample {
38+
type Params = Params;
39+
type Instance = Instance;
40+
41+
fn build(params: Params, builder: &mut CircuitBuilder) -> Result<Self> {
42+
// Create the Blake2s gadget with witness wires
43+
let blake2s_gadget = Blake2s::new_witness(builder, params.max_bytes);
44+
45+
Ok(Self { blake2s_gadget })
46+
}
47+
48+
fn populate_witness(&self, instance: Instance, w: &mut WitnessFiller) -> Result<()> {
49+
// Determine the message bytes to hash
50+
let message_bytes = if let Some(message_string) = instance.message_string {
51+
// Use provided UTF-8 string
52+
message_string.as_bytes().to_vec()
53+
} else {
54+
// Generate random bytes
55+
let mut rng = StdRng::seed_from_u64(0);
56+
let len = instance
57+
.message_len
58+
.unwrap_or(self.blake2s_gadget.max_bytes / 2);
59+
60+
let mut message_bytes = vec![0u8; len];
61+
rng.fill_bytes(&mut message_bytes);
62+
message_bytes
63+
};
64+
65+
// Validate message length
66+
ensure!(
67+
message_bytes.len() <= self.blake2s_gadget.max_bytes,
68+
"Message length ({}) exceeds maximum ({})",
69+
message_bytes.len(),
70+
self.blake2s_gadget.max_bytes
71+
);
72+
73+
// Compute the expected Blake2s digest using the reference implementation
74+
let mut hasher = Blake2s256::new();
75+
hasher.update(&message_bytes);
76+
let digest = hasher.finalize();
77+
let digest_array: [u8; 32] = digest.into();
78+
79+
// Populate the witness values
80+
self.blake2s_gadget.populate_message(w, &message_bytes);
81+
self.blake2s_gadget.populate_digest(w, &digest_array);
82+
83+
Ok(())
84+
}
85+
}
86+
87+
fn main() -> Result<()> {
88+
let _tracing_guard = tracing_profile::init_tracing()?;
89+
90+
// Create and run the CLI
91+
Cli::<Blake2sExample>::new("blake2s")
92+
.about("Blake2s hash function circuit example")
93+
.long_about(
94+
"Blake2s cryptographic hash function circuit implementation.\n\
95+
\n\
96+
This example demonstrates the Blake2s hash function which produces \
97+
32-byte digests. Blake2s is optimized for 32-bit platforms and is \
98+
faster than Blake2b on such architectures.\n\
99+
\n\
100+
The circuit supports variable-length messages up to the specified \
101+
maximum length. It implements the full Blake2s algorithm as \
102+
specified in RFC 7693, including 10 rounds of the compression \
103+
function.\n\
104+
\n\
105+
Examples:\n\
106+
\n\
107+
Hash a string:\n\
108+
cargo run --release --example blake2s -- --message-string \"Hello, World!\"\n\
109+
\n\
110+
Generate and hash random data (64 bytes):\n\
111+
cargo run --release --example blake2s -- --message-len 64\n\
112+
\n\
113+
Test with maximum message length:\n\
114+
cargo run --release --example blake2s -- --max-message-len 256 --message-len 256",
115+
)
116+
.run()
117+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
blake2s circuit
2+
--
3+
Number of gates: 2937
4+
Number of evaluation instructions: 3068
5+
Number of AND constraints: 3897
6+
Number of MUL constraints: 0
7+
Length of value vec: 8192
8+
Constants: 140
9+
Inout: 0
10+
Witness: 137
11+
Internal: 3761
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
//! Blake2s circuit benchmark wrapper
2+
3+
use anyhow::{Result, ensure};
4+
use binius_frontend::{
5+
circuits::blake2s::Blake2s,
6+
compiler::{CircuitBuilder, circuit::WitnessFiller},
7+
};
8+
use clap::Args;
9+
use rand::{RngCore, SeedableRng, rngs::StdRng};
10+
11+
use crate::ExampleCircuit;
12+
13+
#[derive(Debug, Clone, Args)]
14+
pub struct Params {
15+
#[arg(long, default_value_t = 131072)]
16+
pub max_bytes: usize,
17+
}
18+
19+
#[derive(Debug, Clone, Args)]
20+
pub struct Instance {}
21+
22+
pub struct Blake2sExample {
23+
blake2s_circuit: Blake2s,
24+
max_bytes: usize,
25+
}
26+
27+
impl ExampleCircuit for Blake2sExample {
28+
type Params = Params;
29+
type Instance = Instance;
30+
31+
fn build(params: Params, builder: &mut CircuitBuilder) -> Result<Self> {
32+
ensure!(params.max_bytes > 0, "max_bytes must be positive");
33+
34+
// Create the Blake2s circuit with the specified max_bytes
35+
let blake2s_circuit = Blake2s::new_witness(builder, params.max_bytes);
36+
37+
Ok(Self {
38+
blake2s_circuit,
39+
max_bytes: params.max_bytes,
40+
})
41+
}
42+
43+
fn populate_witness(&self, _instance: Instance, w: &mut WitnessFiller) -> Result<()> {
44+
// Generate deterministic random message using the full capacity
45+
let mut rng = StdRng::seed_from_u64(42);
46+
let mut message_bytes = vec![0u8; self.max_bytes];
47+
rng.fill_bytes(&mut message_bytes);
48+
49+
// Compute the expected digest using blake2 crate
50+
use blake2::{Blake2s256, Digest};
51+
let mut hasher = Blake2s256::new();
52+
hasher.update(&message_bytes);
53+
let digest = hasher.finalize();
54+
let digest_bytes: [u8; 32] = digest.into();
55+
56+
// Populate the witness
57+
self.blake2s_circuit.populate_message(w, &message_bytes);
58+
self.blake2s_circuit.populate_digest(w, &digest_bytes);
59+
60+
Ok(())
61+
}
62+
}

crates/examples/src/circuits/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
pub mod blake2s;
12
pub mod ethsign;
23
pub mod keccak;
34
pub mod sha256;

0 commit comments

Comments
 (0)