sonobe/solidity-verifiers/src/verifiers/nova_cyclefold.rs
solidity-verifiers/src/verifiers/nova_cyclefold.rsRaw
1#![allow(non_snake_case)]
2#![allow(non_camel_case_types)]
3#![allow(clippy::upper_case_acronyms)]
4
5use ark_bn254::{Bn254, Fq, Fr, G1Affine, G1Projective};
6use ark_groth16::VerifyingKey as ArkG16VerifierKey;
7use ark_poly_commit::kzg10::VerifierKey as ArkKZG10VerifierKey;
8use ark_serialize::{CanonicalDeserialize, CanonicalSerialize};
9use askama::Template;
10
11use folding_schemes::folding::circuits::nonnative::uint::NonNativeUintVar;
12use folding_schemes::folding::nova::decider_eth::VerifierParam as DeciderVerifierParam;
13
14use super::g16::Groth16Verifier;
15use super::kzg::KZG10Verifier;
16use crate::utils::HeaderInclusion;
17use crate::{Groth16VerifierKey, KZG10VerifierKey, ProtocolVerifierKey, PRAGMA_GROTH16_VERIFIER};
18
19pub fn get_decider_template_for_cyclefold_decider(
20 nova_cyclefold_vk: NovaCycleFoldVerifierKey,
21) -> String {
22 HeaderInclusion::<NovaCycleFoldDecider>::builder()
23 .template(nova_cyclefold_vk)
24 .build()
25 .render()
26 .unwrap()
27}
28
29#[derive(Template, Default)]
30#[template(path = "nova_cyclefold_decider.askama.sol", ext = "sol")]
31pub struct NovaCycleFoldDecider {
32 pp_hash: Fr, // public params hash
33 groth16_verifier: Groth16Verifier,
34 kzg10_verifier: KZG10Verifier,
35 // z_len denotes the FCircuit state (z_i) length
36 z_len: usize,
37 public_inputs_len: usize,
38 num_limbs: usize,
39 bits_per_limb: usize,
40}
41
42impl From<NovaCycleFoldVerifierKey> for NovaCycleFoldDecider {
43 fn from(value: NovaCycleFoldVerifierKey) -> Self {
44 let groth16_verifier = Groth16Verifier::from(value.g16_vk);
45 let public_inputs_len = groth16_verifier.gamma_abc_len;
46 let bits_per_limb = NonNativeUintVar::<Fq>::bits_per_limb();
47 Self {
48 pp_hash: value.pp_hash,
49 groth16_verifier,
50 kzg10_verifier: KZG10Verifier::from(value.kzg_vk),
51 z_len: value.z_len,
52 public_inputs_len,
53 num_limbs: (250_f32 / (bits_per_limb as f32)).ceil() as usize,
54 bits_per_limb,
55 }
56 }
57}
58
59#[derive(CanonicalDeserialize, CanonicalSerialize, PartialEq, Debug, Clone)]
60pub struct NovaCycleFoldVerifierKey {
61 pp_hash: Fr,
62 g16_vk: Groth16VerifierKey,
63 kzg_vk: KZG10VerifierKey,
64 z_len: usize,
65}
66
67impl ProtocolVerifierKey for NovaCycleFoldVerifierKey {
68 const PROTOCOL_NAME: &'static str = "NovaCycleFold";
69
70 fn render_as_template(self, pragma: Option<String>) -> Vec<u8> {
71 HeaderInclusion::<NovaCycleFoldDecider>::builder()
72 .pragma_version(pragma.unwrap_or(PRAGMA_GROTH16_VERIFIER.to_string()))
73 .template(self)
74 .build()
75 .render()
76 .unwrap()
77 .into_bytes()
78 }
79}
80
81impl From<(Fr, Groth16VerifierKey, KZG10VerifierKey, usize)> for NovaCycleFoldVerifierKey {
82 fn from(value: (Fr, Groth16VerifierKey, KZG10VerifierKey, usize)) -> Self {
83 Self {
84 pp_hash: value.0,
85 g16_vk: value.1,
86 kzg_vk: value.2,
87 z_len: value.3,
88 }
89 }
90}
91
92// implements From assuming that the 'batchCheck' method from the KZG10 template will not be used
93// in the NovaCycleFoldDecider verifier contract
94impl
95 From<(
96 DeciderVerifierParam<G1Projective, ArkKZG10VerifierKey<Bn254>, ArkG16VerifierKey<Bn254>>,
97 usize,
98 )> for NovaCycleFoldVerifierKey
99{
100 fn from(
101 value: (
102 DeciderVerifierParam<
103 G1Projective,
104 ArkKZG10VerifierKey<Bn254>,
105 ArkG16VerifierKey<Bn254>,
106 >,
107 usize,
108 ),
109 ) -> Self {
110 let decider_vp = value.0;
111 let g16_vk = Groth16VerifierKey::from(decider_vp.snark_vp);
112 // pass `Vec::new()` since batchCheck will not be used
113 let kzg_vk = KZG10VerifierKey::from((decider_vp.cs_vp, Vec::new()));
114 Self {
115 pp_hash: decider_vp.pp_hash,
116 g16_vk,
117 kzg_vk,
118 z_len: value.1,
119 }
120 }
121}
122
123impl NovaCycleFoldVerifierKey {
124 pub fn new(
125 pp_hash: Fr,
126 vkey_g16: ArkG16VerifierKey<Bn254>,
127 vkey_kzg: ArkKZG10VerifierKey<Bn254>,
128 crs_points: Vec<G1Affine>,
129 z_len: usize,
130 ) -> Self {
131 Self {
132 pp_hash,
133 g16_vk: Groth16VerifierKey::from(vkey_g16),
134 kzg_vk: KZG10VerifierKey::from((vkey_kzg, crs_points)),
135 z_len,
136 }
137 }
138}
139
140#[cfg(test)]
141mod tests {
142 use ark_bn254::{constraints::GVar, Bn254, Fr, G1Projective as G1};
143 use ark_ff::PrimeField;
144 use ark_groth16::Groth16;
145 use ark_grumpkin::{constraints::GVar as GVar2, Projective as G2};
146 use ark_r1cs_std::alloc::AllocVar;
147 use ark_r1cs_std::fields::fp::FpVar;
148 use ark_relations::r1cs::{ConstraintSystemRef, SynthesisError};
149 use ark_std::Zero;
150 use askama::Template;
151 use std::marker::PhantomData;
152 use std::time::Instant;
153
154 use folding_schemes::{
155 commitment::{kzg::KZG, pedersen::Pedersen},
156 folding::nova::{
157 decider_eth::{prepare_calldata, Decider as DeciderEth},
158 Nova, PreprocessorParam,
159 },
160 frontend::FCircuit,
161 transcript::poseidon::poseidon_canonical_config,
162 Decider, Error, FoldingScheme,
163 };
164
165 use super::{DeciderVerifierParam, NovaCycleFoldDecider};
166 use crate::verifiers::tests::{setup, DEFAULT_SETUP_LEN};
167 use crate::{
168 evm::{compile_solidity, save_solidity, Evm},
169 utils::{get_function_selector_for_nova_cyclefold_verifier, HeaderInclusion},
170 verifiers::nova_cyclefold::get_decider_template_for_cyclefold_decider,
171 NovaCycleFoldVerifierKey, ProtocolVerifierKey,
172 };
173
174 type NOVA<FC> = Nova<G1, GVar, G2, GVar2, FC, KZG<'static, Bn254>, Pedersen<G2>, false>;
175 type DECIDER<FC> = DeciderEth<
176 G1,
177 GVar,
178 G2,
179 GVar2,
180 FC,
181 KZG<'static, Bn254>,
182 Pedersen<G2>,
183 Groth16<Bn254>,
184 NOVA<FC>,
185 >;
186
187 type FS_PP<FC> = <NOVA<FC> as FoldingScheme<G1, G2, FC>>::ProverParam;
188 type FS_VP<FC> = <NOVA<FC> as FoldingScheme<G1, G2, FC>>::VerifierParam;
189 type DECIDER_PP<FC> = <DECIDER<FC> as Decider<G1, G2, FC, NOVA<FC>>>::ProverParam;
190 type DECIDER_VP<FC> = <DECIDER<FC> as Decider<G1, G2, FC, NOVA<FC>>>::VerifierParam;
191
192 /// Test circuit to be folded
193 #[derive(Clone, Copy, Debug)]
194 pub struct CubicFCircuit<F: PrimeField> {
195 _f: PhantomData<F>,
196 }
197 impl<F: PrimeField> FCircuit<F> for CubicFCircuit<F> {
198 type Params = ();
199 fn new(_params: Self::Params) -> Result<Self, Error> {
200 Ok(Self { _f: PhantomData })
201 }
202 fn state_len(&self) -> usize {
203 1
204 }
205 fn external_inputs_len(&self) -> usize {
206 0
207 }
208 fn step_native(
209 &self,
210 _i: usize,
211 z_i: Vec<F>,
212 _external_inputs: Vec<F>,
213 ) -> Result<Vec<F>, Error> {
214 Ok(vec![z_i[0] * z_i[0] * z_i[0] + z_i[0] + F::from(5_u32)])
215 }
216 fn generate_step_constraints(
217 &self,
218 cs: ConstraintSystemRef<F>,
219 _i: usize,
220 z_i: Vec<FpVar<F>>,
221 _external_inputs: Vec<FpVar<F>>,
222 ) -> Result<Vec<FpVar<F>>, SynthesisError> {
223 let five = FpVar::<F>::new_constant(cs.clone(), F::from(5u32))?;
224 let z_i = z_i[0].clone();
225
226 Ok(vec![&z_i * &z_i * &z_i + &z_i + &five])
227 }
228 }
229
230 /// This is the circuit that we want to fold, it implements the FCircuit trait. The parameter z_i
231 /// denotes the current state which contains 5 elements, and z_{i+1} denotes the next state which
232 /// we get by applying the step.
233 /// In this example we set z_i and z_{i+1} to have five elements, and at each step we do different
234 /// operations on each of them.
235 #[derive(Clone, Copy, Debug)]
236 pub struct MultiInputsFCircuit<F: PrimeField> {
237 _f: PhantomData<F>,
238 }
239 impl<F: PrimeField> FCircuit<F> for MultiInputsFCircuit<F> {
240 type Params = ();
241
242 fn new(_params: Self::Params) -> Result<Self, Error> {
243 Ok(Self { _f: PhantomData })
244 }
245 fn state_len(&self) -> usize {
246 5
247 }
248 fn external_inputs_len(&self) -> usize {
249 0
250 }
251
252 /// computes the next state values in place, assigning z_{i+1} into z_i, and computing the new
253 /// z_{i+1}
254 fn step_native(
255 &self,
256 _i: usize,
257 z_i: Vec<F>,
258 _external_inputs: Vec<F>,
259 ) -> Result<Vec<F>, Error> {
260 let a = z_i[0] + F::from(4_u32);
261 let b = z_i[1] + F::from(40_u32);
262 let c = z_i[2] * F::from(4_u32);
263 let d = z_i[3] * F::from(40_u32);
264 let e = z_i[4] + F::from(100_u32);
265
266 Ok(vec![a, b, c, d, e])
267 }
268
269 /// generates the constraints for the step of F for the given z_i
270 fn generate_step_constraints(
271 &self,
272 cs: ConstraintSystemRef<F>,
273 _i: usize,
274 z_i: Vec<FpVar<F>>,
275 _external_inputs: Vec<FpVar<F>>,
276 ) -> Result<Vec<FpVar<F>>, SynthesisError> {
277 let four = FpVar::<F>::new_constant(cs.clone(), F::from(4u32))?;
278 let forty = FpVar::<F>::new_constant(cs.clone(), F::from(40u32))?;
279 let onehundred = FpVar::<F>::new_constant(cs.clone(), F::from(100u32))?;
280 let a = z_i[0].clone() + four.clone();
281 let b = z_i[1].clone() + forty.clone();
282 let c = z_i[2].clone() * four;
283 let d = z_i[3].clone() * forty;
284 let e = z_i[4].clone() + onehundred;
285
286 Ok(vec![a, b, c, d, e])
287 }
288 }
289
290 #[test]
291 fn nova_cyclefold_vk_serde_roundtrip() {
292 let (pp_hash, _, kzg_vk, _, g16_vk, _) = setup(DEFAULT_SETUP_LEN);
293
294 let decider_vp = DeciderVerifierParam {
295 pp_hash,
296 snark_vp: g16_vk,
297 cs_vp: kzg_vk,
298 };
299 let nova_cyclefold_vk = NovaCycleFoldVerifierKey::from((decider_vp, 1));
300
301 let mut bytes = vec![];
302 nova_cyclefold_vk
303 .serialize_protocol_verifier_key(&mut bytes)
304 .unwrap();
305 let obtained_nova_cyclefold_vk =
306 NovaCycleFoldVerifierKey::deserialize_protocol_verifier_key(bytes.as_slice()).unwrap();
307
308 assert_eq!(nova_cyclefold_vk, obtained_nova_cyclefold_vk)
309 }
310
311 #[test]
312 fn nova_cyclefold_decider_template_renders() {
313 let (pp_hash, _, kzg_vk, _, g16_vk, _) = setup(DEFAULT_SETUP_LEN);
314 let decider_vp = DeciderVerifierParam {
315 pp_hash,
316 snark_vp: g16_vk,
317 cs_vp: kzg_vk,
318 };
319 let nova_cyclefold_vk = NovaCycleFoldVerifierKey::from((decider_vp, 1));
320
321 let decider_solidity_code = HeaderInclusion::<NovaCycleFoldDecider>::builder()
322 .template(nova_cyclefold_vk)
323 .build();
324
325 save_solidity("NovaDecider.sol", &decider_solidity_code.render().unwrap());
326 }
327
328 /// Initializes Nova parameters and DeciderEth parameters. Only for test purposes.
329 #[allow(clippy::type_complexity)]
330 fn init_params<FC: FCircuit<Fr, Params = ()>>(
331 ) -> ((FS_PP<FC>, FS_VP<FC>), (DECIDER_PP<FC>, DECIDER_VP<FC>)) {
332 let mut rng = rand::rngs::OsRng;
333 let poseidon_config = poseidon_canonical_config::<Fr>();
334
335 let f_circuit = FC::new(()).unwrap();
336 let prep_param =
337 PreprocessorParam::<G1, G2, FC, KZG<'static, Bn254>, Pedersen<G2>, false>::new(
338 poseidon_config,
339 f_circuit.clone(),
340 );
341 let nova_params = NOVA::preprocess(&mut rng, &prep_param).unwrap();
342 let nova = NOVA::init(
343 &nova_params,
344 f_circuit.clone(),
345 vec![Fr::zero(); f_circuit.state_len()].clone(),
346 )
347 .unwrap();
348 let decider_params =
349 DECIDER::preprocess(&mut rng, nova_params.clone(), nova.clone()).unwrap();
350
351 (nova_params, decider_params)
352 }
353
354 /// This function allows to define which FCircuit to use for the test, and how many prove_step
355 /// rounds to perform.
356 /// Actions performed by this test:
357 /// - runs the NovaCycleFold folding scheme for the given FCircuit and n_steps times
358 /// - generates a DeciderEth proof, and executes it through the EVM
359 /// - modifies the calldata and checks that it does not pass the EVM check
360 /// - modifies the z_0 and checks that it does not pass the EVM check
361 #[allow(clippy::type_complexity)]
362 fn nova_cyclefold_solidity_verifier_opt<FC: FCircuit<Fr, Params = ()>>(
363 fs_params: (FS_PP<FC>, FS_VP<FC>),
364 decider_params: (DECIDER_PP<FC>, DECIDER_VP<FC>),
365 z_0: Vec<Fr>,
366 n_steps: usize,
367 ) {
368 let (decider_pp, decider_vp) = decider_params;
369
370 let f_circuit = FC::new(()).unwrap();
371
372 let nova_cyclefold_vk =
373 NovaCycleFoldVerifierKey::from((decider_vp.clone(), f_circuit.state_len()));
374
375 let mut rng = rand::rngs::OsRng;
376
377 let mut nova = NOVA::<FC>::init(&fs_params, f_circuit, z_0).unwrap();
378 for _ in 0..n_steps {
379 nova.prove_step(&mut rng, vec![], None).unwrap();
380 }
381
382 let start = Instant::now();
383 let proof = DECIDER::<FC>::prove(rng, decider_pp, nova.clone()).unwrap();
384 println!("generated Decider proof: {:?}", start.elapsed());
385
386 let verified = DECIDER::<FC>::verify(
387 decider_vp,
388 nova.i,
389 nova.z_0.clone(),
390 nova.z_i.clone(),
391 &nova.U_i,
392 &nova.u_i,
393 &proof,
394 )
395 .unwrap();
396 assert!(verified);
397
398 let function_selector =
399 get_function_selector_for_nova_cyclefold_verifier(nova.z_0.len() * 2 + 1);
400
401 let calldata: Vec<u8> = prepare_calldata(
402 function_selector,
403 nova.i,
404 nova.z_0,
405 nova.z_i,
406 &nova.U_i,
407 &nova.u_i,
408 proof,
409 )
410 .unwrap();
411
412 let decider_solidity_code = get_decider_template_for_cyclefold_decider(nova_cyclefold_vk);
413
414 let nova_cyclefold_verifier_bytecode =
415 compile_solidity(decider_solidity_code, "NovaDecider");
416
417 let mut evm = Evm::default();
418 let verifier_address = evm.create(nova_cyclefold_verifier_bytecode);
419
420 let (_, output) = evm.call(verifier_address, calldata.clone());
421 assert_eq!(*output.last().unwrap(), 1);
422
423 // change i to make calldata invalid, placed between bytes 4 - 35
424 let mut invalid_calldata = calldata.clone();
425 invalid_calldata[35] += 1;
426 let (_, output) = evm.call(verifier_address, invalid_calldata.clone());
427 assert_eq!(*output.last().unwrap(), 0);
428
429 // change z_0 to make the EVM check fail, placed between bytes 35 - 67
430 let mut invalid_calldata = calldata.clone();
431 invalid_calldata[67] += 1;
432 let (_, output) = evm.call(verifier_address, invalid_calldata.clone());
433 assert_eq!(*output.last().unwrap(), 0);
434
435 // change z_i to make the EVM check fail, placed between bytes 68 - 100
436 let mut invalid_calldata = calldata.clone();
437 invalid_calldata[99] += 1;
438 let (_, output) = evm.call(verifier_address, invalid_calldata.clone());
439 assert_eq!(*output.last().unwrap(), 0);
440 }
441
442 #[test]
443 fn nova_cyclefold_solidity_verifier() {
444 let (nova_params, decider_params) = init_params::<CubicFCircuit<Fr>>();
445 let z_0 = vec![Fr::from(3_u32)];
446 nova_cyclefold_solidity_verifier_opt::<CubicFCircuit<Fr>>(
447 nova_params.clone(),
448 decider_params.clone(),
449 z_0.clone(),
450 2,
451 );
452 nova_cyclefold_solidity_verifier_opt::<CubicFCircuit<Fr>>(
453 nova_params,
454 decider_params,
455 z_0,
456 3,
457 );
458
459 let (nova_params, decider_params) = init_params::<MultiInputsFCircuit<Fr>>();
460 let z_0 = vec![
461 Fr::from(1_u32),
462 Fr::from(1_u32),
463 Fr::from(1_u32),
464 Fr::from(1_u32),
465 Fr::from(1_u32),
466 ];
467 nova_cyclefold_solidity_verifier_opt::<MultiInputsFCircuit<Fr>>(
468 nova_params.clone(),
469 decider_params.clone(),
470 z_0.clone(),
471 2,
472 );
473 nova_cyclefold_solidity_verifier_opt::<MultiInputsFCircuit<Fr>>(
474 nova_params,
475 decider_params,
476 z_0.clone(),
477 3,
478 );
479 }
480}