1+ use anyhow:: { Result , ensure} ;
2+ use binius_frontend:: {
3+ circuits:: {
4+ semaphore:: { Identity , MerkleTree , SemaphoreProofKeccak } ,
5+ semaphore_ecdsa:: { SemaphoreProofECDSA , circuit:: IdentityECDSA } ,
6+ } ,
7+ compiler:: { CircuitBuilder , circuit:: WitnessFiller } ,
8+ } ;
9+ use clap:: Args ;
10+
11+ use crate :: ExampleCircuit ;
12+
13+ /// Semaphore anonymous group membership proof example
14+ pub struct SemaphoreExample {
15+ circuit_type : CircuitType ,
16+ tree_height : usize ,
17+ message_len_bytes : usize ,
18+ scope_len_bytes : usize ,
19+ }
20+
21+ enum CircuitType {
22+ Keccak ( SemaphoreProofKeccak ) ,
23+ ECDSA ( SemaphoreProofECDSA ) ,
24+ }
25+
26+ #[ derive( Args , Debug , Clone ) ]
27+ pub struct Params {
28+ /// Use ECDSA key derivation (Ethereum-compatible) instead of basic Keccak
29+ #[ arg( long) ]
30+ pub use_ecdsa : bool ,
31+
32+ /// Height of the Merkle tree (determines max group size = 2^height)
33+ #[ arg( long, default_value_t = 2 ) ]
34+ pub tree_height : usize ,
35+
36+ /// Maximum message length in bytes
37+ #[ arg( long, default_value_t = 32 ) ]
38+ pub message_len_bytes : usize ,
39+
40+ /// Maximum scope length in bytes
41+ #[ arg( long, default_value_t = 24 ) ]
42+ pub scope_len_bytes : usize ,
43+ }
44+
45+ #[ derive( Args , Debug , Clone ) ]
46+ pub struct Instance {
47+ /// Number of group members to create
48+ #[ arg( long, default_value_t = 4 ) ]
49+ pub group_size : usize ,
50+
51+ /// Index of the member generating the proof (0-based)
52+ #[ arg( long, default_value_t = 1 ) ]
53+ pub prover_index : usize ,
54+
55+ /// Message to include in the proof
56+ #[ arg( long, default_value = "I vote YES on proposal #42" ) ]
57+ pub message : String ,
58+
59+ /// Scope for this signal (prevents double-signaling within scope)
60+ #[ arg( long, default_value = "dao_vote_2024_q1" ) ]
61+ pub scope : String ,
62+
63+ }
64+
65+ impl ExampleCircuit for SemaphoreExample {
66+ type Params = Params ;
67+ type Instance = Instance ;
68+
69+ fn build ( params : Params , builder : & mut CircuitBuilder ) -> Result < Self > {
70+ ensure ! ( params. tree_height > 0 , "Tree height must be > 0" ) ;
71+ ensure ! ( params. message_len_bytes > 0 , "Message length must be > 0" ) ;
72+ ensure ! ( params. scope_len_bytes > 0 , "Scope length must be > 0" ) ;
73+
74+ let circuit_type = if params. use_ecdsa {
75+ CircuitType :: ECDSA ( SemaphoreProofECDSA :: new (
76+ builder,
77+ params. tree_height ,
78+ params. message_len_bytes ,
79+ params. scope_len_bytes ,
80+ ) )
81+ } else {
82+ CircuitType :: Keccak ( SemaphoreProofKeccak :: new (
83+ builder,
84+ params. tree_height ,
85+ params. message_len_bytes ,
86+ params. scope_len_bytes ,
87+ ) )
88+ } ;
89+
90+ Ok ( Self {
91+ circuit_type,
92+ tree_height : params. tree_height ,
93+ message_len_bytes : params. message_len_bytes ,
94+ scope_len_bytes : params. scope_len_bytes ,
95+ } )
96+ }
97+
98+ fn populate_witness ( & self , instance : Instance , witness : & mut WitnessFiller ) -> Result < ( ) > {
99+
100+ // Validate inputs
101+ ensure ! ( instance. group_size > 0 , "Group size must be > 0" ) ;
102+ ensure ! ( instance. prover_index < instance. group_size, "Prover index must be < group size" ) ;
103+ ensure ! ( instance. group_size <= ( 1 << self . tree_height) , "Group size exceeds tree capacity" ) ;
104+ ensure ! ( instance. message. len( ) <= self . message_len_bytes, "Message too long" ) ;
105+ ensure ! ( instance. scope. len( ) <= self . scope_len_bytes, "Scope too long" ) ;
106+
107+ // Let the standard tracing system handle all output like other examples
108+
109+ match & self . circuit_type {
110+ CircuitType :: Keccak ( circuit) => {
111+ self . populate_keccak_witness ( circuit, & instance, witness)
112+ }
113+ CircuitType :: ECDSA ( circuit) => {
114+ self . populate_ecdsa_witness ( circuit, & instance, witness)
115+ }
116+ } ?;
117+
118+ // Tracing system handles timing automatically
119+
120+ Ok ( ( ) )
121+ }
122+ }
123+
124+ impl SemaphoreExample {
125+ fn populate_keccak_witness (
126+ & self ,
127+ circuit : & SemaphoreProofKeccak ,
128+ instance : & Instance ,
129+ witness : & mut WitnessFiller
130+ ) -> Result < ( ) > {
131+ // Create group members
132+ let mut identities = Vec :: new ( ) ;
133+ for i in 0 ..instance. group_size {
134+ let trapdoor = [ ( ( i + 1 ) as u8 ) ; 32 ] ;
135+ let nullifier = [ ( ( i + 100 ) as u8 ) ; 32 ] ;
136+ identities. push ( Identity :: new ( trapdoor, nullifier) ) ;
137+ }
138+
139+ // Build Merkle tree
140+ let mut tree = MerkleTree :: new ( self . tree_height ) ;
141+ for identity in & identities {
142+ tree. add_leaf ( identity. commitment ( ) ) ;
143+ }
144+
145+ // Get proof for the prover
146+ let prover_identity = & identities[ instance. prover_index ] ;
147+ let merkle_proof = tree. proof ( instance. prover_index ) ;
148+
149+ // Generate nullifier (computed but not displayed like other examples)
150+
151+ // Populate witness
152+ circuit. populate_witness (
153+ witness,
154+ prover_identity,
155+ & merkle_proof,
156+ instance. message . as_bytes ( ) ,
157+ instance. scope . as_bytes ( ) ,
158+ ) ;
159+
160+ Ok ( ( ) )
161+ }
162+
163+ fn populate_ecdsa_witness (
164+ & self ,
165+ circuit : & SemaphoreProofECDSA ,
166+ instance : & Instance ,
167+ witness : & mut WitnessFiller
168+ ) -> Result < ( ) > {
169+ // Create ECDSA identities
170+ let mut identities = Vec :: new ( ) ;
171+ for i in 0 ..instance. group_size {
172+ let secret_scalar = [ ( ( i + 42 ) as u8 ) ; 32 ] ;
173+ identities. push ( IdentityECDSA :: new ( secret_scalar) ) ;
174+ }
175+
176+ // Build Merkle tree
177+ let mut tree = MerkleTree :: new ( self . tree_height ) ;
178+ for identity in & identities {
179+ tree. add_leaf ( identity. commitment ( ) ) ;
180+ }
181+
182+ // Get proof for the prover
183+ let prover_identity = & identities[ instance. prover_index ] ;
184+ let merkle_proof = tree. proof ( instance. prover_index ) ;
185+
186+ // Generate nullifier (computed but not displayed like other examples)
187+
188+ // Populate witness
189+ circuit. populate_witness (
190+ witness,
191+ prover_identity,
192+ & merkle_proof,
193+ instance. message . as_bytes ( ) ,
194+ instance. scope . as_bytes ( ) ,
195+ ) ;
196+
197+ Ok ( ( ) )
198+ }
199+ }
200+
201+ /// Helper function to show comparison between variants (used by CLI)
202+ pub fn show_comparison ( ) -> Result < ( ) > {
203+ println ! ( "\n ╔══════════════════════════════════════════════╗" ) ;
204+ println ! ( "║ Variant Comparison ║" ) ;
205+ println ! ( "╚══════════════════════════════════════════════╝\n " ) ;
206+
207+ // Build both circuits to get constraint counts
208+ let keccak_builder = CircuitBuilder :: new ( ) ;
209+ let _keccak_circuit = SemaphoreProofKeccak :: new ( & keccak_builder, 2 , 32 , 24 ) ;
210+ let keccak_compiled = keccak_builder. build ( ) ;
211+ let keccak_cs = keccak_compiled. constraint_system ( ) ;
212+ let keccak_total = keccak_cs. and_constraints . len ( ) + keccak_cs. mul_constraints . len ( ) ;
213+
214+ let ecdsa_builder = CircuitBuilder :: new ( ) ;
215+ let _ecdsa_circuit = SemaphoreProofECDSA :: new ( & ecdsa_builder, 2 , 32 , 24 ) ;
216+ let ecdsa_compiled = ecdsa_builder. build ( ) ;
217+ let ecdsa_cs = ecdsa_compiled. constraint_system ( ) ;
218+ let ecdsa_total = ecdsa_cs. and_constraints . len ( ) + ecdsa_cs. mul_constraints . len ( ) ;
219+
220+ println ! ( "┌─────────────────────┬────────────┬────────────┬─────────────────────┐" ) ;
221+ println ! ( "│ Version │ Constraints│ AND │ MUL │" ) ;
222+ println ! ( "├─────────────────────┼────────────┼────────────┼─────────────────────┤" ) ;
223+ println ! ( "│ Keccak-only │ {:>10} │ {:>10} │ {:>19} │" ,
224+ keccak_total, keccak_cs. and_constraints. len( ) , keccak_cs. mul_constraints. len( ) ) ;
225+ println ! ( "│ ECDSA + Keccak │ {:>10} │ {:>10} │ {:>19} │" ,
226+ ecdsa_total, ecdsa_cs. and_constraints. len( ) , ecdsa_cs. mul_constraints. len( ) ) ;
227+ println ! ( "│ Future Poseidon │ ~5000 │ ~4999 │ 1 │" ) ;
228+ println ! ( "└─────────────────────┴────────────┴────────────┴─────────────────────┘" ) ;
229+
230+ println ! ( "\n Use Cases:" ) ;
231+ println ! ( "• Keccak-only: Efficient anonymous voting, private group membership" ) ;
232+ println ! ( "• ECDSA+Keccak: Ethereum wallet integration, existing key infrastructure" ) ;
233+ println ! ( "• Poseidon (future): On-chain verification, minimal proof sizes" ) ;
234+
235+ println ! ( "\n Key Features:" ) ;
236+ println ! ( "✓ Anonymous group membership proofs" ) ;
237+ println ! ( "✓ Nullifiers prevent double-signaling within scope" ) ;
238+ println ! ( "✓ Different scopes allow separate voting contexts" ) ;
239+ println ! ( "✓ Zero-knowledge: proves membership without revealing identity" ) ;
240+
241+ Ok ( ( ) )
242+ }
0 commit comments