semaphore_rs/protocol/
mod.rs

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
36/// Preloads the ZKEY in memory to skip the lazy loading at first verification
37pub fn warmup_for_verification(tree_depth: usize) {
38    let _zkey = zkey(tree_depth);
39}
40
41/// Helper to merkle proof into a bigint vector
42/// TODO: we should create a From trait for this
43fn 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/// Generates the nullifier hash
54#[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
73/// Generates a semaphore proof
74///
75/// # Errors
76///
77/// Returns a [`ProofError`] if proving fails.
78pub 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
93/// Generates a semaphore proof from entropy
94///
95/// # Errors
96///
97/// Returns a [`ProofError`] if proving fails.
98pub 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/// Compute path index
172#[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
184/// Verifies a given semaphore proof
185///
186/// # Errors
187///
188/// Returns a [`ProofError`] if verifying fails. Verification failure does not
189/// necessarily mean the proof is incorrect.
190pub 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        // Deterministic randomness for testing
228        let mut rng = ChaChaRng::seed_from_u64(seed);
229
230        // generate identity
231        let mut seed: [u8; 16] = rng.gen();
232        let id = Identity::from_secret(seed.as_mut(), None);
233
234        // generate merkle tree
235        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}