circom_groth16_verifier/
lib.rs

1#![no_std]
2
3//! Groth16 verifier contract for Circom proofs on Soroban using the native
4//! BN254 precompile.
5
6// Use Soroban's allocator for heap allocations
7extern crate alloc;
8
9pub use contract_types::{Groth16Error, Groth16Proof, VerificationKeyBytes};
10use soroban_sdk::{
11    Env, Vec, contract, contractimpl, contracttype,
12    crypto::bn254::{Bn254G1Affine as G1Affine, Bn254G2Affine as G2Affine, Fr},
13    vec,
14};
15
16/// Groth16 verification key for BN254 curve.
17#[derive(Clone)]
18pub struct VerificationKey {
19    pub alpha: G1Affine,
20    pub beta: G2Affine,
21    pub gamma: G2Affine,
22    pub delta: G2Affine,
23    pub ic: Vec<G1Affine>,
24}
25
26fn verification_key_from_bytes(env: &Env, vk_bytes: &VerificationKeyBytes) -> VerificationKey {
27    let mut ic_vec: Vec<G1Affine> = Vec::new(env);
28    for bytes in vk_bytes.ic.iter() {
29        ic_vec.push_back(G1Affine::from_bytes(bytes));
30    }
31
32    VerificationKey {
33        alpha: G1Affine::from_bytes(vk_bytes.alpha.clone()),
34        beta: G2Affine::from_bytes(vk_bytes.beta.clone()),
35        gamma: G2Affine::from_bytes(vk_bytes.gamma.clone()),
36        delta: G2Affine::from_bytes(vk_bytes.delta.clone()),
37        ic: ic_vec,
38    }
39}
40
41#[contracttype]
42#[derive(Clone)]
43enum DataKey {
44    VerificationKey,
45}
46
47/// Groth16 verifier for BN254/Circom proofs.
48#[contract]
49pub struct CircomGroth16Verifier;
50
51#[contractimpl]
52impl CircomGroth16Verifier {
53    /// Constructor: initialize the contract with a verification key.
54    pub fn __constructor(env: Env, vk: VerificationKeyBytes) -> Result<(), Groth16Error> {
55        let storage = env.storage().persistent();
56        storage.set(&DataKey::VerificationKey, &vk);
57        Ok(())
58    }
59
60    /// Verify a Groth16 proof using the stored verification key.
61    pub fn verify(
62        env: Env,
63        proof: Groth16Proof,
64        public_inputs: Vec<Fr>,
65    ) -> Result<bool, Groth16Error> {
66        let vk_bytes: VerificationKeyBytes = env
67            .storage()
68            .persistent()
69            .get(&DataKey::VerificationKey)
70            .ok_or(Groth16Error::NotInitialized)?;
71        let vk = verification_key_from_bytes(&env, &vk_bytes);
72        Self::verify_with_vk(&env, &vk, proof, public_inputs)
73    }
74
75    fn verify_with_vk(
76        env: &Env,
77        vk: &VerificationKey,
78        proof: Groth16Proof,
79        pub_inputs: Vec<Fr>,
80    ) -> Result<bool, Groth16Error> {
81        let bn = env.crypto().bn254();
82
83        if pub_inputs.len() + 1 != vk.ic.len() {
84            return Err(Groth16Error::MalformedPublicInputs);
85        }
86
87        let mut vk_x = vk.ic.get(0).ok_or(Groth16Error::MalformedPublicInputs)?;
88
89        for i in 0..pub_inputs.len() {
90            let s = pub_inputs.get(i).unwrap();
91            let v = vk.ic.get(i + 1).unwrap();
92            let prod = bn.g1_mul(&v, &s);
93            vk_x = bn.g1_add(&vk_x, &prod);
94        }
95
96        // Compute the pairing check:
97        // e(-A, B) * e(alpha, beta) * e(vk_x, gamma) * e(C, delta) == 1
98        let neg_a = -proof.a;
99
100        let g1_points = vec![env, neg_a, vk.alpha.clone(), vk_x, proof.c];
101        let g2_points = vec![
102            env,
103            proof.b,
104            vk.beta.clone(),
105            vk.gamma.clone(),
106            vk.delta.clone(),
107        ];
108        if bn.pairing_check(g1_points, g2_points) {
109            Ok(true)
110        } else {
111            Err(Groth16Error::InvalidProof)
112        }
113    }
114}
115
116#[cfg(test)]
117mod test;