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::{groups::GroupOpsBounds, 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 for<'a> &'a GC1: GroupOpsBounds<'a, C1, GC1>,
297 for<'a> &'a GC2: GroupOpsBounds<'a, C2, GC2>,
298{
299 type RunningInstance = (LCCCS<C1>, Witness<C1::ScalarField>);
300 type IncomingInstance = (CCCS<C1>, Witness<C1::ScalarField>);
301 type MultiInstance = (Vec<Self::RunningInstance>, Vec<Self::IncomingInstance>);
302
303 /// Creates a new LCCS instance for the given state, which satisfies the HyperNova.CCS. This
304 /// method can be used to generate the 'other' LCCS instances to be folded in the multi-folding
305 /// step.
306 fn new_running_instance(
307 &self,
308 mut rng: impl RngCore,
309 state: Vec<C1::ScalarField>,
310 external_inputs: Vec<C1::ScalarField>,
311 ) -> Result<Self::RunningInstance, Error> {
312 let r1cs_z = self.new_instance_generic(state, external_inputs)?;
313 // compute committed instances, w_{i+1}, u_{i+1}, which will be used as w_i, u_i, so we
314 // assign them directly to w_i, u_i.
315 let (U_i, W_i) = self
316 .ccs
317 .to_lcccs::<_, _, CS1, H>(&mut rng, &self.cs_pp, &r1cs_z)?;
318
319 #[cfg(test)]
320 self.ccs.check_relation(&W_i, &U_i)?;
321
322 Ok((U_i, W_i))
323 }
324
325 /// Creates a new CCCS instance for the given state, which satisfies the HyperNova.CCS. This
326 /// method can be used to generate the 'other' CCCS instances to be folded in the multi-folding
327 /// step.
328 fn new_incoming_instance(
329 &self,
330 mut rng: impl RngCore,
331 state: Vec<C1::ScalarField>,
332 external_inputs: Vec<C1::ScalarField>,
333 ) -> Result<Self::IncomingInstance, Error> {
334 let r1cs_z = self.new_instance_generic(state, external_inputs)?;
335 // compute committed instances, w_{i+1}, u_{i+1}, which will be used as w_i, u_i, so we
336 // assign them directly to w_i, u_i.
337 let (u_i, w_i) = self
338 .ccs
339 .to_cccs::<_, _, CS1, H>(&mut rng, &self.cs_pp, &r1cs_z)?;
340
341 #[cfg(test)]
342 self.ccs.check_relation(&w_i, &u_i)?;
343
344 Ok((u_i, w_i))
345 }
346}
347
348impl<C1, GC1, C2, GC2, FC, CS1, CS2, const MU: usize, const NU: usize, const H: bool>
349 HyperNova<C1, GC1, C2, GC2, FC, CS1, CS2, MU, NU, H>
350where
351 C1: CurveGroup,
352 GC1: CurveVar<C1, CF2<C1>> + ToConstraintFieldGadget<CF2<C1>>,
353 C2: CurveGroup,
354 GC2: CurveVar<C2, CF2<C2>> + ToConstraintFieldGadget<CF2<C2>>,
355 FC: FCircuit<C1::ScalarField>,
356 CS1: CommitmentScheme<C1, H>,
357 CS2: CommitmentScheme<C2, H>,
358 <C1 as CurveGroup>::BaseField: PrimeField,
359 <C2 as CurveGroup>::BaseField: PrimeField,
360 <C1 as Group>::ScalarField: Absorb,
361 <C2 as Group>::ScalarField: Absorb,
362 C1: CurveGroup<BaseField = C2::ScalarField, ScalarField = C2::BaseField>,
363 for<'a> &'a GC1: GroupOpsBounds<'a, C1, GC1>,
364 for<'a> &'a GC2: GroupOpsBounds<'a, C2, GC2>,
365{
366 /// internal helper for new_running_instance & new_incoming_instance methods, returns the R1CS
367 /// z=[u,x,w] vector to be used to create the LCCCS & CCCS fresh instances.
368 fn new_instance_generic(
369 &self,
370 state: Vec<C1::ScalarField>,
371 external_inputs: Vec<C1::ScalarField>,
372 ) -> Result<Vec<C1::ScalarField>, Error> {
373 // prepare the initial dummy instances
374 let U_i = LCCCS::<C1>::dummy(&self.ccs);
375 let mut u_i = CCCS::<C1>::dummy(&self.ccs);
376 let (_, cf_U_i): (CycleFoldWitness<C2>, CycleFoldCommittedInstance<C2>) =
377 self.cf_r1cs.dummy_witness_instance();
378
379 let sponge = PoseidonSponge::<C1::ScalarField>::new(&self.poseidon_config);
380
381 u_i.x = vec![
382 U_i.hash(
383 &sponge,
384 self.pp_hash,
385 C1::ScalarField::zero(), // i
386 &self.z_0,
387 &state,
388 ),
389 cf_U_i.hash_cyclefold(&sponge, self.pp_hash),
390 ];
391 let us = vec![u_i.clone(); NU - 1];
392
393 let z_i1 = self
394 .F
395 .step_native(0, state.clone(), external_inputs.clone())?;
396
397 // compute u_{i+1}.x
398 let U_i1 = LCCCS::dummy(&self.ccs);
399 let u_i1_x = U_i1.hash(
400 &sponge,
401 self.pp_hash,
402 C1::ScalarField::one(), // i+1, where i=0
403 &self.z_0,
404 &z_i1,
405 );
406
407 let cf_u_i1_x = cf_U_i.hash_cyclefold(&sponge, self.pp_hash);
408 let augmented_f_circuit = AugmentedFCircuit::<C1, C2, GC2, FC, MU, NU> {
409 _c2: PhantomData,
410 _gc2: PhantomData,
411 poseidon_config: self.poseidon_config.clone(),
412 ccs: self.ccs.clone(),
413 pp_hash: Some(self.pp_hash),
414 i: Some(C1::ScalarField::zero()),
415 i_usize: Some(0),
416 z_0: Some(self.z_0.clone()),
417 z_i: Some(state.clone()),
418 external_inputs: Some(external_inputs),
419 U_i: Some(U_i.clone()),
420 Us: None,
421 u_i_C: Some(u_i.C),
422 us: Some(us),
423 U_i1_C: Some(U_i1.C),
424 F: self.F.clone(),
425 x: Some(u_i1_x),
426 nimfs_proof: None,
427
428 // cyclefold values
429 cf_u_i_cmW: None,
430 cf_U_i: None,
431 cf_x: Some(cf_u_i1_x),
432 cf_cmT: None,
433 };
434
435 let (cs, _) = augmented_f_circuit.compute_cs_ccs()?;
436
437 #[cfg(test)]
438 assert!(cs.is_satisfied()?);
439
440 let (r1cs_w_i1, r1cs_x_i1) = extract_w_x::<C1::ScalarField>(&cs); // includes 1 and public inputs
441
442 #[cfg(test)]
443 assert_eq!(r1cs_x_i1[0], augmented_f_circuit.x.unwrap());
444
445 let r1cs_z = [
446 vec![C1::ScalarField::one()],
447 r1cs_x_i1.clone(),
448 r1cs_w_i1.clone(),
449 ]
450 .concat();
451 Ok(r1cs_z)
452 }
453}
454
455impl<C1, GC1, C2, GC2, FC, CS1, CS2, const MU: usize, const NU: usize, const H: bool>
456 FoldingScheme<C1, C2, FC> for HyperNova<C1, GC1, C2, GC2, FC, CS1, CS2, MU, NU, H>
457where
458 C1: CurveGroup,
459 GC1: CurveVar<C1, CF2<C1>> + ToConstraintFieldGadget<CF2<C1>>,
460 C2: CurveGroup,
461 GC2: CurveVar<C2, CF2<C2>> + ToConstraintFieldGadget<CF2<C2>>,
462 FC: FCircuit<C1::ScalarField>,
463 CS1: CommitmentScheme<C1, H>,
464 CS2: CommitmentScheme<C2, H>,
465 <C1 as CurveGroup>::BaseField: PrimeField,
466 <C2 as CurveGroup>::BaseField: PrimeField,
467 <C1 as Group>::ScalarField: Absorb,
468 <C2 as Group>::ScalarField: Absorb,
469 C1: CurveGroup<BaseField = C2::ScalarField, ScalarField = C2::BaseField>,
470 for<'a> &'a GC1: GroupOpsBounds<'a, C1, GC1>,
471 for<'a> &'a GC2: GroupOpsBounds<'a, C2, GC2>,
472{
473 /// Reuse Nova's PreprocessorParam.
474 type PreprocessorParam = PreprocessorParam<C1, C2, FC, CS1, CS2, H>;
475 type ProverParam = ProverParams<C1, C2, CS1, CS2, H>;
476 type VerifierParam = VerifierParams<C1, C2, CS1, CS2, H>;
477 type RunningInstance = (LCCCS<C1>, Witness<C1::ScalarField>);
478 type IncomingInstance = (CCCS<C1>, Witness<C1::ScalarField>);
479 type MultiCommittedInstanceWithWitness =
480 (Vec<Self::RunningInstance>, Vec<Self::IncomingInstance>);
481 type CFInstance = (CycleFoldCommittedInstance<C2>, CycleFoldWitness<C2>);
482 type IVCProof = IVCProof<C1, C2>;
483
484 fn pp_deserialize_with_mode<R: std::io::prelude::Read>(
485 mut reader: R,
486 compress: ark_serialize::Compress,
487 validate: ark_serialize::Validate,
488 fc_params: FC::Params,
489 ) -> Result<Self::ProverParam, Error> {
490 let poseidon_config = poseidon_canonical_config::<C1::ScalarField>();
491
492 // generate the r1cs & cf_r1cs needed for the VerifierParams. In this way we avoid needing
493 // to serialize them, saving significant space in the VerifierParams serialized size.
494
495 // main circuit R1CS:
496 let f_circuit = FC::new(fc_params)?;
497 let augmented_F_circuit = AugmentedFCircuit::<C1, C2, GC2, FC, MU, NU>::empty(
498 &poseidon_config,
499 f_circuit.clone(),
500 None,
501 )?;
502 let ccs = augmented_F_circuit.ccs;
503
504 let cs_pp = CS1::ProverParams::deserialize_with_mode(&mut reader, compress, validate)?;
505 let cf_cs_pp = CS2::ProverParams::deserialize_with_mode(&mut reader, compress, validate)?;
506
507 Ok(ProverParams {
508 poseidon_config,
509 cs_pp,
510 cf_cs_pp,
511 ccs: Some(ccs),
512 })
513 }
514
515 fn vp_deserialize_with_mode<R: std::io::prelude::Read>(
516 mut reader: R,
517 compress: ark_serialize::Compress,
518 validate: ark_serialize::Validate,
519 fc_params: FC::Params,
520 ) -> Result<Self::VerifierParam, Error> {
521 let poseidon_config = poseidon_canonical_config::<C1::ScalarField>();
522
523 // generate the r1cs & cf_r1cs needed for the VerifierParams. In this way we avoid needing
524 // to serialize them, saving significant space in the VerifierParams serialized size.
525
526 // main circuit R1CS:
527 let f_circuit = FC::new(fc_params)?;
528 let augmented_F_circuit = AugmentedFCircuit::<C1, C2, GC2, FC, MU, NU>::empty(
529 &poseidon_config,
530 f_circuit.clone(),
531 None,
532 )?;
533 let ccs = augmented_F_circuit.ccs;
534
535 // CycleFold circuit R1CS
536 let cf_circuit = HyperNovaCycleFoldCircuit::<C1, GC1, MU, NU>::empty();
537 let cf_r1cs = get_r1cs_from_cs::<C2::ScalarField>(cf_circuit)?;
538
539 let cs_vp = CS1::VerifierParams::deserialize_with_mode(&mut reader, compress, validate)?;
540 let cf_cs_vp = CS2::VerifierParams::deserialize_with_mode(&mut reader, compress, validate)?;
541
542 Ok(VerifierParams {
543 poseidon_config,
544 ccs,
545 cf_r1cs,
546 cs_vp,
547 cf_cs_vp,
548 })
549 }
550
551 fn preprocess(
552 mut rng: impl RngCore,
553 prep_param: &Self::PreprocessorParam,
554 ) -> Result<(Self::ProverParam, Self::VerifierParam), Error> {
555 if MU < 1 || NU < 1 {
556 return Err(Error::CantBeZero("mu,nu".to_string()));
557 }
558
559 let augmented_f_circuit = AugmentedFCircuit::<C1, C2, GC2, FC, MU, NU>::empty(
560 &prep_param.poseidon_config,
561 prep_param.F.clone(),
562 None,
563 )?;
564 let ccs = augmented_f_circuit.ccs.clone();
565
566 let cf_circuit = HyperNovaCycleFoldCircuit::<C1, GC1, MU, NU>::empty();
567 let cf_r1cs = get_r1cs_from_cs::<C2::ScalarField>(cf_circuit)?;
568
569 // if cs params exist, use them, if not, generate new ones
570 let cs_pp: CS1::ProverParams;
571 let cs_vp: CS1::VerifierParams;
572 let cf_cs_pp: CS2::ProverParams;
573 let cf_cs_vp: CS2::VerifierParams;
574 if prep_param.cs_pp.is_some()
575 && prep_param.cf_cs_pp.is_some()
576 && prep_param.cs_vp.is_some()
577 && prep_param.cf_cs_vp.is_some()
578 {
579 cs_pp = prep_param.clone().cs_pp.unwrap();
580 cs_vp = prep_param.clone().cs_vp.unwrap();
581 cf_cs_pp = prep_param.clone().cf_cs_pp.unwrap();
582 cf_cs_vp = prep_param.clone().cf_cs_vp.unwrap();
583 } else {
584 (cs_pp, cs_vp) = CS1::setup(&mut rng, ccs.n - ccs.l - 1)?;
585 (cf_cs_pp, cf_cs_vp) = CS2::setup(&mut rng, cf_r1cs.A.n_cols - cf_r1cs.l - 1)?;
586 }
587
588 let pp = ProverParams::<C1, C2, CS1, CS2, H> {
589 poseidon_config: prep_param.poseidon_config.clone(),
590 cs_pp,
591 cf_cs_pp,
592 ccs: Some(ccs.clone()),
593 };
594 let vp = VerifierParams::<C1, C2, CS1, CS2, H> {
595 poseidon_config: prep_param.poseidon_config.clone(),
596 ccs,
597 cf_r1cs,
598 cs_vp: cs_vp.clone(),
599 cf_cs_vp: cf_cs_vp.clone(),
600 };
601 Ok((pp, vp))
602 }
603
604 /// Initializes the HyperNova+CycleFold's IVC for the given parameters and initial state `z_0`.
605 fn init(
606 params: &(Self::ProverParam, Self::VerifierParam),
607 F: FC,
608 z_0: Vec<C1::ScalarField>,
609 ) -> Result<Self, Error> {
610 let (pp, vp) = params;
611 if MU < 1 || NU < 1 {
612 return Err(Error::CantBeZero("mu,nu".to_string()));
613 }
614
615 // `sponge` is for digest computation.
616 let sponge = PoseidonSponge::<C1::ScalarField>::new(&pp.poseidon_config);
617
618 // prepare the HyperNova's AugmentedFCircuit and CycleFold's circuits and obtain its CCS
619 // and R1CS respectively
620 let augmented_f_circuit = AugmentedFCircuit::<C1, C2, GC2, FC, MU, NU>::empty(
621 &pp.poseidon_config,
622 F.clone(),
623 pp.ccs.clone(),
624 )?;
625 let ccs = augmented_f_circuit.ccs.clone();
626
627 let cf_circuit = HyperNovaCycleFoldCircuit::<C1, GC1, MU, NU>::empty();
628 let cf_r1cs = get_r1cs_from_cs::<C2::ScalarField>(cf_circuit)?;
629
630 // compute the public params hash
631 let pp_hash = vp.pp_hash()?;
632
633 // setup the dummy instances
634 let W_dummy = Witness::<C1::ScalarField>::dummy(&ccs);
635 let U_dummy = LCCCS::<C1>::dummy(&ccs);
636 let w_dummy = W_dummy.clone();
637 let mut u_dummy = CCCS::<C1>::dummy(&ccs);
638 let (cf_W_dummy, cf_U_dummy): (CycleFoldWitness<C2>, CycleFoldCommittedInstance<C2>) =
639 cf_r1cs.dummy_witness_instance();
640 u_dummy.x = vec![
641 U_dummy.hash(&sponge, pp_hash, C1::ScalarField::zero(), &z_0, &z_0),
642 cf_U_dummy.hash_cyclefold(&sponge, pp_hash),
643 ];
644
645 // W_dummy=W_0 is a 'dummy witness', all zeroes, but with the size corresponding to the
646 // R1CS that we're working with.
647 Ok(Self {
648 _gc1: PhantomData,
649 _c2: PhantomData,
650 _gc2: PhantomData,
651 ccs,
652 cf_r1cs,
653 poseidon_config: pp.poseidon_config.clone(),
654 cs_pp: pp.cs_pp.clone(),
655 cf_cs_pp: pp.cf_cs_pp.clone(),
656 F,
657 pp_hash,
658 i: C1::ScalarField::zero(),
659 z_0: z_0.clone(),
660 z_i: z_0,
661 W_i: W_dummy,
662 U_i: U_dummy,
663 w_i: w_dummy,
664 u_i: u_dummy,
665 // cyclefold running instance
666 cf_W_i: cf_W_dummy,
667 cf_U_i: cf_U_dummy,
668 })
669 }
670
671 /// Implements IVC.P of HyperNova+CycleFold
672 fn prove_step(
673 &mut self,
674 mut rng: impl RngCore,
675 external_inputs: Vec<C1::ScalarField>,
676 other_instances: Option<Self::MultiCommittedInstanceWithWitness>,
677 ) -> Result<(), Error> {
678 // ensure that commitments are blinding if user has specified so.
679
680 if H {
681 let blinding_commitments = if self.i == C1::ScalarField::zero() {
682 vec![self.w_i.r_w]
683 } else {
684 vec![self.w_i.r_w, self.W_i.r_w]
685 };
686 if blinding_commitments.contains(&C1::ScalarField::zero()) {
687 return Err(Error::IncorrectBlinding(
688 H,
689 format!("{:?}", blinding_commitments),
690 ));
691 }
692 }
693
694 // `sponge` is for digest computation.
695 let sponge = PoseidonSponge::<C1::ScalarField>::new(&self.poseidon_config);
696
697 let (Us, Ws, us, ws) = if MU > 1 || NU > 1 {
698 let other_instances = other_instances.ok_or(Error::MissingOtherInstances(MU, NU))?;
699
700 #[allow(clippy::type_complexity)]
701 let (lcccs, cccs): (
702 Vec<(LCCCS<C1>, Witness<C1::ScalarField>)>,
703 Vec<(CCCS<C1>, Witness<C1::ScalarField>)>,
704 ) = other_instances;
705
706 // recall, mu & nu is the number of all the LCCCS & CCCS respectively, including the
707 // running and incoming instances that are not part of the 'other_instances', hence the +1
708 // in the couple of following checks.
709 if lcccs.len() + 1 != MU {
710 return Err(Error::NotSameLength(
711 "other_instances.lcccs.len()".to_string(),
712 lcccs.len(),
713 "hypernova.mu".to_string(),
714 MU,
715 ));
716 }
717 if cccs.len() + 1 != NU {
718 return Err(Error::NotSameLength(
719 "other_instances.cccs.len()".to_string(),
720 cccs.len(),
721 "hypernova.nu".to_string(),
722 NU,
723 ));
724 }
725
726 let (Us, Ws): (Vec<LCCCS<C1>>, Vec<Witness<C1::ScalarField>>) =
727 lcccs.into_iter().unzip();
728 let (us, ws): (Vec<CCCS<C1>>, Vec<Witness<C1::ScalarField>>) = cccs.into_iter().unzip();
729 (Some(Us), Some(Ws), Some(us), Some(ws))
730 } else {
731 (None, None, None, None)
732 };
733
734 let augmented_f_circuit: AugmentedFCircuit<C1, C2, GC2, FC, MU, NU>;
735
736 if self.z_i.len() != self.F.state_len() {
737 return Err(Error::NotSameLength(
738 "z_i.len()".to_string(),
739 self.z_i.len(),
740 "F.state_len()".to_string(),
741 self.F.state_len(),
742 ));
743 }
744 if external_inputs.len() != self.F.external_inputs_len() {
745 return Err(Error::NotSameLength(
746 "F.external_inputs_len()".to_string(),
747 self.F.external_inputs_len(),
748 "external_inputs.len()".to_string(),
749 external_inputs.len(),
750 ));
751 }
752
753 if self.i > C1::ScalarField::from_le_bytes_mod_order(&usize::MAX.to_le_bytes()) {
754 return Err(Error::MaxStep);
755 }
756
757 let i_usize;
758
759 #[cfg(target_pointer_width = "64")]
760 {
761 let mut i_bytes: [u8; 8] = [0; 8];
762 i_bytes.copy_from_slice(&self.i.into_bigint().to_bytes_le()[..8]);
763 i_usize = usize::from_le_bytes(i_bytes);
764 }
765
766 #[cfg(target_pointer_width = "32")]
767 {
768 let mut i_bytes: [u8; 4] = [0; 4];
769 i_bytes.copy_from_slice(&self.i.into_bigint().to_bytes_le()[..4]);
770 i_usize = usize::from_le_bytes(i_bytes);
771 }
772
773 let z_i1 = self
774 .F
775 .step_native(i_usize, self.z_i.clone(), external_inputs.clone())?;
776
777 // u_{i+1}.x[1] = H(cf_U_{i+1})
778 let cf_u_i1_x: C1::ScalarField;
779 let (U_i1, mut W_i1);
780
781 if self.i == C1::ScalarField::zero() {
782 W_i1 = Witness::<C1::ScalarField>::dummy(&self.ccs);
783 W_i1.r_w = self.W_i.r_w;
784 U_i1 = LCCCS::dummy(&self.ccs);
785
786 let u_i1_x = U_i1.hash(
787 &sponge,
788 self.pp_hash,
789 C1::ScalarField::one(),
790 &self.z_0,
791 &z_i1,
792 );
793
794 // hash the initial (dummy) CycleFold instance, which is used as the 2nd public
795 // input in the AugmentedFCircuit
796 cf_u_i1_x = self.cf_U_i.hash_cyclefold(&sponge, self.pp_hash);
797
798 augmented_f_circuit = AugmentedFCircuit::<C1, C2, GC2, FC, MU, NU> {
799 _c2: PhantomData,
800 _gc2: PhantomData,
801 poseidon_config: self.poseidon_config.clone(),
802 ccs: self.ccs.clone(),
803 pp_hash: Some(self.pp_hash),
804 i: Some(C1::ScalarField::zero()),
805 i_usize: Some(0),
806 z_0: Some(self.z_0.clone()),
807 z_i: Some(self.z_i.clone()),
808 external_inputs: Some(external_inputs.clone()),
809 U_i: Some(self.U_i.clone()),
810 Us: Us.clone(),
811 u_i_C: Some(self.u_i.C),
812 us: us.clone(),
813 U_i1_C: Some(U_i1.C),
814 F: self.F.clone(),
815 x: Some(u_i1_x),
816 nimfs_proof: None,
817
818 // cyclefold values
819 cf_u_i_cmW: None,
820 cf_U_i: None,
821 cf_x: Some(cf_u_i1_x),
822 cf_cmT: None,
823 };
824 } else {
825 let mut transcript_p: PoseidonSponge<C1::ScalarField> =
826 PoseidonSponge::<C1::ScalarField>::new(&self.poseidon_config);
827 transcript_p.absorb(&self.pp_hash);
828
829 let (all_Us, all_us, all_Ws, all_ws) = if MU > 1 || NU > 1 {
830 (
831 [vec![self.U_i.clone()], Us.clone().unwrap()].concat(),
832 [vec![self.u_i.clone()], us.clone().unwrap()].concat(),
833 [vec![self.W_i.clone()], Ws.unwrap()].concat(),
834 [vec![self.w_i.clone()], ws.unwrap()].concat(),
835 )
836 } else {
837 (
838 vec![self.U_i.clone()],
839 vec![self.u_i.clone()],
840 vec![self.W_i.clone()],
841 vec![self.w_i.clone()],
842 )
843 };
844
845 let (rho, nimfs_proof);
846 (nimfs_proof, U_i1, W_i1, rho) = NIMFS::<C1, PoseidonSponge<C1::ScalarField>>::prove(
847 &mut transcript_p,
848 &self.ccs,
849 &all_Us,
850 &all_us,
851 &all_Ws,
852 &all_ws,
853 )?;
854
855 // sanity check: check the folded instance relation
856 #[cfg(test)]
857 self.ccs.check_relation(&W_i1, &U_i1)?;
858
859 let u_i1_x = U_i1.hash(
860 &sponge,
861 self.pp_hash,
862 self.i + C1::ScalarField::one(),
863 &self.z_0,
864 &z_i1,
865 );
866
867 let rho_bits = rho.into_bigint().to_bits_le()[..NOVA_N_BITS_RO].to_vec();
868 let rho_Fq = C1::BaseField::from_bigint(BigInteger::from_bits_le(&rho_bits)).unwrap();
869
870 // CycleFold part:
871 // get the vector used as public inputs 'x' in the CycleFold circuit.
872 // Place the random values and the points coordinates as the public input x:
873 // In Nova, this is: x == [r, p1, p2, p3].
874 // In multifolding schemes such as HyperNova, this is:
875 // computed_x = [r, p_0, p_1, p_2, ..., p_n],
876 // where each p_i is in fact p_i.to_constraint_field()
877 let cf_u_i_x = [
878 vec![rho_Fq],
879 all_Us
880 .iter()
881 .flat_map(|Us_i| get_cm_coordinates(&Us_i.C))
882 .collect(),
883 all_us
884 .iter()
885 .flat_map(|us_i| get_cm_coordinates(&us_i.C))
886 .collect(),
887 get_cm_coordinates(&U_i1.C),
888 ]
889 .concat();
890
891 let cf_circuit = HyperNovaCycleFoldCircuit::<C1, GC1, MU, NU> {
892 _gc: PhantomData,
893 r_bits: Some(rho_bits.clone()),
894 points: Some(
895 [
896 all_Us.iter().map(|Us_i| Us_i.C).collect::<Vec<_>>(),
897 all_us.iter().map(|us_i| us_i.C).collect::<Vec<_>>(),
898 ]
899 .concat(),
900 ),
901 x: Some(cf_u_i_x.clone()),
902 };
903
904 let (_cf_w_i, cf_u_i, cf_W_i1, cf_U_i1, cf_cmT, _) = fold_cyclefold_circuit::<
905 HyperNovaCycleFoldConfig<C1, MU, NU>,
906 C1,
907 GC1,
908 C2,
909 GC2,
910 CS2,
911 H,
912 >(
913 &mut transcript_p,
914 self.cf_r1cs.clone(),
915 self.cf_cs_pp.clone(),
916 self.pp_hash,
917 self.cf_W_i.clone(), // CycleFold running instance witness
918 self.cf_U_i.clone(), // CycleFold running instance
919 cf_u_i_x,
920 cf_circuit,
921 &mut rng,
922 )?;
923
924 cf_u_i1_x = cf_U_i1.hash_cyclefold(&sponge, self.pp_hash);
925
926 augmented_f_circuit = AugmentedFCircuit::<C1, C2, GC2, FC, MU, NU> {
927 _c2: PhantomData,
928 _gc2: PhantomData,
929 poseidon_config: self.poseidon_config.clone(),
930 ccs: self.ccs.clone(),
931 pp_hash: Some(self.pp_hash),
932 i: Some(self.i),
933 i_usize: Some(i_usize),
934 z_0: Some(self.z_0.clone()),
935 z_i: Some(self.z_i.clone()),
936 external_inputs: Some(external_inputs),
937 U_i: Some(self.U_i.clone()),
938 Us: Us.clone(),
939 u_i_C: Some(self.u_i.C),
940 us: us.clone(),
941 U_i1_C: Some(U_i1.C),
942 F: self.F.clone(),
943 x: Some(u_i1_x),
944 nimfs_proof: Some(nimfs_proof),
945
946 // cyclefold values
947 cf_u_i_cmW: Some(cf_u_i.cmW),
948 cf_U_i: Some(self.cf_U_i.clone()),
949 cf_x: Some(cf_u_i1_x),
950 cf_cmT: Some(cf_cmT),
951 };
952
953 // assign the next round instances
954 self.cf_W_i = cf_W_i1;
955 self.cf_U_i = cf_U_i1;
956 }
957
958 let (cs, _) = augmented_f_circuit.compute_cs_ccs()?;
959
960 #[cfg(test)]
961 assert!(cs.is_satisfied()?);
962
963 let (r1cs_w_i1, r1cs_x_i1) = extract_w_x::<C1::ScalarField>(&cs); // includes 1 and public inputs
964
965 let r1cs_z = [
966 vec![C1::ScalarField::one()],
967 r1cs_x_i1.clone(),
968 r1cs_w_i1.clone(),
969 ]
970 .concat();
971 // compute committed instances, w_{i+1}, u_{i+1}, which will be used as w_i, u_i, so we
972 // assign them directly to w_i, u_i.
973 let (u_i, w_i) = self
974 .ccs
975 .to_cccs::<_, C1, CS1, H>(&mut rng, &self.cs_pp, &r1cs_z)?;
976 self.u_i = u_i.clone();
977 self.w_i = w_i.clone();
978
979 // set values for next iteration
980 self.i += C1::ScalarField::one();
981 // assign z_{i+1} into z_i
982 self.z_i = z_i1.clone();
983 self.U_i = U_i1.clone();
984 self.W_i = W_i1.clone();
985
986 #[cfg(test)]
987 {
988 // check the new LCCCS instance relation
989 self.ccs.check_relation(&self.W_i, &self.U_i)?;
990 // check the new CCCS instance relation
991 self.ccs.check_relation(&self.w_i, &self.u_i)?;
992 }
993
994 Ok(())
995 }
996
997 fn state(&self) -> Vec<C1::ScalarField> {
998 self.z_i.clone()
999 }
1000
1001 fn ivc_proof(&self) -> Self::IVCProof {
1002 Self::IVCProof {
1003 i: self.i,
1004 z_0: self.z_0.clone(),
1005 z_i: self.z_i.clone(),
1006 W_i: self.W_i.clone(),
1007 U_i: self.U_i.clone(),
1008 w_i: self.w_i.clone(),
1009 u_i: self.u_i.clone(),
1010 cf_W_i: self.cf_W_i.clone(),
1011 cf_U_i: self.cf_U_i.clone(),
1012 }
1013 }
1014
1015 fn from_ivc_proof(
1016 ivc_proof: Self::IVCProof,
1017 fcircuit_params: FC::Params,
1018 params: (Self::ProverParam, Self::VerifierParam),
1019 ) -> Result<Self, Error> {
1020 let IVCProof {
1021 i,
1022 z_0,
1023 z_i,
1024 W_i,
1025 U_i,
1026 w_i,
1027 u_i,
1028 cf_W_i,
1029 cf_U_i,
1030 } = ivc_proof;
1031 let (pp, vp) = params;
1032
1033 let f_circuit = FC::new(fcircuit_params).unwrap();
1034 let augmented_f_circuit = AugmentedFCircuit::<C1, C2, GC2, FC, MU, NU>::empty(
1035 &pp.poseidon_config,
1036 f_circuit.clone(),
1037 None,
1038 )?;
1039 let cf_circuit = HyperNovaCycleFoldCircuit::<C1, GC1, MU, NU>::empty();
1040
1041 let ccs = augmented_f_circuit.ccs.clone();
1042 let cf_r1cs = get_r1cs_from_cs::<C2::ScalarField>(cf_circuit)?;
1043
1044 Ok(Self {
1045 _gc1: PhantomData,
1046 _c2: PhantomData,
1047 _gc2: PhantomData,
1048 ccs,
1049 cf_r1cs,
1050 poseidon_config: pp.poseidon_config,
1051 cs_pp: pp.cs_pp,
1052 cf_cs_pp: pp.cf_cs_pp,
1053 F: f_circuit,
1054 pp_hash: vp.pp_hash()?,
1055 i,
1056 z_0,
1057 z_i,
1058 w_i,
1059 u_i,
1060 W_i,
1061 U_i,
1062 cf_W_i,
1063 cf_U_i,
1064 })
1065 }
1066
1067 /// Implements IVC.V of Hyp.clone()erNova+CycleFold. Notice that this method does not include the
1068 /// commitments verification, which is done in the Decider.
1069 fn verify(vp: Self::VerifierParam, ivc_proof: Self::IVCProof) -> Result<(), Error> {
1070 let Self::IVCProof {
1071 i: num_steps,
1072 z_0,
1073 z_i,
1074 W_i,
1075 U_i,
1076 w_i,
1077 u_i,
1078 cf_W_i,
1079 cf_U_i,
1080 } = ivc_proof;
1081
1082 if num_steps == C1::ScalarField::zero() {
1083 if z_0 != z_i {
1084 return Err(Error::IVCVerificationFail);
1085 }
1086 return Ok(());
1087 }
1088 // `sponge` is for digest computation.
1089 let sponge = PoseidonSponge::<C1::ScalarField>::new(&vp.poseidon_config);
1090
1091 if u_i.x.len() != 2 || U_i.x.len() != 2 {
1092 return Err(Error::IVCVerificationFail);
1093 }
1094
1095 let pp_hash = vp.pp_hash()?;
1096
1097 // check that u_i's output points to the running instance
1098 // u_i.X[0] == H(i, z_0, z_i, U_i)
1099 let expected_u_i_x = U_i.hash(&sponge, pp_hash, num_steps, &z_0, &z_i);
1100 if expected_u_i_x != u_i.x[0] {
1101 return Err(Error::IVCVerificationFail);
1102 }
1103 // u_i.X[1] == H(cf_U_i)
1104 let expected_cf_u_i_x = cf_U_i.hash_cyclefold(&sponge, pp_hash);
1105 if expected_cf_u_i_x != u_i.x[1] {
1106 return Err(Error::IVCVerificationFail);
1107 }
1108
1109 // check LCCCS satisfiability
1110 vp.ccs.check_relation(&W_i, &U_i)?;
1111 // check CCCS satisfiability
1112 vp.ccs.check_relation(&w_i, &u_i)?;
1113
1114 // check CycleFold's RelaxedR1CS satisfiability
1115 vp.cf_r1cs.check_relation(&cf_W_i, &cf_U_i)?;
1116
1117 Ok(())
1118 }
1119}
1120
1121#[cfg(test)]
1122mod tests {
1123 use crate::commitment::kzg::KZG;
1124 use ark_bn254::{constraints::GVar, Bn254, Fr, G1Projective as Projective};
1125 use ark_grumpkin::{constraints::GVar as GVar2, Projective as Projective2};
1126 use ark_std::UniformRand;
1127
1128 use super::*;
1129 use crate::commitment::pedersen::Pedersen;
1130 use crate::frontend::utils::CubicFCircuit;
1131 use crate::transcript::poseidon::poseidon_canonical_config;
1132
1133 #[test]
1134 pub fn test_ivc() {
1135 let poseidon_config = poseidon_canonical_config::<Fr>();
1136
1137 let F_circuit = CubicFCircuit::<Fr>::new(()).unwrap();
1138
1139 // run the test using Pedersen commitments on both sides of the curve cycle
1140 test_ivc_opt::<Pedersen<Projective>, Pedersen<Projective2>, false>(
1141 poseidon_config.clone(),
1142 F_circuit,
1143 );
1144
1145 test_ivc_opt::<Pedersen<Projective, true>, Pedersen<Projective2, true>, true>(
1146 poseidon_config.clone(),
1147 F_circuit,
1148 );
1149
1150 // run the test using KZG for the commitments on the main curve, and Pedersen for the
1151 // commitments on the secondary curve
1152 test_ivc_opt::<KZG<Bn254>, Pedersen<Projective2>, false>(poseidon_config, F_circuit);
1153 }
1154
1155 #[allow(clippy::type_complexity)]
1156 // test_ivc allowing to choose the CommitmentSchemes
1157 pub fn test_ivc_opt<
1158 CS1: CommitmentScheme<Projective, H>,
1159 CS2: CommitmentScheme<Projective2, H>,
1160 const H: bool,
1161 >(
1162 poseidon_config: PoseidonConfig<Fr>,
1163 F_circuit: CubicFCircuit<Fr>,
1164 ) {
1165 let mut rng = ark_std::test_rng();
1166
1167 const MU: usize = 2;
1168 const NU: usize = 3;
1169
1170 type HN<CS1, CS2, const H: bool> =
1171 HyperNova<Projective, GVar, Projective2, GVar2, CubicFCircuit<Fr>, CS1, CS2, MU, NU, H>;
1172
1173 let prep_param =
1174 PreprocessorParam::<Projective, Projective2, CubicFCircuit<Fr>, CS1, CS2, H>::new(
1175 poseidon_config.clone(),
1176 F_circuit,
1177 );
1178 let hypernova_params = HN::preprocess(&mut rng, &prep_param).unwrap();
1179
1180 let z_0 = vec![Fr::from(3_u32)];
1181 let mut hypernova = HN::init(&hypernova_params, F_circuit, z_0.clone()).unwrap();
1182
1183 let (w_i_blinding, W_i_blinding) = if H {
1184 (Fr::rand(&mut rng), Fr::rand(&mut rng))
1185 } else {
1186 (Fr::zero(), Fr::zero())
1187 };
1188 hypernova.w_i.r_w = w_i_blinding;
1189 hypernova.W_i.r_w = W_i_blinding;
1190
1191 let num_steps: usize = 3;
1192 for _ in 0..num_steps {
1193 // prepare some new instances to fold in the multifolding step
1194 let mut lcccs = vec![];
1195 for j in 0..MU - 1 {
1196 let instance_state = vec![Fr::from(j as u32 + 85_u32)];
1197 let (U, W) = hypernova
1198 .new_running_instance(&mut rng, instance_state, vec![])
1199 .unwrap();
1200 lcccs.push((U, W));
1201 }
1202 let mut cccs = vec![];
1203 for j in 0..NU - 1 {
1204 let instance_state = vec![Fr::from(j as u32 + 15_u32)];
1205 let (u, w) = hypernova
1206 .new_incoming_instance(&mut rng, instance_state, vec![])
1207 .unwrap();
1208 cccs.push((u, w));
1209 }
1210
1211 hypernova
1212 .prove_step(&mut rng, vec![], Some((lcccs, cccs)))
1213 .unwrap();
1214 }
1215 assert_eq!(Fr::from(num_steps as u32), hypernova.i);
1216
1217 let ivc_proof = hypernova.ivc_proof();
1218 HN::verify(
1219 hypernova_params.1.clone(), // verifier_params
1220 ivc_proof,
1221 )
1222 .unwrap();
1223 }
1224}