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