Skip to main content

binius_examples/circuits/
hashsign.rs

1// Copyright 2026 The Binius Developers
2//! XMSS multi-signature verification.
3//!
4//! `n` signers with independent trees sign one message at one epoch, and the circuit verifies
5//! every signature. The scheme's parameters are fixed, so the signer count is the only dial.
6
7use anyhow::Result;
8use binius_circuits::hash_based_sig::{
9	MESSAGE_LEN, Message,
10	aggregate::{MultiSigWires, circuit_xmss_multisig},
11	xmss::generate_signature,
12};
13use binius_frontend::{CircuitBuilder, WitnessFiller};
14use clap::Args;
15use rand::{Rng, SeedableRng, rngs::StdRng};
16
17use crate::ExampleCircuit;
18
19/// Fixed seed, so a benchmark run is comparable with the one before it.
20const SEED: u64 = 42;
21
22pub struct HashBasedSigExample {
23	num_signers: usize,
24	wires: MultiSigWires,
25}
26
27#[derive(Args, Debug, Clone)]
28pub struct Params {
29	/// Number of signers in the multi-signature
30	#[arg(short = 'n', long, default_value_t = 3)]
31	pub num_signers: usize,
32}
33
34#[derive(Args, Debug, Clone)]
35pub struct Instance {}
36
37impl ExampleCircuit for HashBasedSigExample {
38	type Params = Params;
39	type Instance = Instance;
40
41	fn build(params: Params, builder: &mut CircuitBuilder) -> Result<Self> {
42		if params.num_signers == 0 {
43			anyhow::bail!("num_signers must be positive");
44		}
45
46		let wires = MultiSigWires::new(builder, params.num_signers);
47		circuit_xmss_multisig(builder, &wires);
48
49		Ok(Self {
50			num_signers: params.num_signers,
51			wires,
52		})
53	}
54
55	fn populate_witness(&self, _instance: Instance, w: &mut WitnessFiller<'_>) -> Result<()> {
56		let mut rng = StdRng::seed_from_u64(SEED);
57
58		let mut message: Message = [0u8; MESSAGE_LEN];
59		rng.fill_bytes(&mut message);
60		let mut epoch_bytes = [0u8; 4];
61		rng.fill_bytes(&mut epoch_bytes);
62		let epoch = u32::from_le_bytes(epoch_bytes);
63
64		// Each signer has its own tree, so each generates its own key alongside its signature.
65		let signatures = (0..self.num_signers)
66			.map(|_| generate_signature(&mut rng, &message, epoch))
67			.collect::<Vec<_>>();
68
69		self.wires.populate(w, &message, epoch, &signatures);
70		Ok(())
71	}
72
73	fn param_summary(params: &Self::Params) -> Option<String> {
74		Some(format!("{}s", params.num_signers))
75	}
76}