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}