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::{KZGProver, KZGSetup, ProverKey},
72 CommitmentProver,
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 KZGSetup::<Bn254>::setup(&mut rng, n);
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 = KZGProver::<G1>::commit(&kzg_pk, &v, &Fr::zero()).unwrap();
294 let (eval, proof) =
295 KZGProver::<G1>::prove(&kzg_pk, transcript_p, &cm, &v, &Fr::zero(), None).unwrap();
296 let template = HeaderInclusion::<KZG10Verifier>::builder()
297 .template(kzg_data)
298 .build()
299 .render()
300 .unwrap();
301
302 let kzg_verifier_bytecode = compile_solidity(template, "KZG10");
303 let mut evm = Evm::default();
304 let verifier_address = evm.create(kzg_verifier_bytecode);
305
306 let (cm_affine, proof_affine) = (cm.into_affine(), proof.into_affine());
307 let (x_comm, y_comm) = cm_affine.xy().unwrap();
308 let (x_proof, y_proof) = proof_affine.xy().unwrap();
309 let y = eval.into_bigint().to_bytes_be();
310
311 transcript_v.absorb_point(&cm).unwrap();
312 let x = transcript_v.get_challenge();
313
314 let x = x.into_bigint().to_bytes_be();
315 let mut calldata: Vec<u8> = chain![
316 FUNCTION_SIGNATURE_KZG10_CHECK,
317 x_comm.into_bigint().to_bytes_be(),
318 y_comm.into_bigint().to_bytes_be(),
319 x_proof.into_bigint().to_bytes_be(),
320 y_proof.into_bigint().to_bytes_be(),
321 x.clone(),
322 y,
323 ]
324 .collect();
325
326 let (_, output) = evm.call(verifier_address, calldata.clone());
327 assert_eq!(*output.last().unwrap(), 1);
328
329 // change calldata to make it invalid
330 let last_calldata_element = calldata.last_mut().unwrap();
331 *last_calldata_element = 0;
332 let (_, output) = evm.call(verifier_address, calldata);
333 assert_eq!(*output.last().unwrap(), 0);
334 }
335
336 #[test]
337 fn nova_cyclefold_verifier_compiles() {
338 let (kzg_pk, kzg_vk, _, g16_vk, _) = setup(DEFAULT_SETUP_LEN);
339 let g16_data = Groth16Data::from(g16_vk);
340 let kzg_data = KzgData::from((kzg_vk, Some(kzg_pk.powers_of_g[0..3].to_vec())));
341 let nova_cyclefold_data = NovaCyclefoldData::from((g16_data, kzg_data));
342
343 let decider_template = HeaderInclusion::<NovaCyclefoldDecider>::builder()
344 .template(nova_cyclefold_data)
345 .build()
346 .render()
347 .unwrap();
348
349 let nova_cyclefold_verifier_bytecode = compile_solidity(decider_template, "NovaDecider");
350 let mut evm = Evm::default();
351 _ = evm.create(nova_cyclefold_verifier_bytecode);
352 }
353
354 #[test]
355 fn nova_cyclefold_verifier_accepts_and_rejects_proofs() {
356 let mut rng = ark_std::rand::rngs::StdRng::seed_from_u64(test_rng().next_u64());
357 let (kzg_pk, kzg_vk, g16_pk, g16_vk, circuit) = setup(DEFAULT_SETUP_LEN);
358 let g16_data = Groth16Data::from(g16_vk);
359 let kzg_data = KzgData::from((kzg_vk, Some(kzg_pk.powers_of_g[0..3].to_vec())));
360 let nova_cyclefold_data = NovaCyclefoldData::from((g16_data, kzg_data));
361
362 let g16_proof = Groth16::<Bn254>::prove(&g16_pk, circuit, &mut rng).unwrap();
363
364 let (a_x, a_y) = g16_proof.a.xy().unwrap();
365 let (b_x, b_y) = g16_proof.b.xy().unwrap();
366 let (c_x, c_y) = g16_proof.c.xy().unwrap();
367
368 let poseidon_config = poseidon_test_config::<Fr>();
369 let transcript_p = &mut PoseidonTranscript::<G1>::new(&poseidon_config);
370 let transcript_v = &mut PoseidonTranscript::<G1>::new(&poseidon_config);
371
372 let v: Vec<Fr> = std::iter::repeat_with(|| Fr::rand(&mut rng))
373 .take(DEFAULT_SETUP_LEN)
374 .collect();
375 let cm = KZGProver::<G1>::commit(&kzg_pk, &v, &Fr::zero()).unwrap();
376 let (eval, proof) =
377 KZGProver::<G1>::prove(&kzg_pk, transcript_p, &cm, &v, &Fr::zero(), None).unwrap();
378
379 let decider_template = HeaderInclusion::<NovaCyclefoldDecider>::builder()
380 .template(nova_cyclefold_data)
381 .build()
382 .render()
383 .unwrap();
384
385 let nova_cyclefold_verifier_bytecode = compile_solidity(decider_template, "NovaDecider");
386
387 let mut evm = Evm::default();
388 let verifier_address = evm.create(nova_cyclefold_verifier_bytecode);
389
390 let (cm_affine, proof_affine) = (cm.into_affine(), proof.into_affine());
391 let (x_comm, y_comm) = cm_affine.xy().unwrap();
392 let (x_proof, y_proof) = proof_affine.xy().unwrap();
393 let y = eval.into_bigint().to_bytes_be();
394
395 transcript_v.absorb_point(&cm).unwrap();
396 let x = transcript_v.get_challenge();
397
398 let x = x.into_bigint().to_bytes_be();
399 let mut calldata: Vec<u8> = chain![
400 FUNCTION_SIGNATURE_NOVA_CYCLEFOLD_CHECK,
401 a_x.into_bigint().to_bytes_be(),
402 a_y.into_bigint().to_bytes_be(),
403 b_x.c1.into_bigint().to_bytes_be(),
404 b_x.c0.into_bigint().to_bytes_be(),
405 b_y.c1.into_bigint().to_bytes_be(),
406 b_y.c0.into_bigint().to_bytes_be(),
407 c_x.into_bigint().to_bytes_be(),
408 c_y.into_bigint().to_bytes_be(),
409 BigInt::from(Fr::from(circuit.z)).to_bytes_be(),
410 x_comm.into_bigint().to_bytes_be(),
411 y_comm.into_bigint().to_bytes_be(),
412 x_proof.into_bigint().to_bytes_be(),
413 y_proof.into_bigint().to_bytes_be(),
414 x.clone(),
415 y,
416 ]
417 .collect();
418
419 let (_, output) = evm.call(verifier_address, calldata.clone());
420 assert_eq!(*output.last().unwrap(), 1);
421
422 // change calldata to make it invalid
423 let last_calldata_element = calldata.last_mut().unwrap();
424 *last_calldata_element = 0;
425 let (_, output) = evm.call(verifier_address, calldata);
426 assert_eq!(*output.last().unwrap(), 0);
427 }
428}