arnaucube
arnaucube/sonobePublic Self-hosted repository
sonobe/solidity-verifiers/src/verifiers/mod.rs
solidity-verifiers/src/verifiers/mod.rsRaw
  1//! Solidity templates for the verifier contracts.
  2//! We use askama for templating and define which variables are required for each template.
  3
  4// Pragma statements for verifiers
  5pub(crate) const PRAGMA_GROTH16_VERIFIER: &str = "pragma solidity >=0.7.0 <0.9.0;"; // from snarkjs, avoid changing
  6pub(crate) const PRAGMA_KZG10_VERIFIER: &str = "pragma solidity >=0.8.1 <=0.8.4;";
  7
  8/// Default SDPX License identifier
  9pub(crate) const GPL3_SDPX_IDENTIFIER: &str = "// SPDX-License-Identifier: GPL-3.0";
 10pub(crate) const MIT_SDPX_IDENTIFIER: &str = "// SPDX-License-Identifier: MIT";
 11use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, Read, SerializationError, Write};
 12
 13mod g16;
 14mod kzg;
 15mod nova_cyclefold;
 16
 17pub use g16::Groth16Data;
 18pub use kzg::KzgData;
 19pub use nova_cyclefold::NovaCyclefoldData;
 20
 21pub trait ProtocolData: CanonicalDeserialize + CanonicalSerialize {
 22    const PROTOCOL_NAME: &'static str;
 23
 24    fn serialize_name<W: Write>(&self, writer: &mut W) -> Result<(), SerializationError> {
 25        Self::PROTOCOL_NAME
 26            .to_string()
 27            .serialize_uncompressed(writer)
 28    }
 29
 30    fn serialize_protocol_data<W: Write>(&self, writer: &mut W) -> Result<(), SerializationError> {
 31        self.serialize_name(writer)?;
 32        self.serialize_compressed(writer)
 33    }
 34    fn deserialize_protocol_data<R: Read + Copy>(
 35        mut reader: R,
 36    ) -> Result<Self, SerializationError> {
 37        let name: String = String::deserialize_uncompressed(&mut reader)?;
 38        let data = Self::deserialize_compressed(&mut reader)?;
 39
 40        if name != Self::PROTOCOL_NAME {
 41            return Err(SerializationError::InvalidData);
 42        }
 43
 44        Ok(data)
 45    }
 46
 47    fn render_as_template(self, pragma: Option<String>) -> Vec<u8>;
 48}
 49
 50#[cfg(test)]
 51mod tests {
 52    use crate::evm::{compile_solidity, save_solidity, Evm};
 53    use crate::utils::HeaderInclusion;
 54    use crate::{Groth16Data, KzgData, NovaCyclefoldData, ProtocolData};
 55    use ark_bn254::{Bn254, Fr, G1Projective as G1};
 56    use ark_crypto_primitives::snark::{CircuitSpecificSetupSNARK, SNARK};
 57    use ark_ec::{AffineRepr, CurveGroup};
 58    use ark_ff::{BigInt, BigInteger, PrimeField};
 59    use ark_groth16::Groth16;
 60    use ark_poly_commit::kzg10::VerifierKey;
 61    use ark_r1cs_std::alloc::AllocVar;
 62    use ark_r1cs_std::eq::EqGadget;
 63    use ark_r1cs_std::fields::fp::FpVar;
 64    use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystemRef, SynthesisError};
 65    use ark_std::rand::{RngCore, SeedableRng};
 66    use ark_std::Zero;
 67    use ark_std::{test_rng, UniformRand};
 68    use askama::Template;
 69    use folding_schemes::{
 70        commitment::{
 71            kzg::{ProverKey, KZG},
 72            CommitmentScheme,
 73        },
 74        transcript::{
 75            poseidon::{poseidon_test_config, PoseidonTranscript},
 76            Transcript,
 77        },
 78    };
 79    use itertools::chain;
 80    use std::marker::PhantomData;
 81
 82    use super::g16::Groth16Verifier;
 83    use super::kzg::KZG10Verifier;
 84    use super::nova_cyclefold::NovaCyclefoldDecider;
 85
 86    // Function signatures for proof verification on kzg10 and groth16 contracts
 87    pub const FUNCTION_SIGNATURE_KZG10_CHECK: [u8; 4] = [0x9e, 0x78, 0xcc, 0xf7];
 88    pub const FUNCTION_SIGNATURE_GROTH16_VERIFY_PROOF: [u8; 4] = [0x43, 0x75, 0x3b, 0x4d];
 89    pub const FUNCTION_SIGNATURE_NOVA_CYCLEFOLD_CHECK: [u8; 4] = [0x37, 0x98, 0x0b, 0xb6];
 90
 91    /// Default setup length for testing.
 92    const DEFAULT_SETUP_LEN: usize = 5;
 93
 94    #[derive(Debug, Clone, Copy)]
 95    struct TestAddCircuit<F: PrimeField> {
 96        _f: PhantomData<F>,
 97        pub x: u8,
 98        pub y: u8,
 99        pub z: u8,
100    }
101
102    impl<F: PrimeField> ConstraintSynthesizer<F> for TestAddCircuit<F> {
103        fn generate_constraints(self, cs: ConstraintSystemRef<F>) -> Result<(), SynthesisError> {
104            let x = FpVar::<F>::new_witness(cs.clone(), || Ok(F::from(self.x)))?;
105            let y = FpVar::<F>::new_witness(cs.clone(), || Ok(F::from(self.y)))?;
106            let z = FpVar::<F>::new_input(cs.clone(), || Ok(F::from(self.z)))?;
107            let comp_z = x.clone() + y.clone();
108            comp_z.enforce_equal(&z)?;
109            Ok(())
110        }
111    }
112
113    #[allow(clippy::type_complexity)]
114    fn setup<'a>(
115        n: usize,
116    ) -> (
117        ProverKey<'a, G1>,
118        VerifierKey<Bn254>,
119        ark_groth16::ProvingKey<Bn254>,
120        ark_groth16::VerifyingKey<Bn254>,
121        TestAddCircuit<Fr>,
122    ) {
123        let mut rng = ark_std::rand::rngs::StdRng::seed_from_u64(test_rng().next_u64());
124        let (x, y, z) = (21, 21, 42);
125        let circuit = TestAddCircuit::<Fr> {
126            _f: PhantomData,
127            x,
128            y,
129            z,
130        };
131        let (g16_pk, g16_vk) = Groth16::<Bn254>::setup(circuit, &mut rng).unwrap();
132
133        let (kzg_pk, kzg_vk): (ProverKey<G1>, VerifierKey<Bn254>) =
134            KZG::<Bn254>::setup(&mut rng, n).unwrap();
135        (kzg_pk, kzg_vk, g16_pk, g16_vk, circuit)
136    }
137
138    #[test]
139    fn groth16_data_serde_roundtrip() {
140        let (_, _, _, vk, _) = setup(DEFAULT_SETUP_LEN);
141
142        let g16_data = Groth16Data::from(vk);
143        let mut bytes = vec![];
144        g16_data.serialize_protocol_data(&mut bytes).unwrap();
145        let obtained_g16_data = Groth16Data::deserialize_protocol_data(bytes.as_slice()).unwrap();
146
147        assert_eq!(g16_data, obtained_g16_data)
148    }
149
150    #[test]
151    fn kzg_data_serde_roundtrip() {
152        let (pk, vk, _, _, _) = setup(DEFAULT_SETUP_LEN);
153
154        let kzg_data = KzgData::from((vk, Some(pk.powers_of_g[0..3].to_vec())));
155        let mut bytes = vec![];
156        kzg_data.serialize_protocol_data(&mut bytes).unwrap();
157        let obtained_kzg_data = KzgData::deserialize_protocol_data(bytes.as_slice()).unwrap();
158
159        assert_eq!(kzg_data, obtained_kzg_data)
160    }
161
162    #[test]
163    fn nova_cyclefold_data_serde_roundtrip() {
164        let (kzg_pk, kzg_vk, _, g16_vk, _) = setup(DEFAULT_SETUP_LEN);
165        let g16_data = Groth16Data::from(g16_vk);
166        let kzg_data = KzgData::from((kzg_vk, Some(kzg_pk.powers_of_g[0..3].to_vec())));
167
168        let mut bytes = vec![];
169        let nova_cyclefold_data = NovaCyclefoldData::from((g16_data, kzg_data));
170
171        nova_cyclefold_data
172            .serialize_protocol_data(&mut bytes)
173            .unwrap();
174        let obtained_nova_cyclefold_data =
175            NovaCyclefoldData::deserialize_protocol_data(bytes.as_slice()).unwrap();
176
177        assert_eq!(nova_cyclefold_data, obtained_nova_cyclefold_data)
178    }
179
180    #[test]
181    fn nova_cyclefold_decider_template_renders() {
182        let (kzg_pk, kzg_vk, _, g16_vk, _) = setup(DEFAULT_SETUP_LEN);
183        let g16_data = Groth16Data::from(g16_vk);
184        let kzg_data = KzgData::from((kzg_vk, Some(kzg_pk.powers_of_g[0..3].to_vec())));
185        let nova_cyclefold_data = NovaCyclefoldData::from((g16_data, kzg_data));
186
187        let decider_template = HeaderInclusion::<NovaCyclefoldDecider>::builder()
188            .template(nova_cyclefold_data)
189            .build();
190
191        save_solidity("NovaDecider.sol", &decider_template.render().unwrap());
192    }
193
194    #[test]
195    fn nova_cyclefold_decider_template_compiles() {
196        let (kzg_pk, kzg_vk, _, g16_vk, _) = setup(DEFAULT_SETUP_LEN);
197        let g16_data = Groth16Data::from(g16_vk);
198        let kzg_data = KzgData::from((kzg_vk, Some(kzg_pk.powers_of_g[0..3].to_vec())));
199        let nova_cyclefold_data = NovaCyclefoldData::from((g16_data, kzg_data));
200
201        let decider_template = HeaderInclusion::<NovaCyclefoldDecider>::builder()
202            .template(nova_cyclefold_data)
203            .build();
204        let decider_verifier_bytecode =
205            compile_solidity(decider_template.render().unwrap(), "NovaDecider");
206        let mut evm = Evm::default();
207        _ = evm.create(decider_verifier_bytecode);
208    }
209
210    #[test]
211    fn test_groth16_verifier_accepts_and_rejects_proofs() {
212        let mut rng = ark_std::rand::rngs::StdRng::seed_from_u64(test_rng().next_u64());
213        let (_, _, g16_pk, g16_vk, circuit) = setup(DEFAULT_SETUP_LEN);
214        let g16_data = Groth16Data::from(g16_vk);
215
216        let proof = Groth16::<Bn254>::prove(&g16_pk, circuit, &mut rng).unwrap();
217        let res = Groth16Verifier::from(g16_data).render().unwrap();
218        save_solidity("groth16_verifier.sol", &res);
219        let groth16_verifier_bytecode = compile_solidity(&res, "Verifier");
220        let mut evm = Evm::default();
221        let verifier_address = evm.create(groth16_verifier_bytecode);
222        let (a_x, a_y) = proof.a.xy().unwrap();
223        let (b_x, b_y) = proof.b.xy().unwrap();
224        let (c_x, c_y) = proof.c.xy().unwrap();
225        let mut calldata: Vec<u8> = chain![
226            FUNCTION_SIGNATURE_GROTH16_VERIFY_PROOF,
227            a_x.into_bigint().to_bytes_be(),
228            a_y.into_bigint().to_bytes_be(),
229            b_x.c1.into_bigint().to_bytes_be(),
230            b_x.c0.into_bigint().to_bytes_be(),
231            b_y.c1.into_bigint().to_bytes_be(),
232            b_y.c0.into_bigint().to_bytes_be(),
233            c_x.into_bigint().to_bytes_be(),
234            c_y.into_bigint().to_bytes_be(),
235            BigInt::from(Fr::from(circuit.z)).to_bytes_be(),
236        ]
237        .collect();
238        let (_, output) = evm.call(verifier_address, calldata.clone());
239        assert_eq!(*output.last().unwrap(), 1);
240
241        // change calldata to make it invalid
242        let last_calldata_element = calldata.last_mut().unwrap();
243        *last_calldata_element = 0;
244        let (_, output) = evm.call(verifier_address, calldata);
245        assert_eq!(*output.last().unwrap(), 0);
246    }
247
248    #[test]
249    fn kzg_verifier_template_renders() {
250        let (kzg_pk, kzg_vk, _, _, _) = setup(DEFAULT_SETUP_LEN);
251        let kzg_data = KzgData::from((kzg_vk.clone(), Some(kzg_pk.powers_of_g[0..3].to_vec())));
252
253        let res = HeaderInclusion::<KZG10Verifier>::builder()
254            .template(kzg_data)
255            .build()
256            .render()
257            .unwrap();
258
259        // TODO: Unsure what this is testing. If we want to test correct rendering,
260        // we should first check that it COMPLETELY renders to what we expect.
261        assert!(res.contains(&kzg_vk.g.x.to_string()));
262    }
263
264    #[test]
265    fn kzg_verifier_compiles() {
266        let (kzg_pk, kzg_vk, _, _, _) = setup(DEFAULT_SETUP_LEN);
267        let kzg_data = KzgData::from((kzg_vk.clone(), Some(kzg_pk.powers_of_g[0..3].to_vec())));
268
269        let res = HeaderInclusion::<KZG10Verifier>::builder()
270            .template(kzg_data)
271            .build()
272            .render()
273            .unwrap();
274
275        let kzg_verifier_bytecode = compile_solidity(res, "KZG10");
276        let mut evm = Evm::default();
277        _ = evm.create(kzg_verifier_bytecode);
278    }
279
280    #[test]
281    fn kzg_verifier_accepts_and_rejects_proofs() {
282        let mut rng = ark_std::rand::rngs::StdRng::seed_from_u64(test_rng().next_u64());
283        let poseidon_config = poseidon_test_config::<Fr>();
284        let transcript_p = &mut PoseidonTranscript::<G1>::new(&poseidon_config);
285        let transcript_v = &mut PoseidonTranscript::<G1>::new(&poseidon_config);
286
287        let (kzg_pk, kzg_vk, _, _, _) = setup(DEFAULT_SETUP_LEN);
288        let kzg_data = KzgData::from((kzg_vk.clone(), Some(kzg_pk.powers_of_g[0..3].to_vec())));
289
290        let v: Vec<Fr> = std::iter::repeat_with(|| Fr::rand(&mut rng))
291            .take(DEFAULT_SETUP_LEN)
292            .collect();
293        let cm = KZG::<Bn254>::commit(&kzg_pk, &v, &Fr::zero()).unwrap();
294        let proof = KZG::<Bn254>::prove(&kzg_pk, transcript_p, &cm, &v, &Fr::zero(), None).unwrap();
295        let template = HeaderInclusion::<KZG10Verifier>::builder()
296            .template(kzg_data)
297            .build()
298            .render()
299            .unwrap();
300
301        let kzg_verifier_bytecode = compile_solidity(template, "KZG10");
302        let mut evm = Evm::default();
303        let verifier_address = evm.create(kzg_verifier_bytecode);
304
305        let (cm_affine, proof_affine) = (cm.into_affine(), proof.proof.into_affine());
306        let (x_comm, y_comm) = cm_affine.xy().unwrap();
307        let (x_proof, y_proof) = proof_affine.xy().unwrap();
308        let y = proof.eval.into_bigint().to_bytes_be();
309
310        transcript_v.absorb_point(&cm).unwrap();
311        let x = transcript_v.get_challenge();
312
313        let x = x.into_bigint().to_bytes_be();
314        let mut calldata: Vec<u8> = chain![
315            FUNCTION_SIGNATURE_KZG10_CHECK,
316            x_comm.into_bigint().to_bytes_be(),
317            y_comm.into_bigint().to_bytes_be(),
318            x_proof.into_bigint().to_bytes_be(),
319            y_proof.into_bigint().to_bytes_be(),
320            x.clone(),
321            y,
322        ]
323        .collect();
324
325        let (_, output) = evm.call(verifier_address, calldata.clone());
326        assert_eq!(*output.last().unwrap(), 1);
327
328        // change calldata to make it invalid
329        let last_calldata_element = calldata.last_mut().unwrap();
330        *last_calldata_element = 0;
331        let (_, output) = evm.call(verifier_address, calldata);
332        assert_eq!(*output.last().unwrap(), 0);
333    }
334
335    #[test]
336    fn nova_cyclefold_verifier_compiles() {
337        let (kzg_pk, kzg_vk, _, g16_vk, _) = setup(DEFAULT_SETUP_LEN);
338        let g16_data = Groth16Data::from(g16_vk);
339        let kzg_data = KzgData::from((kzg_vk, Some(kzg_pk.powers_of_g[0..3].to_vec())));
340        let nova_cyclefold_data = NovaCyclefoldData::from((g16_data, kzg_data));
341
342        let decider_template = HeaderInclusion::<NovaCyclefoldDecider>::builder()
343            .template(nova_cyclefold_data)
344            .build()
345            .render()
346            .unwrap();
347
348        let nova_cyclefold_verifier_bytecode = compile_solidity(decider_template, "NovaDecider");
349        let mut evm = Evm::default();
350        _ = evm.create(nova_cyclefold_verifier_bytecode);
351    }
352
353    #[test]
354    fn nova_cyclefold_verifier_accepts_and_rejects_proofs() {
355        let mut rng = ark_std::rand::rngs::StdRng::seed_from_u64(test_rng().next_u64());
356        let (kzg_pk, kzg_vk, g16_pk, g16_vk, circuit) = setup(DEFAULT_SETUP_LEN);
357        let g16_data = Groth16Data::from(g16_vk);
358        let kzg_data = KzgData::from((kzg_vk, Some(kzg_pk.powers_of_g[0..3].to_vec())));
359        let nova_cyclefold_data = NovaCyclefoldData::from((g16_data, kzg_data));
360
361        let g16_proof = Groth16::<Bn254>::prove(&g16_pk, circuit, &mut rng).unwrap();
362
363        let (a_x, a_y) = g16_proof.a.xy().unwrap();
364        let (b_x, b_y) = g16_proof.b.xy().unwrap();
365        let (c_x, c_y) = g16_proof.c.xy().unwrap();
366
367        let poseidon_config = poseidon_test_config::<Fr>();
368        let transcript_p = &mut PoseidonTranscript::<G1>::new(&poseidon_config);
369        let transcript_v = &mut PoseidonTranscript::<G1>::new(&poseidon_config);
370
371        let v: Vec<Fr> = std::iter::repeat_with(|| Fr::rand(&mut rng))
372            .take(DEFAULT_SETUP_LEN)
373            .collect();
374        let cm = KZG::<Bn254>::commit(&kzg_pk, &v, &Fr::zero()).unwrap();
375        let proof = KZG::<Bn254>::prove(&kzg_pk, transcript_p, &cm, &v, &Fr::zero(), None).unwrap();
376
377        let decider_template = HeaderInclusion::<NovaCyclefoldDecider>::builder()
378            .template(nova_cyclefold_data)
379            .build()
380            .render()
381            .unwrap();
382
383        let nova_cyclefold_verifier_bytecode = compile_solidity(decider_template, "NovaDecider");
384
385        let mut evm = Evm::default();
386        let verifier_address = evm.create(nova_cyclefold_verifier_bytecode);
387
388        let (cm_affine, proof_affine) = (cm.into_affine(), proof.proof.into_affine());
389        let (x_comm, y_comm) = cm_affine.xy().unwrap();
390        let (x_proof, y_proof) = proof_affine.xy().unwrap();
391        let y = proof.eval.into_bigint().to_bytes_be();
392
393        transcript_v.absorb_point(&cm).unwrap();
394        let x = transcript_v.get_challenge();
395
396        let x = x.into_bigint().to_bytes_be();
397        let mut calldata: Vec<u8> = chain![
398            FUNCTION_SIGNATURE_NOVA_CYCLEFOLD_CHECK,
399            a_x.into_bigint().to_bytes_be(),
400            a_y.into_bigint().to_bytes_be(),
401            b_x.c1.into_bigint().to_bytes_be(),
402            b_x.c0.into_bigint().to_bytes_be(),
403            b_y.c1.into_bigint().to_bytes_be(),
404            b_y.c0.into_bigint().to_bytes_be(),
405            c_x.into_bigint().to_bytes_be(),
406            c_y.into_bigint().to_bytes_be(),
407            BigInt::from(Fr::from(circuit.z)).to_bytes_be(),
408            x_comm.into_bigint().to_bytes_be(),
409            y_comm.into_bigint().to_bytes_be(),
410            x_proof.into_bigint().to_bytes_be(),
411            y_proof.into_bigint().to_bytes_be(),
412            x.clone(),
413            y,
414        ]
415        .collect();
416
417        let (_, output) = evm.call(verifier_address, calldata.clone());
418        assert_eq!(*output.last().unwrap(), 1);
419
420        // change calldata to make it invalid
421        let last_calldata_element = calldata.last_mut().unwrap();
422        *last_calldata_element = 0;
423        let (_, output) = evm.call(verifier_address, calldata);
424        assert_eq!(*output.last().unwrap(), 0);
425    }
426}