1use std::collections::HashMap;
2
3use ark_bn254::Fr;
4use ark_ff::PrimeField;
5use ark_groth16::{prepare_verifying_key, Groth16};
6use ark_relations::r1cs::SynthesisError;
7use ark_std::UniformRand;
8use color_eyre::Result;
9use once_cell::sync::Lazy;
10use rand::{thread_rng, Rng};
11use semaphore_rs_ark_circom::ethereum::AffineError;
12use semaphore_rs_ark_circom::CircomReduction;
13use semaphore_rs_depth_config::{get_depth_index, get_supported_depth_count};
14use semaphore_rs_depth_macros::array_for_depths;
15use semaphore_rs_poseidon::Poseidon;
16use semaphore_rs_trees::{Branch, InclusionProof};
17use semaphore_rs_witness::Graph;
18use thiserror::Error;
19
20use crate::circuit::zkey;
21use crate::identity::Identity;
22use crate::Field;
23
24pub use semaphore_rs_proof::compression;
25pub use semaphore_rs_proof::Proof;
26
27pub mod authentication;
28
29static WITHESS_GRAPH: [Lazy<Graph>; get_supported_depth_count()] = array_for_depths!(|depth| {
30 Lazy::new(|| {
31 semaphore_rs_witness::init_graph(crate::circuit::graph(depth))
32 .expect("Failed to initialize Graph")
33 })
34});
35
36pub fn warmup_for_verification(tree_depth: usize) {
38 let _zkey = zkey(tree_depth);
39}
40
41fn merkle_proof_to_vec(proof: &InclusionProof<Poseidon>) -> Vec<Field> {
44 proof
45 .0
46 .iter()
47 .map(|x| match x {
48 Branch::Left(value) | Branch::Right(value) => *value,
49 })
50 .collect()
51}
52
53#[must_use]
55pub fn generate_nullifier_hash(identity: &Identity, external_nullifier: Field) -> Field {
56 semaphore_rs_poseidon::poseidon::hash2(external_nullifier, identity.nullifier)
57}
58
59#[derive(Error, Debug)]
60pub enum ProofError {
61 #[error("Error reading circuit key: {0}")]
62 CircuitKeyError(#[from] std::io::Error),
63 #[error("Error producing witness: {0}")]
64 WitnessError(color_eyre::Report),
65 #[error("Error producing proof: {0}")]
66 SynthesisError(#[from] SynthesisError),
67 #[error("Error converting public input: {0}")]
68 ToFieldError(#[from] ruint::ToFieldError),
69 #[error(transparent)]
70 G1AffineError(#[from] AffineError),
71}
72
73pub fn generate_proof(
79 identity: &Identity,
80 merkle_proof: &InclusionProof<Poseidon>,
81 external_nullifier_hash: Field,
82 signal_hash: Field,
83) -> Result<Proof, ProofError> {
84 generate_proof_rng(
85 identity,
86 merkle_proof,
87 external_nullifier_hash,
88 signal_hash,
89 &mut thread_rng(),
90 )
91}
92
93pub fn generate_proof_rng(
99 identity: &Identity,
100 merkle_proof: &InclusionProof<Poseidon>,
101 external_nullifier_hash: Field,
102 signal_hash: Field,
103 rng: &mut impl Rng,
104) -> Result<Proof, ProofError> {
105 generate_proof_rs(
106 identity,
107 merkle_proof,
108 external_nullifier_hash,
109 signal_hash,
110 ark_bn254::Fr::rand(rng),
111 ark_bn254::Fr::rand(rng),
112 )
113}
114
115fn generate_proof_rs(
116 identity: &Identity,
117 merkle_proof: &InclusionProof<Poseidon>,
118 external_nullifier_hash: Field,
119 signal_hash: Field,
120 r: ark_bn254::Fr,
121 s: ark_bn254::Fr,
122) -> Result<Proof, ProofError> {
123 let depth = merkle_proof.0.len();
124 let full_assignment =
125 generate_witness(identity, merkle_proof, external_nullifier_hash, signal_hash);
126
127 let zkey = zkey(depth);
128 let ark_proof = Groth16::<_, CircomReduction>::create_proof_with_reduction_and_matrices(
129 &zkey.0,
130 r,
131 s,
132 &zkey.1,
133 zkey.1.num_instance_variables,
134 zkey.1.num_constraints,
135 full_assignment.as_slice(),
136 )?;
137 let proof = ark_proof.into();
138
139 Ok(proof)
140}
141
142pub fn generate_witness(
143 identity: &Identity,
144 merkle_proof: &InclusionProof<Poseidon>,
145 external_nullifier_hash: Field,
146 signal_hash: Field,
147) -> Vec<Fr> {
148 let depth = merkle_proof.0.len();
149 let inputs = HashMap::from([
150 ("identityNullifier".to_owned(), vec![identity.nullifier]),
151 ("identityTrapdoor".to_owned(), vec![identity.trapdoor]),
152 ("treePathIndices".to_owned(), path_index(merkle_proof)),
153 ("treeSiblings".to_owned(), merkle_proof_to_vec(merkle_proof)),
154 (
155 "externalNullifier".to_owned(),
156 vec![external_nullifier_hash],
157 ),
158 ("signalHash".to_owned(), vec![signal_hash]),
159 ]);
160
161 let graph = &WITHESS_GRAPH
162 [get_depth_index(depth).unwrap_or_else(|| panic!("Depth {depth} not supported"))];
163
164 let witness = semaphore_rs_witness::calculate_witness(inputs, graph).unwrap();
165 witness
166 .into_iter()
167 .map(|x| Fr::from_bigint(x.into()).expect("Couldn't cast U256 to BigInteger"))
168 .collect::<Vec<_>>()
169}
170
171#[must_use]
173pub fn path_index(proof: &InclusionProof<Poseidon>) -> Vec<Field> {
174 proof
175 .0
176 .iter()
177 .map(|branch| match branch {
178 Branch::Left(_) => Field::from(0),
179 Branch::Right(_) => Field::from(1),
180 })
181 .collect()
182}
183
184pub fn verify_proof(
191 root: Field,
192 nullifier_hash: Field,
193 signal_hash: Field,
194 external_nullifier_hash: Field,
195 proof: &Proof,
196 tree_depth: usize,
197) -> Result<bool, ProofError> {
198 let zkey = zkey(tree_depth);
199 let pvk = prepare_verifying_key(&zkey.0.vk);
200
201 let public_inputs = [root, nullifier_hash, signal_hash, external_nullifier_hash]
202 .iter()
203 .map(ark_bn254::Fr::try_from)
204 .collect::<Result<Vec<_>, _>>()?;
205
206 let ark_proof = (*proof).try_into()?;
207 let result = Groth16::<_, CircomReduction>::verify_proof(&pvk, &ark_proof, &public_inputs[..])?;
208 Ok(result)
209}
210
211#[cfg(test)]
212#[allow(dead_code)]
213mod test {
214 use ark_bn254::Config;
215 use ark_ec::bn::Bn;
216 use ark_groth16::Proof as ArkProof;
217 use rand::SeedableRng as _;
218 use rand_chacha::ChaChaRng;
219 use semaphore_rs_depth_macros::test_all_depths;
220 use serde_json::json;
221
222 use super::*;
223 use crate::hash_to_field;
224 use crate::poseidon_tree::LazyPoseidonTree;
225
226 fn arb_proof(seed: u64, depth: usize) -> Proof {
227 let mut rng = ChaChaRng::seed_from_u64(seed);
229
230 let mut seed: [u8; 16] = rng.gen();
232 let id = Identity::from_secret(seed.as_mut(), None);
233
234 let leaf = Field::from(0);
236 let mut tree = LazyPoseidonTree::new(depth, leaf).derived();
237 tree = tree.update(0, &id.commitment());
238
239 let merkle_proof = tree.proof(0);
240
241 let external_nullifier: [u8; 16] = rng.gen();
242 let external_nullifier_hash = hash_to_field(&external_nullifier);
243
244 let signal: [u8; 16] = rng.gen();
245 let signal_hash = hash_to_field(&signal);
246
247 generate_proof_rng(
248 &id,
249 &merkle_proof,
250 external_nullifier_hash,
251 signal_hash,
252 &mut rng,
253 )
254 .unwrap()
255 }
256
257 #[test_all_depths]
258 fn test_proof_cast_roundtrip(depth: usize) {
259 let proof = arb_proof(123, depth);
260 let ark_proof: ArkProof<Bn<Config>> = proof.try_into().unwrap();
261 let result: Proof = ark_proof.into();
262 assert_eq!(proof, result);
263 }
264
265 #[test_all_depths]
266 fn test_proof_serialize(depth: usize) {
267 let proof = arb_proof(456, depth);
268 let json = serde_json::to_value(proof).unwrap();
269 let valid_values = match depth {
270 16 => json!([
271 [
272 "0xe4267974945a50a541e90a399ed9211752216a3e4e1cefab1f0bcd8925ea56e",
273 "0xdd9ada36c50d3f1bf75abe5c5ad7d0a29355b74fc3f604aa108b8886a6ac7f8"
274 ],
275 [
276 [
277 "0x1621577ad2f90fe2e7ec6f675751693515c3b7e91ee228f1db47fe3aba7c0450",
278 "0x2b07bc915b377f8c7126c2d46636632cdbcb426b446a06edf3320939ee4e1911"
279 ],
280 [
281 "0xf40e93e057c7521720448b3d443eac36ff48705312181c41bd78981923be41a",
282 "0x9ce138011687b44a08b979a85b3b122e7335254a02d4fbae7b38b57653c7eb0"
283 ]
284 ],
285 [
286 "0x295b30c0c025a2b176de1220acdb5f95119a8938689d73076f02bb6d01601fbb",
287 "0xc71250468b955584be8769b047f79614df1176a7a64683f14c27889d47e614"
288 ]
289 ]),
290 20 => json!([
291 [
292 "0x2296e314c88daf893769f4ed0cad8a7f584b39db6ebd4bba230591b5d78f48b3",
293 "0x2e5d33bf993b8e4aba7c06ee82ff7dd674857b491c46f53eda4365ecbf3e5fde"
294 ],
295 [
296 [
297 "0x277c239fa1cf9e8a7ca65ef09371bee470aad7936583a0b48e60f6a76f17a97c",
298 "0x2b21c607eff04f704e546451dcd27c5f090639074a54b45e345337e09d0ab3d0"
299 ],
300 [
301 "0x73fde4daa004ecb853159e54b98cdd204e7874008f91581601881c968607451",
302 "0x171ee4d007b9286d91b581f6d38902e5befc3876b96c71bc178b5f5e8dbf1e40"
303 ]
304 ],
305 [
306 "0x25afbb8fef95d8481e9e49b4a94848473794447d032fdde2cd73a0d6318b6c3c",
307 "0x2a24e19699e2d8495357cf9b65fb215cebbcda2817b1627758a330e57db5c4b9"
308 ]
309 ]),
310 30 => json!([
311 [
312 "0x19ded61ab5c58fdb12367526c6bc04b9186d0980c4b6fd48a44093e80f9b4206",
313 "0x2e619a034be10e9aab294f1c77a480378e84782c8519449aef0c8f6952382bda"
314 ],
315 [
316 [
317 "0x2202954c0cdb43dc240d56c3a60d125dbc676f8d97bfeac5987500eb0ff4b9a1",
318 "0x35f5b9d8bfba1341fe9fabef6f46d242e1b22c4006ed3ae3f240f0409b20799"
319 ],
320 [
321 "0x13ef645aeaffda30d38c1df68d79d9682d3d002a388e5672fe9b9c7f3224acd7",
322 "0x10a45a9a99cfaf9aef84ab40c5fdad411e800e24471f24ec76addb74b9e041af"
323 ]
324 ],
325 [
326 "0x1f72d009494e8694cf608c54131e7d565625d59e4637ea77cbf2620c719e8c77",
327 "0x19ee17159b599f6f4b2294d4fb29760d2dc1b58adc0519ce546ad274928f6bc4"
328 ]
329 ]),
330 _ => panic!("unexpected depth: {}", depth),
331 };
332 assert_eq!(json, valid_values);
333 }
334}