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