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