sonobe/folding-schemes/src/folding/nova/mod.rs
folding-schemes/src/folding/nova/mod.rsRaw
1/// Implements the scheme described in [Nova](https://eprint.iacr.org/2021/370.pdf) and
2/// [CycleFold](https://eprint.iacr.org/2023/1192.pdf).
3use ark_crypto_primitives::sponge::{
4 poseidon::{PoseidonConfig, PoseidonSponge},
5 Absorb, CryptographicSponge,
6};
7use ark_ec::{CurveGroup, Group};
8use ark_ff::{BigInteger, PrimeField};
9use ark_r1cs_std::{groups::GroupOpsBounds, prelude::CurveVar, ToConstraintFieldGadget};
10use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystem};
11use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, Valid};
12use ark_std::fmt::Debug;
13use ark_std::rand::RngCore;
14use ark_std::{One, UniformRand, Zero};
15use core::marker::PhantomData;
16use decider_eth_circuit::WitnessVar;
17
18use crate::folding::circuits::cyclefold::{
19 fold_cyclefold_circuit, CycleFoldCircuit, CycleFoldCommittedInstance, CycleFoldConfig,
20 CycleFoldWitness,
21};
22use crate::folding::{
23 circuits::{CF1, CF2},
24 traits::Dummy,
25};
26use crate::frontend::FCircuit;
27use crate::transcript::{poseidon::poseidon_canonical_config, AbsorbNonNative, Transcript};
28use crate::utils::vec::is_zero_vec;
29use crate::Error;
30use crate::FoldingScheme;
31use crate::{
32 arith::r1cs::{extract_r1cs, extract_w_x, R1CS},
33 constants::NOVA_N_BITS_RO,
34 utils::{get_cm_coordinates, pp_hash},
35};
36use crate::{arith::Arith, commitment::CommitmentScheme};
37
38pub mod circuits;
39pub mod nifs;
40pub mod ova;
41pub mod serialize;
42pub mod traits;
43pub mod zk;
44
45use circuits::{AugmentedFCircuit, ChallengeGadget, CommittedInstanceVar};
46use nifs::NIFS;
47use traits::NIFSTrait;
48
49// offchain decider
50pub mod decider;
51pub mod decider_circuits;
52// onchain decider
53pub mod decider_eth;
54pub mod decider_eth_circuit;
55
56use super::traits::{CommittedInstanceOps, WitnessOps};
57
58/// Configuration for Nova's CycleFold circuit
59pub struct NovaCycleFoldConfig<C: CurveGroup> {
60 _c: PhantomData<C>,
61}
62
63impl<C: CurveGroup> CycleFoldConfig for NovaCycleFoldConfig<C> {
64 const RANDOMNESS_BIT_LENGTH: usize = NOVA_N_BITS_RO;
65 // Number of points to be folded in the CycleFold circuit, in Nova's case, this is a fixed
66 // amount:
67 // 2 points to be folded.
68 const N_INPUT_POINTS: usize = 2;
69 type C = C;
70 type F = C::BaseField;
71}
72
73/// CycleFold circuit for computing random linear combinations of group elements
74/// in Nova instances.
75pub type NovaCycleFoldCircuit<C, GC> = CycleFoldCircuit<NovaCycleFoldConfig<C>, GC>;
76
77#[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)]
78pub struct CommittedInstance<C: CurveGroup> {
79 pub cmE: C,
80 pub u: C::ScalarField,
81 pub cmW: C,
82 pub x: Vec<C::ScalarField>,
83}
84
85impl<C: CurveGroup> Dummy<usize> for CommittedInstance<C> {
86 fn dummy(io_len: usize) -> Self {
87 Self {
88 cmE: C::zero(),
89 u: CF1::<C>::zero(),
90 cmW: C::zero(),
91 x: vec![CF1::<C>::zero(); io_len],
92 }
93 }
94}
95
96impl<C: CurveGroup> Dummy<&R1CS<CF1<C>>> for CommittedInstance<C> {
97 fn dummy(r1cs: &R1CS<CF1<C>>) -> Self {
98 Self::dummy(r1cs.l)
99 }
100}
101
102impl<C: CurveGroup> Absorb for CommittedInstance<C>
103where
104 C::ScalarField: Absorb,
105{
106 fn to_sponge_bytes(&self, dest: &mut Vec<u8>) {
107 C::ScalarField::batch_to_sponge_bytes(&self.to_sponge_field_elements_as_vec(), dest);
108 }
109
110 fn to_sponge_field_elements<F: PrimeField>(&self, dest: &mut Vec<F>) {
111 self.u.to_sponge_field_elements(dest);
112 self.x.to_sponge_field_elements(dest);
113 // We cannot call `to_native_sponge_field_elements(dest)` directly, as
114 // `to_native_sponge_field_elements` needs `F` to be `C::ScalarField`,
115 // but here `F` is a generic `PrimeField`.
116 self.cmE
117 .to_native_sponge_field_elements_as_vec()
118 .to_sponge_field_elements(dest);
119 self.cmW
120 .to_native_sponge_field_elements_as_vec()
121 .to_sponge_field_elements(dest);
122 }
123}
124
125impl<C: CurveGroup> CommittedInstanceOps<C> for CommittedInstance<C> {
126 type Var = CommittedInstanceVar<C>;
127
128 fn get_commitments(&self) -> Vec<C> {
129 vec![self.cmW, self.cmE]
130 }
131
132 fn is_incoming(&self) -> bool {
133 self.cmE == C::zero() && self.u == One::one()
134 }
135}
136
137#[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)]
138pub struct Witness<C: CurveGroup> {
139 pub E: Vec<C::ScalarField>,
140 pub rE: C::ScalarField,
141 pub W: Vec<C::ScalarField>,
142 pub rW: C::ScalarField,
143}
144
145impl<C: CurveGroup> Witness<C> {
146 pub fn new<const H: bool>(w: Vec<C::ScalarField>, e_len: usize, mut rng: impl RngCore) -> Self {
147 let (rW, rE) = if H {
148 (
149 C::ScalarField::rand(&mut rng),
150 C::ScalarField::rand(&mut rng),
151 )
152 } else {
153 (C::ScalarField::zero(), C::ScalarField::zero())
154 };
155
156 Self {
157 E: vec![C::ScalarField::zero(); e_len],
158 rE,
159 W: w,
160 rW,
161 }
162 }
163
164 pub fn commit<CS: CommitmentScheme<C, HC>, const HC: bool>(
165 &self,
166 params: &CS::ProverParams,
167 x: Vec<C::ScalarField>,
168 ) -> Result<CommittedInstance<C>, Error> {
169 let mut cmE = C::zero();
170 if !is_zero_vec::<C::ScalarField>(&self.E) {
171 cmE = CS::commit(params, &self.E, &self.rE)?;
172 }
173 let cmW = CS::commit(params, &self.W, &self.rW)?;
174 Ok(CommittedInstance {
175 cmE,
176 u: C::ScalarField::one(),
177 cmW,
178 x,
179 })
180 }
181}
182
183impl<C: CurveGroup> Dummy<&R1CS<CF1<C>>> for Witness<C> {
184 fn dummy(r1cs: &R1CS<CF1<C>>) -> Self {
185 Self {
186 E: vec![C::ScalarField::zero(); r1cs.A.n_rows],
187 rE: C::ScalarField::zero(),
188 W: vec![C::ScalarField::zero(); r1cs.A.n_cols - 1 - r1cs.l],
189 rW: C::ScalarField::zero(),
190 }
191 }
192}
193
194impl<C: CurveGroup> WitnessOps<C::ScalarField> for Witness<C> {
195 type Var = WitnessVar<C>;
196
197 fn get_openings(&self) -> Vec<(&[C::ScalarField], C::ScalarField)> {
198 vec![(&self.W, self.rW), (&self.E, self.rE)]
199 }
200}
201
202#[derive(Debug, Clone)]
203pub struct PreprocessorParam<C1, C2, FC, CS1, CS2, const H: bool = false>
204where
205 C1: CurveGroup,
206 C2: CurveGroup,
207 FC: FCircuit<C1::ScalarField>,
208 CS1: CommitmentScheme<C1, H>,
209 CS2: CommitmentScheme<C2, H>,
210{
211 pub poseidon_config: PoseidonConfig<C1::ScalarField>,
212 pub F: FC,
213 // cs params if not provided, will be generated at the preprocess method
214 pub cs_pp: Option<CS1::ProverParams>,
215 pub cs_vp: Option<CS1::VerifierParams>,
216 pub cf_cs_pp: Option<CS2::ProverParams>,
217 pub cf_cs_vp: Option<CS2::VerifierParams>,
218}
219
220impl<C1, C2, FC, CS1, CS2, const H: bool> PreprocessorParam<C1, C2, FC, CS1, CS2, H>
221where
222 C1: CurveGroup,
223 C2: CurveGroup,
224 FC: FCircuit<C1::ScalarField>,
225 CS1: CommitmentScheme<C1, H>,
226 CS2: CommitmentScheme<C2, H>,
227{
228 pub fn new(poseidon_config: PoseidonConfig<C1::ScalarField>, F: FC) -> Self {
229 Self {
230 poseidon_config,
231 F,
232 cs_pp: None,
233 cs_vp: None,
234 cf_cs_pp: None,
235 cf_cs_vp: None,
236 }
237 }
238}
239
240/// Proving parameters for Nova-based IVC
241#[derive(Debug, Clone)]
242pub struct ProverParams<C1, C2, CS1, CS2, const H: bool = false>
243where
244 C1: CurveGroup,
245 C2: CurveGroup,
246 CS1: CommitmentScheme<C1, H>,
247 CS2: CommitmentScheme<C2, H>,
248{
249 /// Poseidon sponge configuration
250 pub poseidon_config: PoseidonConfig<C1::ScalarField>,
251 /// Proving parameters of the underlying commitment scheme over C1
252 pub cs_pp: CS1::ProverParams,
253 /// Proving parameters of the underlying commitment scheme over C2
254 pub cf_cs_pp: CS2::ProverParams,
255}
256
257impl<C1, C2, CS1, CS2, const H: bool> Valid for ProverParams<C1, C2, CS1, CS2, H>
258where
259 C1: CurveGroup,
260 C2: CurveGroup,
261 CS1: CommitmentScheme<C1, H>,
262 CS2: CommitmentScheme<C2, H>,
263{
264 fn check(&self) -> Result<(), ark_serialize::SerializationError> {
265 self.poseidon_config.full_rounds.check()?;
266 self.poseidon_config.partial_rounds.check()?;
267 self.poseidon_config.alpha.check()?;
268 self.poseidon_config.ark.check()?;
269 self.poseidon_config.mds.check()?;
270 self.poseidon_config.rate.check()?;
271 self.poseidon_config.capacity.check()?;
272 self.cs_pp.check()?;
273 self.cf_cs_pp.check()?;
274 Ok(())
275 }
276}
277impl<C1, C2, CS1, CS2, const H: bool> CanonicalSerialize for ProverParams<C1, C2, CS1, CS2, H>
278where
279 C1: CurveGroup,
280 C2: CurveGroup,
281 CS1: CommitmentScheme<C1, H>,
282 CS2: CommitmentScheme<C2, H>,
283{
284 fn serialize_with_mode<W: std::io::prelude::Write>(
285 &self,
286 mut writer: W,
287 compress: ark_serialize::Compress,
288 ) -> Result<(), ark_serialize::SerializationError> {
289 self.cs_pp.serialize_with_mode(&mut writer, compress)?;
290 self.cf_cs_pp.serialize_with_mode(&mut writer, compress)
291 }
292
293 fn serialized_size(&self, compress: ark_serialize::Compress) -> usize {
294 self.cs_pp.serialized_size(compress) + self.cf_cs_pp.serialized_size(compress)
295 }
296}
297impl<C1, C2, CS1, CS2, const H: bool> CanonicalDeserialize for ProverParams<C1, C2, CS1, CS2, H>
298where
299 C1: CurveGroup,
300 C2: CurveGroup,
301 CS1: CommitmentScheme<C1, H>,
302 CS2: CommitmentScheme<C2, H>,
303{
304 fn deserialize_with_mode<R: std::io::prelude::Read>(
305 mut reader: R,
306 compress: ark_serialize::Compress,
307 validate: ark_serialize::Validate,
308 ) -> Result<Self, ark_serialize::SerializationError> {
309 let cs_pp = CS1::ProverParams::deserialize_with_mode(&mut reader, compress, validate)?;
310 let cf_cs_pp = CS2::ProverParams::deserialize_with_mode(&mut reader, compress, validate)?;
311 Ok(ProverParams {
312 poseidon_config: poseidon_canonical_config::<C1::ScalarField>(),
313 cs_pp,
314 cf_cs_pp,
315 })
316 }
317}
318
319/// Verification parameters for Nova-based IVC
320#[derive(Debug, Clone)]
321pub struct VerifierParams<C1, C2, CS1, CS2, const H: bool = false>
322where
323 C1: CurveGroup,
324 C2: CurveGroup,
325 CS1: CommitmentScheme<C1, H>,
326 CS2: CommitmentScheme<C2, H>,
327{
328 /// Poseidon sponge configuration
329 pub poseidon_config: PoseidonConfig<C1::ScalarField>,
330 /// R1CS of the Augmented step circuit
331 pub r1cs: R1CS<C1::ScalarField>,
332 /// R1CS of the CycleFold circuit
333 pub cf_r1cs: R1CS<C2::ScalarField>,
334 /// Verification parameters of the underlying commitment scheme over C1
335 pub cs_vp: CS1::VerifierParams,
336 /// Verification parameters of the underlying commitment scheme over C2
337 pub cf_cs_vp: CS2::VerifierParams,
338}
339
340impl<C1, C2, CS1, CS2, const H: bool> Valid for VerifierParams<C1, C2, CS1, CS2, H>
341where
342 C1: CurveGroup,
343 C2: CurveGroup,
344 CS1: CommitmentScheme<C1, H>,
345 CS2: CommitmentScheme<C2, H>,
346{
347 fn check(&self) -> Result<(), ark_serialize::SerializationError> {
348 self.poseidon_config.full_rounds.check()?;
349 self.poseidon_config.partial_rounds.check()?;
350 self.poseidon_config.alpha.check()?;
351 self.poseidon_config.ark.check()?;
352 self.poseidon_config.mds.check()?;
353 self.poseidon_config.rate.check()?;
354 self.poseidon_config.capacity.check()?;
355 self.r1cs.check()?;
356 self.cf_r1cs.check()?;
357 self.cs_vp.check()?;
358 self.cf_cs_vp.check()?;
359 Ok(())
360 }
361}
362impl<C1, C2, CS1, CS2, const H: bool> CanonicalSerialize for VerifierParams<C1, C2, CS1, CS2, H>
363where
364 C1: CurveGroup,
365 C2: CurveGroup,
366 CS1: CommitmentScheme<C1, H>,
367 CS2: CommitmentScheme<C2, H>,
368{
369 fn serialize_with_mode<W: std::io::prelude::Write>(
370 &self,
371 mut writer: W,
372 compress: ark_serialize::Compress,
373 ) -> Result<(), ark_serialize::SerializationError> {
374 self.r1cs.serialize_with_mode(&mut writer, compress)?;
375 self.cf_r1cs.serialize_with_mode(&mut writer, compress)?;
376 self.cs_vp.serialize_with_mode(&mut writer, compress)?;
377 self.cf_cs_vp.serialize_with_mode(&mut writer, compress)
378 }
379
380 fn serialized_size(&self, compress: ark_serialize::Compress) -> usize {
381 self.r1cs.serialized_size(compress)
382 + self.cf_r1cs.serialized_size(compress)
383 + self.cs_vp.serialized_size(compress)
384 + self.cf_cs_vp.serialized_size(compress)
385 }
386}
387impl<C1, C2, CS1, CS2, const H: bool> CanonicalDeserialize for VerifierParams<C1, C2, CS1, CS2, H>
388where
389 C1: CurveGroup,
390 C2: CurveGroup,
391 CS1: CommitmentScheme<C1, H>,
392 CS2: CommitmentScheme<C2, H>,
393{
394 fn deserialize_with_mode<R: std::io::prelude::Read>(
395 mut reader: R,
396 compress: ark_serialize::Compress,
397 validate: ark_serialize::Validate,
398 ) -> Result<Self, ark_serialize::SerializationError> {
399 let r1cs = R1CS::deserialize_with_mode(&mut reader, compress, validate)?;
400 let cf_r1cs = R1CS::deserialize_with_mode(&mut reader, compress, validate)?;
401 let cs_vp = CS1::VerifierParams::deserialize_with_mode(&mut reader, compress, validate)?;
402 let cf_cs_vp = CS2::VerifierParams::deserialize_with_mode(&mut reader, compress, validate)?;
403 Ok(VerifierParams {
404 poseidon_config: poseidon_canonical_config::<C1::ScalarField>(),
405 r1cs,
406 cf_r1cs,
407 cs_vp,
408 cf_cs_vp,
409 })
410 }
411}
412
413impl<C1, C2, CS1, CS2, const H: bool> VerifierParams<C1, C2, CS1, CS2, H>
414where
415 C1: CurveGroup,
416 C2: CurveGroup,
417 CS1: CommitmentScheme<C1, H>,
418 CS2: CommitmentScheme<C2, H>,
419{
420 /// returns the hash of the public parameters of Nova
421 pub fn pp_hash(&self) -> Result<C1::ScalarField, Error> {
422 pp_hash::<C1, C2, CS1, CS2, H>(
423 &self.r1cs,
424 &self.cf_r1cs,
425 &self.cs_vp,
426 &self.cf_cs_vp,
427 &self.poseidon_config,
428 )
429 }
430}
431
432/// Implements Nova+CycleFold's IVC, described in [Nova](https://eprint.iacr.org/2021/370.pdf) and
433/// [CycleFold](https://eprint.iacr.org/2023/1192.pdf), following the FoldingScheme trait
434/// The `H` const generic specifies whether the homorphic commitment scheme is blinding
435#[derive(Clone, Debug)]
436pub struct Nova<C1, GC1, C2, GC2, FC, CS1, CS2, const H: bool = false>
437where
438 C1: CurveGroup,
439 GC1: CurveVar<C1, CF2<C1>> + ToConstraintFieldGadget<CF2<C1>>,
440 C2: CurveGroup,
441 GC2: CurveVar<C2, CF2<C2>>,
442 FC: FCircuit<C1::ScalarField>,
443 CS1: CommitmentScheme<C1, H>,
444 CS2: CommitmentScheme<C2, H>,
445{
446 _gc1: PhantomData<GC1>,
447 _c2: PhantomData<C2>,
448 _gc2: PhantomData<GC2>,
449 /// R1CS of the Augmented Function circuit
450 pub r1cs: R1CS<C1::ScalarField>,
451 /// R1CS of the CycleFold circuit
452 pub cf_r1cs: R1CS<C2::ScalarField>,
453 pub poseidon_config: PoseidonConfig<C1::ScalarField>,
454 /// CommitmentScheme::ProverParams over C1
455 pub cs_pp: CS1::ProverParams,
456 /// CycleFold CommitmentScheme::ProverParams, over C2
457 pub cf_cs_pp: CS2::ProverParams,
458 /// F circuit, the circuit that is being folded
459 pub F: FC,
460 /// public params hash
461 pub pp_hash: C1::ScalarField,
462 pub i: C1::ScalarField,
463 /// initial state
464 pub z_0: Vec<C1::ScalarField>,
465 /// current i-th state
466 pub z_i: Vec<C1::ScalarField>,
467 /// Nova instances
468 pub w_i: Witness<C1>,
469 pub u_i: CommittedInstance<C1>,
470 pub W_i: Witness<C1>,
471 pub U_i: CommittedInstance<C1>,
472
473 /// CycleFold running instance
474 pub cf_W_i: CycleFoldWitness<C2>,
475 pub cf_U_i: CycleFoldCommittedInstance<C2>,
476}
477
478impl<C1, GC1, C2, GC2, FC, CS1, CS2, const H: bool> FoldingScheme<C1, C2, FC>
479 for Nova<C1, GC1, C2, GC2, FC, CS1, CS2, H>
480where
481 C1: CurveGroup,
482 GC1: CurveVar<C1, CF2<C1>> + ToConstraintFieldGadget<CF2<C1>>,
483 C2: CurveGroup,
484 GC2: CurveVar<C2, CF2<C2>> + ToConstraintFieldGadget<CF2<C2>>,
485 FC: FCircuit<C1::ScalarField>,
486 CS1: CommitmentScheme<C1, H>,
487 CS2: CommitmentScheme<C2, H>,
488 <C1 as CurveGroup>::BaseField: PrimeField,
489 <C2 as CurveGroup>::BaseField: PrimeField,
490 <C1 as Group>::ScalarField: Absorb,
491 <C2 as Group>::ScalarField: Absorb,
492 C1: CurveGroup<BaseField = C2::ScalarField, ScalarField = C2::BaseField>,
493 for<'a> &'a GC1: GroupOpsBounds<'a, C1, GC1>,
494 for<'a> &'a GC2: GroupOpsBounds<'a, C2, GC2>,
495{
496 type PreprocessorParam = PreprocessorParam<C1, C2, FC, CS1, CS2, H>;
497 type ProverParam = ProverParams<C1, C2, CS1, CS2, H>;
498 type VerifierParam = VerifierParams<C1, C2, CS1, CS2, H>;
499 type RunningInstance = (CommittedInstance<C1>, Witness<C1>);
500 type IncomingInstance = (CommittedInstance<C1>, Witness<C1>);
501 type MultiCommittedInstanceWithWitness = ();
502 type CFInstance = (CycleFoldCommittedInstance<C2>, CycleFoldWitness<C2>);
503
504 fn preprocess(
505 mut rng: impl RngCore,
506 prep_param: &Self::PreprocessorParam,
507 ) -> Result<(Self::ProverParam, Self::VerifierParam), Error> {
508 let (r1cs, cf_r1cs) =
509 get_r1cs::<C1, GC1, C2, GC2, FC>(&prep_param.poseidon_config, prep_param.F.clone())?;
510
511 // if cs params exist, use them, if not, generate new ones
512 let cs_pp: CS1::ProverParams;
513 let cs_vp: CS1::VerifierParams;
514 let cf_cs_pp: CS2::ProverParams;
515 let cf_cs_vp: CS2::VerifierParams;
516 if prep_param.cs_pp.is_some()
517 && prep_param.cf_cs_pp.is_some()
518 && prep_param.cs_vp.is_some()
519 && prep_param.cf_cs_vp.is_some()
520 {
521 cs_pp = prep_param.clone().cs_pp.unwrap();
522 cs_vp = prep_param.clone().cs_vp.unwrap();
523 cf_cs_pp = prep_param.clone().cf_cs_pp.unwrap();
524 cf_cs_vp = prep_param.clone().cf_cs_vp.unwrap();
525 } else {
526 (cs_pp, cs_vp) = CS1::setup(&mut rng, r1cs.A.n_rows)?;
527 (cf_cs_pp, cf_cs_vp) = CS2::setup(&mut rng, cf_r1cs.A.n_rows)?;
528 }
529
530 let prover_params = ProverParams::<C1, C2, CS1, CS2, H> {
531 poseidon_config: prep_param.poseidon_config.clone(),
532 cs_pp: cs_pp.clone(),
533 cf_cs_pp: cf_cs_pp.clone(),
534 };
535 let verifier_params = VerifierParams::<C1, C2, CS1, CS2, H> {
536 poseidon_config: prep_param.poseidon_config.clone(),
537 r1cs,
538 cf_r1cs,
539 cs_vp,
540 cf_cs_vp,
541 };
542
543 Ok((prover_params, verifier_params))
544 }
545
546 /// Initializes the Nova+CycleFold's IVC for the given parameters and initial state `z_0`.
547 fn init(
548 params: &(Self::ProverParam, Self::VerifierParam),
549 F: FC,
550 z_0: Vec<C1::ScalarField>,
551 ) -> Result<Self, Error> {
552 let (pp, vp) = params;
553
554 // prepare the circuit to obtain its R1CS
555 let cs = ConstraintSystem::<C1::ScalarField>::new_ref();
556 let cs2 = ConstraintSystem::<C1::BaseField>::new_ref();
557
558 let augmented_F_circuit =
559 AugmentedFCircuit::<C1, C2, GC2, FC>::empty(&pp.poseidon_config, F.clone());
560 let cf_circuit = NovaCycleFoldCircuit::<C1, GC1>::empty();
561
562 augmented_F_circuit.generate_constraints(cs.clone())?;
563 cs.finalize();
564 let cs = cs.into_inner().ok_or(Error::NoInnerConstraintSystem)?;
565 let r1cs = extract_r1cs::<C1::ScalarField>(&cs);
566
567 cf_circuit.generate_constraints(cs2.clone())?;
568 cs2.finalize();
569 let cs2 = cs2.into_inner().ok_or(Error::NoInnerConstraintSystem)?;
570 let cf_r1cs = extract_r1cs::<C1::BaseField>(&cs2);
571
572 // compute the public params hash
573 let pp_hash = vp.pp_hash()?;
574
575 // setup the dummy instances
576 let (W_dummy, U_dummy) = r1cs.dummy_witness_instance();
577 let (w_dummy, u_dummy) = r1cs.dummy_witness_instance();
578 let (cf_W_dummy, cf_U_dummy) = cf_r1cs.dummy_witness_instance();
579
580 // W_dummy=W_0 is a 'dummy witness', all zeroes, but with the size corresponding to the
581 // R1CS that we're working with.
582 Ok(Self {
583 _gc1: PhantomData,
584 _c2: PhantomData,
585 _gc2: PhantomData,
586 r1cs,
587 cf_r1cs,
588 poseidon_config: pp.poseidon_config.clone(),
589 cs_pp: pp.cs_pp.clone(),
590 cf_cs_pp: pp.cf_cs_pp.clone(),
591 F,
592 pp_hash,
593 i: C1::ScalarField::zero(),
594 z_0: z_0.clone(),
595 z_i: z_0,
596 w_i: w_dummy,
597 u_i: u_dummy,
598 W_i: W_dummy,
599 U_i: U_dummy,
600 // cyclefold running instance
601 cf_W_i: cf_W_dummy,
602 cf_U_i: cf_U_dummy,
603 })
604 }
605
606 /// Implements IVC.P of Nova+CycleFold
607 fn prove_step(
608 &mut self,
609 mut rng: impl RngCore,
610 external_inputs: Vec<C1::ScalarField>,
611 // Nova does not support multi-instances folding
612 _other_instances: Option<Self::MultiCommittedInstanceWithWitness>,
613 ) -> Result<(), Error> {
614 // ensure that commitments are blinding if user has specified so.
615 if H && self.i >= C1::ScalarField::one() {
616 let blinding_commitments = if self.i == C1::ScalarField::one() {
617 // blinding values of the running instances are zero at the first iteration
618 vec![self.w_i.rW, self.w_i.rE]
619 } else {
620 vec![self.w_i.rW, self.w_i.rE, self.W_i.rW, self.W_i.rE]
621 };
622 if blinding_commitments.contains(&C1::ScalarField::zero()) {
623 return Err(Error::IncorrectBlinding(
624 H,
625 format!("{:?}", blinding_commitments),
626 ));
627 }
628 }
629 // `sponge` is for digest computation.
630 let sponge = PoseidonSponge::<C1::ScalarField>::new(&self.poseidon_config);
631 // `transcript` is for challenge generation.
632 let mut transcript = sponge.clone();
633
634 let augmented_F_circuit: AugmentedFCircuit<C1, C2, GC2, FC>;
635
636 // Nova does not support (by design) multi-instances folding
637 if _other_instances.is_some() {
638 return Err(Error::NoMultiInstances);
639 }
640
641 if self.z_i.len() != self.F.state_len() {
642 return Err(Error::NotSameLength(
643 "z_i.len()".to_string(),
644 self.z_i.len(),
645 "F.state_len()".to_string(),
646 self.F.state_len(),
647 ));
648 }
649 if external_inputs.len() != self.F.external_inputs_len() {
650 return Err(Error::NotSameLength(
651 "F.external_inputs_len()".to_string(),
652 self.F.external_inputs_len(),
653 "external_inputs.len()".to_string(),
654 external_inputs.len(),
655 ));
656 }
657
658 if self.i > C1::ScalarField::from_le_bytes_mod_order(&usize::MAX.to_le_bytes()) {
659 return Err(Error::MaxStep);
660 }
661
662 let i_usize;
663
664 #[cfg(target_pointer_width = "64")]
665 {
666 let mut i_bytes: [u8; 8] = [0; 8];
667 i_bytes.copy_from_slice(&self.i.into_bigint().to_bytes_le()[..8]);
668 i_usize = usize::from_le_bytes(i_bytes);
669 }
670
671 #[cfg(target_pointer_width = "32")]
672 {
673 let mut i_bytes: [u8; 4] = [0; 4];
674 i_bytes.copy_from_slice(&self.i.into_bigint().to_bytes_le()[..4]);
675 i_usize = usize::from_le_bytes(i_bytes);
676 }
677
678 let z_i1 = self
679 .F
680 .step_native(i_usize, self.z_i.clone(), external_inputs.clone())?;
681
682 // compute T and cmT for AugmentedFCircuit
683 let (aux_p, aux_v) = self.compute_cmT()?;
684 let cmT = aux_v;
685
686 // r_bits is the r used to the RLC of the F' instances
687 let r_bits = ChallengeGadget::<C1, CommittedInstance<C1>>::get_challenge_native(
688 &mut transcript,
689 self.pp_hash,
690 &self.U_i,
691 &self.u_i,
692 Some(&cmT),
693 );
694 let r_Fr = C1::ScalarField::from_bigint(BigInteger::from_bits_le(&r_bits))
695 .ok_or(Error::OutOfBounds)?;
696 let r_Fq = C1::BaseField::from_bigint(BigInteger::from_bits_le(&r_bits))
697 .ok_or(Error::OutOfBounds)?;
698
699 // fold Nova instances
700 let (W_i1, U_i1): (Witness<C1>, CommittedInstance<C1>) = NIFS::<C1, CS1, H>::prove(
701 r_Fr, &self.W_i, &self.U_i, &self.w_i, &self.u_i, &aux_p, &aux_v,
702 )?;
703
704 // folded instance output (public input, x)
705 // u_{i+1}.x[0] = H(i+1, z_0, z_{i+1}, U_{i+1})
706 let u_i1_x = U_i1.hash(
707 &sponge,
708 self.pp_hash,
709 self.i + C1::ScalarField::one(),
710 &self.z_0,
711 &z_i1,
712 );
713 // u_{i+1}.x[1] = H(cf_U_{i+1})
714 let cf_u_i1_x: C1::ScalarField;
715
716 if self.i == C1::ScalarField::zero() {
717 cf_u_i1_x = self.cf_U_i.hash_cyclefold(&sponge, self.pp_hash);
718 // base case
719 augmented_F_circuit = AugmentedFCircuit::<C1, C2, GC2, FC> {
720 _gc2: PhantomData,
721 poseidon_config: self.poseidon_config.clone(),
722 pp_hash: Some(self.pp_hash),
723 i: Some(C1::ScalarField::zero()), // = i=0
724 i_usize: Some(0),
725 z_0: Some(self.z_0.clone()), // = z_i
726 z_i: Some(self.z_i.clone()),
727 external_inputs: Some(external_inputs.clone()),
728 u_i_cmW: Some(self.u_i.cmW), // = dummy
729 U_i: Some(self.U_i.clone()), // = dummy
730 U_i1_cmE: Some(U_i1.cmE),
731 U_i1_cmW: Some(U_i1.cmW),
732 cmT: Some(cmT),
733 F: self.F.clone(),
734 x: Some(u_i1_x),
735 cf1_u_i_cmW: None,
736 cf2_u_i_cmW: None,
737 cf_U_i: None,
738 cf1_cmT: None,
739 cf2_cmT: None,
740 cf_x: Some(cf_u_i1_x),
741 };
742
743 #[cfg(test)]
744 NIFS::<C1, CS1, H>::verify_folded_instance(r_Fr, &self.U_i, &self.u_i, &U_i1, &cmT)?;
745 } else {
746 // CycleFold part:
747 // get the vector used as public inputs 'x' in the CycleFold circuit
748 // cyclefold circuit for cmW
749 let cfW_u_i_x = [
750 vec![r_Fq],
751 get_cm_coordinates(&self.U_i.cmW),
752 get_cm_coordinates(&self.u_i.cmW),
753 get_cm_coordinates(&U_i1.cmW),
754 ]
755 .concat();
756 // cyclefold circuit for cmE
757 let cfE_u_i_x = [
758 vec![r_Fq],
759 get_cm_coordinates(&self.U_i.cmE),
760 get_cm_coordinates(&cmT),
761 get_cm_coordinates(&U_i1.cmE),
762 ]
763 .concat();
764
765 let cfW_circuit = NovaCycleFoldCircuit::<C1, GC1> {
766 _gc: PhantomData,
767 r_bits: Some(r_bits.clone()),
768 points: Some(vec![self.U_i.clone().cmW, self.u_i.clone().cmW]),
769 x: Some(cfW_u_i_x.clone()),
770 };
771 let cfE_circuit = NovaCycleFoldCircuit::<C1, GC1> {
772 _gc: PhantomData,
773 r_bits: Some(r_bits.clone()),
774 points: Some(vec![self.U_i.clone().cmE, cmT]),
775 x: Some(cfE_u_i_x.clone()),
776 };
777
778 // fold self.cf_U_i + cfW_U -> folded running with cfW
779 let (_cfW_w_i, cfW_u_i, cfW_W_i1, cfW_U_i1, cfW_cmT, _) = self.fold_cyclefold_circuit(
780 &mut transcript,
781 self.cf_W_i.clone(), // CycleFold running instance witness
782 self.cf_U_i.clone(), // CycleFold running instance
783 cfW_u_i_x,
784 cfW_circuit,
785 &mut rng,
786 )?;
787 // fold [the output from folding self.cf_U_i + cfW_U] + cfE_U = folded_running_with_cfW + cfE
788 let (_cfE_w_i, cfE_u_i, cf_W_i1, cf_U_i1, cf_cmT, _) = self.fold_cyclefold_circuit(
789 &mut transcript,
790 cfW_W_i1,
791 cfW_U_i1.clone(),
792 cfE_u_i_x,
793 cfE_circuit,
794 &mut rng,
795 )?;
796
797 cf_u_i1_x = cf_U_i1.hash_cyclefold(&sponge, self.pp_hash);
798
799 augmented_F_circuit = AugmentedFCircuit::<C1, C2, GC2, FC> {
800 _gc2: PhantomData,
801 poseidon_config: self.poseidon_config.clone(),
802 pp_hash: Some(self.pp_hash),
803 i: Some(self.i),
804 i_usize: Some(i_usize),
805 z_0: Some(self.z_0.clone()),
806 z_i: Some(self.z_i.clone()),
807 external_inputs: Some(external_inputs.clone()),
808 u_i_cmW: Some(self.u_i.cmW),
809 U_i: Some(self.U_i.clone()),
810 U_i1_cmE: Some(U_i1.cmE),
811 U_i1_cmW: Some(U_i1.cmW),
812 cmT: Some(cmT),
813 F: self.F.clone(),
814 x: Some(u_i1_x),
815 // cyclefold values
816 cf1_u_i_cmW: Some(cfW_u_i.cmW),
817 cf2_u_i_cmW: Some(cfE_u_i.cmW),
818 cf_U_i: Some(self.cf_U_i.clone()),
819 cf1_cmT: Some(cfW_cmT),
820 cf2_cmT: Some(cf_cmT),
821 cf_x: Some(cf_u_i1_x),
822 };
823
824 self.cf_W_i = cf_W_i1;
825 self.cf_U_i = cf_U_i1;
826
827 #[cfg(test)]
828 {
829 cfW_u_i.check_incoming()?;
830 cfE_u_i.check_incoming()?;
831 self.cf_r1cs.check_relation(&_cfW_w_i, &cfW_u_i)?;
832 self.cf_r1cs.check_relation(&_cfE_w_i, &cfE_u_i)?;
833 self.cf_r1cs.check_relation(&self.cf_W_i, &self.cf_U_i)?;
834 }
835 }
836
837 let cs = ConstraintSystem::<C1::ScalarField>::new_ref();
838
839 augmented_F_circuit.generate_constraints(cs.clone())?;
840
841 #[cfg(test)]
842 assert!(cs.is_satisfied().unwrap());
843
844 let cs = cs.into_inner().ok_or(Error::NoInnerConstraintSystem)?;
845 let (w_i1, x_i1) = extract_w_x::<C1::ScalarField>(&cs);
846 if x_i1[0] != u_i1_x || x_i1[1] != cf_u_i1_x {
847 return Err(Error::NotEqual);
848 }
849
850 #[cfg(test)]
851 if x_i1.len() != 2 {
852 return Err(Error::NotExpectedLength(x_i1.len(), 2));
853 }
854
855 // set values for next iteration
856 self.i += C1::ScalarField::one();
857 self.z_i = z_i1;
858 self.w_i = Witness::<C1>::new::<H>(w_i1, self.r1cs.A.n_rows, &mut rng);
859 self.u_i = self.w_i.commit::<CS1, H>(&self.cs_pp, x_i1)?;
860 self.W_i = W_i1;
861 self.U_i = U_i1;
862
863 #[cfg(test)]
864 {
865 self.u_i.check_incoming()?;
866 self.r1cs.check_relation(&self.w_i, &self.u_i)?;
867 self.r1cs.check_relation(&self.W_i, &self.U_i)?;
868 }
869
870 Ok(())
871 }
872
873 fn state(&self) -> Vec<C1::ScalarField> {
874 self.z_i.clone()
875 }
876
877 fn instances(
878 &self,
879 ) -> (
880 Self::RunningInstance,
881 Self::IncomingInstance,
882 Self::CFInstance,
883 ) {
884 (
885 (self.U_i.clone(), self.W_i.clone()),
886 (self.u_i.clone(), self.w_i.clone()),
887 (self.cf_U_i.clone(), self.cf_W_i.clone()),
888 )
889 }
890
891 /// Implements IVC.V of Nova+CycleFold. Notice that this method does not include the
892 /// commitments verification, which is done in the Decider.
893 fn verify(
894 vp: Self::VerifierParam,
895 z_0: Vec<C1::ScalarField>, // initial state
896 z_i: Vec<C1::ScalarField>, // last state
897 num_steps: C1::ScalarField,
898 running_instance: Self::RunningInstance,
899 incoming_instance: Self::IncomingInstance,
900 cyclefold_instance: Self::CFInstance,
901 ) -> Result<(), Error> {
902 let sponge = PoseidonSponge::<C1::ScalarField>::new(&vp.poseidon_config);
903
904 if num_steps == C1::ScalarField::zero() {
905 if z_0 != z_i {
906 return Err(Error::IVCVerificationFail);
907 }
908 return Ok(());
909 }
910
911 let (U_i, W_i) = running_instance;
912 let (u_i, w_i) = incoming_instance;
913 let (cf_U_i, cf_W_i) = cyclefold_instance;
914
915 if u_i.x.len() != 2 || U_i.x.len() != 2 {
916 return Err(Error::IVCVerificationFail);
917 }
918
919 let pp_hash = vp.pp_hash()?;
920
921 // check that u_i's output points to the running instance
922 // u_i.X[0] == H(i, z_0, z_i, U_i)
923 let expected_u_i_x = U_i.hash(&sponge, pp_hash, num_steps, &z_0, &z_i);
924 if expected_u_i_x != u_i.x[0] {
925 return Err(Error::IVCVerificationFail);
926 }
927 // u_i.X[1] == H(cf_U_i)
928 let expected_cf_u_i_x = cf_U_i.hash_cyclefold(&sponge, pp_hash);
929 if expected_cf_u_i_x != u_i.x[1] {
930 return Err(Error::IVCVerificationFail);
931 }
932
933 // check R1CS satisfiability, which is equivalent to checking if `u_i`
934 // is an incoming instance and if `w_i` and `u_i` satisfy RelaxedR1CS
935 u_i.check_incoming()?;
936 vp.r1cs.check_relation(&w_i, &u_i)?;
937 // check RelaxedR1CS satisfiability
938 vp.r1cs.check_relation(&W_i, &U_i)?;
939
940 // check CycleFold RelaxedR1CS satisfiability
941 vp.cf_r1cs.check_relation(&cf_W_i, &cf_U_i)?;
942
943 Ok(())
944 }
945}
946
947impl<C1, GC1, C2, GC2, FC, CS1, CS2, const H: bool> Nova<C1, GC1, C2, GC2, FC, CS1, CS2, H>
948where
949 C1: CurveGroup,
950 GC1: CurveVar<C1, CF2<C1>> + ToConstraintFieldGadget<CF2<C1>>,
951 C2: CurveGroup,
952 GC2: CurveVar<C2, CF2<C2>>,
953 FC: FCircuit<C1::ScalarField>,
954 CS1: CommitmentScheme<C1, H>,
955 CS2: CommitmentScheme<C2, H>,
956 <C2 as CurveGroup>::BaseField: PrimeField,
957 <C1 as Group>::ScalarField: Absorb,
958 <C2 as Group>::ScalarField: Absorb,
959 C1: CurveGroup<BaseField = C2::ScalarField, ScalarField = C2::BaseField>,
960{
961 // computes T and cmT for the AugmentedFCircuit
962 fn compute_cmT(&self) -> Result<(Vec<C1::ScalarField>, C1), Error> {
963 NIFS::<C1, CS1, H>::compute_aux(
964 &self.cs_pp,
965 &self.r1cs,
966 &self.w_i,
967 &self.u_i,
968 &self.W_i,
969 &self.U_i,
970 )
971 }
972}
973
974impl<C1, GC1, C2, GC2, FC, CS1, CS2, const H: bool> Nova<C1, GC1, C2, GC2, FC, CS1, CS2, H>
975where
976 C1: CurveGroup,
977 GC1: CurveVar<C1, CF2<C1>> + ToConstraintFieldGadget<CF2<C1>>,
978 C2: CurveGroup,
979 GC2: CurveVar<C2, CF2<C2>> + ToConstraintFieldGadget<CF2<C2>>,
980 FC: FCircuit<C1::ScalarField>,
981 CS1: CommitmentScheme<C1, H>,
982 CS2: CommitmentScheme<C2, H>,
983 <C1 as CurveGroup>::BaseField: PrimeField,
984 <C2 as CurveGroup>::BaseField: PrimeField,
985 <C1 as Group>::ScalarField: Absorb,
986 <C2 as Group>::ScalarField: Absorb,
987 C1: CurveGroup<BaseField = C2::ScalarField, ScalarField = C2::BaseField>,
988 for<'a> &'a GC1: GroupOpsBounds<'a, C1, GC1>,
989 for<'a> &'a GC2: GroupOpsBounds<'a, C2, GC2>,
990{
991 // folds the given cyclefold circuit and its instances
992 #[allow(clippy::type_complexity)]
993 fn fold_cyclefold_circuit<T: Transcript<C1::ScalarField>>(
994 &self,
995 transcript: &mut T,
996 cf_W_i: CycleFoldWitness<C2>, // witness of the running instance
997 cf_U_i: CycleFoldCommittedInstance<C2>, // running instance
998 cf_u_i_x: Vec<C2::ScalarField>,
999 cf_circuit: NovaCycleFoldCircuit<C1, GC1>,
1000 rng: &mut impl RngCore,
1001 ) -> Result<
1002 (
1003 CycleFoldWitness<C2>,
1004 CycleFoldCommittedInstance<C2>, // u_i
1005 CycleFoldWitness<C2>, // W_i1
1006 CycleFoldCommittedInstance<C2>, // U_i1
1007 C2, // cmT
1008 C2::ScalarField, // r_Fq
1009 ),
1010 Error,
1011 > {
1012 fold_cyclefold_circuit::<NovaCycleFoldConfig<C1>, C1, GC1, C2, GC2, CS2, H>(
1013 transcript,
1014 self.cf_r1cs.clone(),
1015 self.cf_cs_pp.clone(),
1016 self.pp_hash,
1017 cf_W_i,
1018 cf_U_i,
1019 cf_u_i_x,
1020 cf_circuit,
1021 rng,
1022 )
1023 }
1024}
1025
1026/// helper method to get the r1cs from the ConstraintSynthesizer
1027pub fn get_r1cs_from_cs<F: PrimeField>(
1028 circuit: impl ConstraintSynthesizer<F>,
1029) -> Result<R1CS<F>, Error> {
1030 let cs = ConstraintSystem::<F>::new_ref();
1031 circuit.generate_constraints(cs.clone())?;
1032 cs.finalize();
1033 let cs = cs.into_inner().ok_or(Error::NoInnerConstraintSystem)?;
1034 let r1cs = extract_r1cs::<F>(&cs);
1035 Ok(r1cs)
1036}
1037
1038/// helper method to get the R1CS for both the AugmentedFCircuit and the CycleFold circuit
1039#[allow(clippy::type_complexity)]
1040pub fn get_r1cs<C1, GC1, C2, GC2, FC>(
1041 poseidon_config: &PoseidonConfig<C1::ScalarField>,
1042 F_circuit: FC,
1043) -> Result<(R1CS<C1::ScalarField>, R1CS<C2::ScalarField>), Error>
1044where
1045 C1: CurveGroup,
1046 GC1: CurveVar<C1, CF2<C1>> + ToConstraintFieldGadget<CF2<C1>>,
1047 C2: CurveGroup,
1048 GC2: CurveVar<C2, CF2<C2>> + ToConstraintFieldGadget<CF2<C2>>,
1049 FC: FCircuit<C1::ScalarField>,
1050 <C1 as CurveGroup>::BaseField: PrimeField,
1051 <C2 as CurveGroup>::BaseField: PrimeField,
1052 <C1 as Group>::ScalarField: Absorb,
1053 <C2 as Group>::ScalarField: Absorb,
1054 C1: CurveGroup<BaseField = C2::ScalarField, ScalarField = C2::BaseField>,
1055 for<'a> &'a GC1: GroupOpsBounds<'a, C1, GC1>,
1056 for<'a> &'a GC2: GroupOpsBounds<'a, C2, GC2>,
1057{
1058 let augmented_F_circuit =
1059 AugmentedFCircuit::<C1, C2, GC2, FC>::empty(poseidon_config, F_circuit);
1060 let cf_circuit = NovaCycleFoldCircuit::<C1, GC1>::empty();
1061 let r1cs = get_r1cs_from_cs::<C1::ScalarField>(augmented_F_circuit)?;
1062 let cf_r1cs = get_r1cs_from_cs::<C2::ScalarField>(cf_circuit)?;
1063 Ok((r1cs, cf_r1cs))
1064}
1065
1066/// helper method to get the pedersen params length for both the AugmentedFCircuit and the
1067/// CycleFold circuit
1068pub fn get_cs_params_len<C1, GC1, C2, GC2, FC>(
1069 poseidon_config: &PoseidonConfig<C1::ScalarField>,
1070 F_circuit: FC,
1071) -> Result<(usize, usize), Error>
1072where
1073 C1: CurveGroup,
1074 GC1: CurveVar<C1, CF2<C1>> + ToConstraintFieldGadget<CF2<C1>>,
1075 C2: CurveGroup,
1076 GC2: CurveVar<C2, CF2<C2>> + ToConstraintFieldGadget<CF2<C2>>,
1077 FC: FCircuit<C1::ScalarField>,
1078 <C1 as CurveGroup>::BaseField: PrimeField,
1079 <C2 as CurveGroup>::BaseField: PrimeField,
1080 <C1 as Group>::ScalarField: Absorb,
1081 <C2 as Group>::ScalarField: Absorb,
1082 C1: CurveGroup<BaseField = C2::ScalarField, ScalarField = C2::BaseField>,
1083 for<'a> &'a GC1: GroupOpsBounds<'a, C1, GC1>,
1084 for<'a> &'a GC2: GroupOpsBounds<'a, C2, GC2>,
1085{
1086 let (r1cs, cf_r1cs) = get_r1cs::<C1, GC1, C2, GC2, FC>(poseidon_config, F_circuit)?;
1087 Ok((r1cs.A.n_rows, cf_r1cs.A.n_rows))
1088}
1089
1090#[cfg(test)]
1091pub mod tests {
1092 use crate::commitment::kzg::KZG;
1093 use ark_bn254::{constraints::GVar, Bn254, Fr, G1Projective as Projective};
1094 use ark_grumpkin::{constraints::GVar as GVar2, Projective as Projective2};
1095
1096 use super::*;
1097 use crate::commitment::pedersen::Pedersen;
1098 use crate::frontend::utils::CubicFCircuit;
1099 use crate::transcript::poseidon::poseidon_canonical_config;
1100
1101 /// This test tests the Nova+CycleFold IVC, and by consequence it is also testing the
1102 /// AugmentedFCircuit
1103 #[test]
1104 fn test_ivc() {
1105 let poseidon_config = poseidon_canonical_config::<Fr>();
1106
1107 let F_circuit = CubicFCircuit::<Fr>::new(()).unwrap();
1108
1109 // run the test using Pedersen commitments on both sides of the curve cycle
1110 test_ivc_opt::<Pedersen<Projective>, Pedersen<Projective2>, false>(
1111 poseidon_config.clone(),
1112 F_circuit,
1113 3,
1114 );
1115
1116 test_ivc_opt::<Pedersen<Projective, true>, Pedersen<Projective2, true>, true>(
1117 poseidon_config.clone(),
1118 F_circuit,
1119 3,
1120 );
1121
1122 // run the test using KZG for the commitments on the main curve, and Pedersen for the
1123 // commitments on the secondary curve
1124 test_ivc_opt::<KZG<Bn254>, Pedersen<Projective2>, false>(poseidon_config, F_circuit, 3);
1125 }
1126
1127 // test_ivc allowing to choose the CommitmentSchemes
1128 #[allow(clippy::type_complexity)]
1129 pub(crate) fn test_ivc_opt<
1130 CS1: CommitmentScheme<Projective, H>,
1131 CS2: CommitmentScheme<Projective2, H>,
1132 const H: bool,
1133 >(
1134 poseidon_config: PoseidonConfig<Fr>,
1135 F_circuit: CubicFCircuit<Fr>,
1136 num_steps: usize,
1137 ) -> (
1138 Vec<Fr>,
1139 Nova<Projective, GVar, Projective2, GVar2, CubicFCircuit<Fr>, CS1, CS2, H>,
1140 ) {
1141 let mut rng = ark_std::test_rng();
1142
1143 let prep_param =
1144 PreprocessorParam::<Projective, Projective2, CubicFCircuit<Fr>, CS1, CS2, H> {
1145 poseidon_config,
1146 F: F_circuit,
1147 cs_pp: None,
1148 cs_vp: None,
1149 cf_cs_pp: None,
1150 cf_cs_vp: None,
1151 };
1152 let nova_params = Nova::<
1153 Projective,
1154 GVar,
1155 Projective2,
1156 GVar2,
1157 CubicFCircuit<Fr>,
1158 CS1,
1159 CS2,
1160 H,
1161 >::preprocess(&mut rng, &prep_param)
1162 .unwrap();
1163
1164 let z_0 = vec![Fr::from(3_u32)];
1165 let mut nova =
1166 Nova::<Projective, GVar, Projective2, GVar2, CubicFCircuit<Fr>, CS1, CS2, H>::init(
1167 &nova_params,
1168 F_circuit,
1169 z_0.clone(),
1170 )
1171 .unwrap();
1172
1173 for _ in 0..num_steps {
1174 nova.prove_step(&mut rng, vec![], None).unwrap();
1175 }
1176 assert_eq!(Fr::from(num_steps as u32), nova.i);
1177
1178 let (running_instance, incoming_instance, cyclefold_instance) = nova.instances();
1179 Nova::<Projective, GVar, Projective2, GVar2, CubicFCircuit<Fr>, CS1, CS2, H>::verify(
1180 nova_params.1, // Nova's verifier params
1181 z_0.clone(),
1182 nova.z_i.clone(),
1183 nova.i,
1184 running_instance,
1185 incoming_instance,
1186 cyclefold_instance,
1187 )
1188 .unwrap();
1189
1190 (z_0, nova)
1191 }
1192}