sonobe/folding-schemes/src/folding/nova/circuits.rs
folding-schemes/src/folding/nova/circuits.rsRaw
1/// contains [Nova](https://eprint.iacr.org/2021/370.pdf) related circuits
2use ark_crypto_primitives::crh::{
3 poseidon::constraints::{CRHGadget, CRHParametersVar},
4 CRHSchemeGadget,
5};
6use ark_crypto_primitives::sponge::{
7 constraints::CryptographicSpongeVar,
8 poseidon::{constraints::PoseidonSpongeVar, PoseidonConfig, PoseidonSponge},
9 Absorb, CryptographicSponge,
10};
11use ark_ec::{AffineRepr, CurveGroup, Group};
12use ark_ff::{Field, PrimeField};
13use ark_r1cs_std::{
14 alloc::{AllocVar, AllocationMode},
15 boolean::Boolean,
16 eq::EqGadget,
17 fields::{fp::FpVar, nonnative::NonNativeFieldVar, FieldVar},
18 groups::GroupOpsBounds,
19 prelude::CurveVar,
20 ToBitsGadget, ToConstraintFieldGadget,
21};
22use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystemRef, Namespace, SynthesisError};
23use ark_std::fmt::Debug;
24use ark_std::{One, Zero};
25use core::{borrow::Borrow, marker::PhantomData};
26
27use super::{
28 cyclefold::{
29 CycleFoldChallengeGadget, CycleFoldCommittedInstanceVar, NIFSFullGadget, CF_IO_LEN,
30 },
31 CommittedInstance,
32};
33use crate::constants::N_BITS_RO;
34use crate::folding::circuits::nonnative::{point_to_nonnative_limbs, NonNativeAffineVar};
35use crate::frontend::FCircuit;
36
37/// CF1 represents the ConstraintField used for the main Nova circuit which is over E1::Fr, where
38/// E1 is the main curve where we do the folding.
39pub type CF1<C> = <<C as CurveGroup>::Affine as AffineRepr>::ScalarField;
40/// CF2 represents the ConstraintField used for the CycleFold circuit which is over E2::Fr=E1::Fq,
41/// where E2 is the auxiliary curve (from [CycleFold](https://eprint.iacr.org/2023/1192.pdf)
42/// approach) where we check the folding of the commitments (elliptic curve points).
43pub type CF2<C> = <<C as CurveGroup>::BaseField as Field>::BasePrimeField;
44
45/// CommittedInstanceVar contains the u, x, cmE and cmW values which are folded on the main Nova
46/// constraints field (E1::Fr, where E1 is the main curve). The peculiarity is that cmE and cmW are
47/// represented non-natively over the constraint field.
48#[derive(Debug, Clone)]
49pub struct CommittedInstanceVar<C: CurveGroup>
50where
51 <C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField,
52{
53 pub u: FpVar<C::ScalarField>,
54 pub x: Vec<FpVar<C::ScalarField>>,
55 pub cmE: NonNativeAffineVar<C>,
56 pub cmW: NonNativeAffineVar<C>,
57}
58
59impl<C> AllocVar<CommittedInstance<C>, CF1<C>> for CommittedInstanceVar<C>
60where
61 C: CurveGroup,
62 <C as ark_ec::CurveGroup>::BaseField: PrimeField,
63{
64 fn new_variable<T: Borrow<CommittedInstance<C>>>(
65 cs: impl Into<Namespace<CF1<C>>>,
66 f: impl FnOnce() -> Result<T, SynthesisError>,
67 mode: AllocationMode,
68 ) -> Result<Self, SynthesisError> {
69 f().and_then(|val| {
70 let cs = cs.into();
71
72 let u = FpVar::<C::ScalarField>::new_variable(cs.clone(), || Ok(val.borrow().u), mode)?;
73 let x: Vec<FpVar<C::ScalarField>> =
74 Vec::new_variable(cs.clone(), || Ok(val.borrow().x.clone()), mode)?;
75
76 let cmE =
77 NonNativeAffineVar::<C>::new_variable(cs.clone(), || Ok(val.borrow().cmE), mode)?;
78 let cmW =
79 NonNativeAffineVar::<C>::new_variable(cs.clone(), || Ok(val.borrow().cmW), mode)?;
80
81 Ok(Self { u, x, cmE, cmW })
82 })
83 }
84}
85
86impl<C> CommittedInstanceVar<C>
87where
88 C: CurveGroup,
89 <C as Group>::ScalarField: Absorb,
90 <C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField,
91{
92 /// hash implements the committed instance hash compatible with the native implementation from
93 /// CommittedInstance.hash.
94 /// Returns `H(i, z_0, z_i, U_i)`, where `i` can be `i` but also `i+1`, and `U` is the
95 /// `CommittedInstance`.
96 /// Additionally it returns the vector of the field elements from the self parameters, so they
97 /// can be reused in other gadgets avoiding recalculating (reconstraining) them.
98 #[allow(clippy::type_complexity)]
99 pub fn hash(
100 self,
101 crh_params: &CRHParametersVar<CF1<C>>,
102 i: FpVar<CF1<C>>,
103 z_0: Vec<FpVar<CF1<C>>>,
104 z_i: Vec<FpVar<CF1<C>>>,
105 ) -> Result<(FpVar<CF1<C>>, Vec<FpVar<CF1<C>>>), SynthesisError> {
106 let U_vec = [
107 vec![self.u],
108 self.x,
109 self.cmE.x.to_constraint_field()?,
110 self.cmE.y.to_constraint_field()?,
111 self.cmW.x.to_constraint_field()?,
112 self.cmW.y.to_constraint_field()?,
113 ]
114 .concat();
115 let input = [vec![i], z_0, z_i, U_vec.clone()].concat();
116 Ok((
117 CRHGadget::<C::ScalarField>::evaluate(crh_params, &input)?,
118 U_vec,
119 ))
120 }
121}
122
123/// Implements the circuit that does the checks of the Non-Interactive Folding Scheme Verifier
124/// described in section 4 of [Nova](https://eprint.iacr.org/2021/370.pdf), where the cmE & cmW checks are
125/// delegated to the NIFSCycleFoldGadget.
126pub struct NIFSGadget<C: CurveGroup> {
127 _c: PhantomData<C>,
128}
129
130impl<C: CurveGroup> NIFSGadget<C>
131where
132 C: CurveGroup,
133 <C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField,
134{
135 /// Implements the constraints for NIFS.V for u and x, since cm(E) and cm(W) are delegated to
136 /// the CycleFold circuit.
137 pub fn verify(
138 r: FpVar<CF1<C>>,
139 ci1: CommittedInstanceVar<C>, // U_i
140 ci2: CommittedInstanceVar<C>, // u_i
141 ci3: CommittedInstanceVar<C>, // U_{i+1}
142 ) -> Result<Boolean<CF1<C>>, SynthesisError> {
143 // ensure that: ci3.u == ci1.u + r * ci2.u
144 let first_check = ci3.u.is_eq(&(ci1.u + r.clone() * ci2.u))?;
145
146 // ensure that: ci3.x == ci1.x + r * ci2.x
147 let x_rlc = ci1
148 .x
149 .iter()
150 .zip(ci2.x)
151 .map(|(a, b)| a + &r * &b)
152 .collect::<Vec<FpVar<CF1<C>>>>();
153 let second_check = x_rlc.is_eq(&ci3.x)?;
154
155 first_check.and(&second_check)
156 }
157}
158
159/// ChallengeGadget computes the RO challenge used for the Nova instances NIFS, it contains a
160/// rust-native and a in-circuit compatible versions.
161pub struct ChallengeGadget<C: CurveGroup> {
162 _c: PhantomData<C>,
163}
164impl<C: CurveGroup> ChallengeGadget<C>
165where
166 C: CurveGroup,
167 <C as CurveGroup>::BaseField: PrimeField,
168 <C as Group>::ScalarField: Absorb,
169{
170 pub fn get_challenge_native(
171 poseidon_config: &PoseidonConfig<C::ScalarField>,
172 U_i: CommittedInstance<C>,
173 u_i: CommittedInstance<C>,
174 cmT: C,
175 ) -> Result<Vec<bool>, SynthesisError> {
176 let (U_cmE_x, U_cmE_y) = point_to_nonnative_limbs::<C>(U_i.cmE)?;
177 let (U_cmW_x, U_cmW_y) = point_to_nonnative_limbs::<C>(U_i.cmW)?;
178 let (u_cmE_x, u_cmE_y) = point_to_nonnative_limbs::<C>(u_i.cmE)?;
179 let (u_cmW_x, u_cmW_y) = point_to_nonnative_limbs::<C>(u_i.cmW)?;
180 let (cmT_x, cmT_y) = point_to_nonnative_limbs::<C>(cmT)?;
181
182 let mut sponge = PoseidonSponge::<C::ScalarField>::new(poseidon_config);
183 let input = vec![
184 vec![U_i.u],
185 U_i.x.clone(),
186 U_cmE_x,
187 U_cmE_y,
188 U_cmW_x,
189 U_cmW_y,
190 vec![u_i.u],
191 u_i.x.clone(),
192 u_cmE_x,
193 u_cmE_y,
194 u_cmW_x,
195 u_cmW_y,
196 cmT_x,
197 cmT_y,
198 ]
199 .concat();
200 sponge.absorb(&input);
201 let bits = sponge.squeeze_bits(N_BITS_RO);
202 Ok(bits)
203 }
204
205 // compatible with the native get_challenge_native
206 pub fn get_challenge_gadget(
207 cs: ConstraintSystemRef<C::ScalarField>,
208 poseidon_config: &PoseidonConfig<C::ScalarField>,
209 U_i_vec: Vec<FpVar<CF1<C>>>, // apready processed input, so we don't have to recompute these values
210 u_i: CommittedInstanceVar<C>,
211 cmT: NonNativeAffineVar<C>,
212 ) -> Result<Vec<Boolean<C::ScalarField>>, SynthesisError> {
213 let mut sponge = PoseidonSpongeVar::<C::ScalarField>::new(cs, poseidon_config);
214
215 let input: Vec<FpVar<C::ScalarField>> = vec![
216 U_i_vec,
217 vec![u_i.u.clone()],
218 u_i.x.clone(),
219 u_i.cmE.x.to_constraint_field()?,
220 u_i.cmE.y.to_constraint_field()?,
221 u_i.cmW.x.to_constraint_field()?,
222 u_i.cmW.y.to_constraint_field()?,
223 cmT.x.to_constraint_field()?,
224 cmT.y.to_constraint_field()?,
225 ]
226 .concat();
227 sponge.absorb(&input)?;
228 let bits = sponge.squeeze_bits(N_BITS_RO)?;
229 Ok(bits)
230 }
231}
232
233/// AugmentedFCircuit implements the F' circuit (augmented F) defined in
234/// [Nova](https://eprint.iacr.org/2021/370.pdf) together with the extra constraints defined in
235/// [CycleFold](https://eprint.iacr.org/2023/1192.pdf).
236#[derive(Debug, Clone)]
237pub struct AugmentedFCircuit<
238 C1: CurveGroup,
239 C2: CurveGroup,
240 GC2: CurveVar<C2, CF2<C2>>,
241 FC: FCircuit<CF1<C1>>,
242> where
243 for<'a> &'a GC2: GroupOpsBounds<'a, C2, GC2>,
244{
245 pub _gc2: PhantomData<GC2>,
246 pub poseidon_config: PoseidonConfig<CF1<C1>>,
247 pub i: Option<CF1<C1>>,
248 pub i_usize: Option<usize>,
249 pub z_0: Option<Vec<C1::ScalarField>>,
250 pub z_i: Option<Vec<C1::ScalarField>>,
251 pub u_i: Option<CommittedInstance<C1>>,
252 pub U_i: Option<CommittedInstance<C1>>,
253 pub U_i1: Option<CommittedInstance<C1>>,
254 pub r_nonnat: Option<CF2<C1>>,
255 pub cmT: Option<C1>,
256 pub F: FC, // F circuit
257 pub x: Option<CF1<C1>>, // public inputs (u_{i+1}.x)
258
259 // cyclefold verifier on C1
260 // Here 'cf1, cf2' are for each of the CycleFold circuits, corresponding to the fold of cmW and
261 // cmE respectively
262 pub cf1_u_i: Option<CommittedInstance<C2>>, // input
263 pub cf2_u_i: Option<CommittedInstance<C2>>, // input
264 pub cf_U_i: Option<CommittedInstance<C2>>, // input
265 pub cf1_U_i1: Option<CommittedInstance<C2>>, // intermediate
266 pub cf_U_i1: Option<CommittedInstance<C2>>, // output
267 pub cf1_cmT: Option<C2>,
268 pub cf2_cmT: Option<C2>,
269 pub cf1_r_nonnat: Option<C2::ScalarField>,
270 pub cf2_r_nonnat: Option<C2::ScalarField>,
271}
272
273impl<C1: CurveGroup, C2: CurveGroup, GC2: CurveVar<C2, CF2<C2>>, FC: FCircuit<CF1<C1>>>
274 AugmentedFCircuit<C1, C2, GC2, FC>
275where
276 for<'a> &'a GC2: GroupOpsBounds<'a, C2, GC2>,
277{
278 pub fn empty(poseidon_config: &PoseidonConfig<CF1<C1>>, F_circuit: FC) -> Self {
279 Self {
280 _gc2: PhantomData,
281 poseidon_config: poseidon_config.clone(),
282 i: None,
283 i_usize: None,
284 z_0: None,
285 z_i: None,
286 u_i: None,
287 U_i: None,
288 U_i1: None,
289 r_nonnat: None,
290 cmT: None,
291 F: F_circuit,
292 x: None,
293 // cyclefold values
294 cf1_u_i: None,
295 cf2_u_i: None,
296 cf_U_i: None,
297 cf1_U_i1: None,
298 cf_U_i1: None,
299 cf1_cmT: None,
300 cf2_cmT: None,
301 cf1_r_nonnat: None,
302 cf2_r_nonnat: None,
303 }
304 }
305}
306
307impl<C1, C2, GC2, FC> ConstraintSynthesizer<CF1<C1>> for AugmentedFCircuit<C1, C2, GC2, FC>
308where
309 C1: CurveGroup,
310 C2: CurveGroup,
311 GC2: CurveVar<C2, CF2<C2>>,
312 FC: FCircuit<CF1<C1>>,
313 <C1 as CurveGroup>::BaseField: PrimeField,
314 <C2 as CurveGroup>::BaseField: PrimeField,
315 <C1 as Group>::ScalarField: Absorb,
316 <C2 as Group>::ScalarField: Absorb,
317 C1: CurveGroup<BaseField = C2::ScalarField, ScalarField = C2::BaseField>,
318 for<'a> &'a GC2: GroupOpsBounds<'a, C2, GC2>,
319{
320 fn generate_constraints(self, cs: ConstraintSystemRef<CF1<C1>>) -> Result<(), SynthesisError> {
321 let i = FpVar::<CF1<C1>>::new_witness(cs.clone(), || {
322 Ok(self.i.unwrap_or_else(CF1::<C1>::zero))
323 })?;
324 let z_0 = Vec::<FpVar<CF1<C1>>>::new_witness(cs.clone(), || {
325 Ok(self
326 .z_0
327 .unwrap_or(vec![CF1::<C1>::zero(); self.F.state_len()]))
328 })?;
329 let z_i = Vec::<FpVar<CF1<C1>>>::new_witness(cs.clone(), || {
330 Ok(self
331 .z_i
332 .unwrap_or(vec![CF1::<C1>::zero(); self.F.state_len()]))
333 })?;
334
335 let u_dummy_native = CommittedInstance::<C1>::dummy(1);
336 let u_i = CommittedInstanceVar::<C1>::new_witness(cs.clone(), || {
337 Ok(self.u_i.unwrap_or(u_dummy_native.clone()))
338 })?;
339 let U_i = CommittedInstanceVar::<C1>::new_witness(cs.clone(), || {
340 Ok(self.U_i.unwrap_or(u_dummy_native.clone()))
341 })?;
342 let U_i1 = CommittedInstanceVar::<C1>::new_witness(cs.clone(), || {
343 Ok(self.U_i1.unwrap_or(u_dummy_native.clone()))
344 })?;
345 let r_nonnat =
346 NonNativeFieldVar::<C1::BaseField, C1::ScalarField>::new_witness(cs.clone(), || {
347 Ok(self.r_nonnat.unwrap_or_else(CF2::<C1>::zero))
348 })?;
349 let cmT =
350 NonNativeAffineVar::new_witness(cs.clone(), || Ok(self.cmT.unwrap_or_else(C1::zero)))?;
351 let x =
352 FpVar::<CF1<C1>>::new_input(cs.clone(), || Ok(self.x.unwrap_or_else(CF1::<C1>::zero)))?;
353
354 let crh_params = CRHParametersVar::<C1::ScalarField>::new_constant(
355 cs.clone(),
356 self.poseidon_config.clone(),
357 )?;
358
359 // get z_{i+1} from the F circuit
360 let i_usize = self.i_usize.unwrap_or(0);
361 let z_i1 = self
362 .F
363 .generate_step_constraints(cs.clone(), i_usize, z_i.clone())?;
364
365 let zero = FpVar::<CF1<C1>>::new_constant(cs.clone(), CF1::<C1>::zero())?;
366 let is_not_basecase = i.is_neq(&zero)?;
367
368 // 1. u_i.x == H(i, z_0, z_i, U_i)
369 let (u_i_x, U_i_vec) =
370 U_i.clone()
371 .hash(&crh_params, i.clone(), z_0.clone(), z_i.clone())?;
372 // check that h == u_i.x
373 (u_i.x[0]).conditional_enforce_equal(&u_i_x, &is_not_basecase)?;
374
375 // 2. u_i.cmE==cm(0), u_i.u==1
376 let zero_x = NonNativeFieldVar::<C1::BaseField, C1::ScalarField>::new_constant(
377 cs.clone(),
378 C1::BaseField::zero(),
379 )?;
380 let zero_y = NonNativeFieldVar::<C1::BaseField, C1::ScalarField>::new_constant(
381 cs.clone(),
382 C1::BaseField::one(),
383 )?;
384 (u_i.cmE.x.is_eq(&zero_x)?).conditional_enforce_equal(&Boolean::TRUE, &is_not_basecase)?;
385 (u_i.cmE.y.is_eq(&zero_y)?).conditional_enforce_equal(&Boolean::TRUE, &is_not_basecase)?;
386 (u_i.u.is_one()?).conditional_enforce_equal(&Boolean::TRUE, &is_not_basecase)?;
387
388 // 3. nifs.verify, checks that folding u_i & U_i obtains U_{i+1}.
389 // compute r = H(u_i, U_i, cmT)
390 let r_bits = ChallengeGadget::<C1>::get_challenge_gadget(
391 cs.clone(),
392 &self.poseidon_config,
393 U_i_vec,
394 u_i.clone(),
395 cmT.clone(),
396 )?;
397 let r = Boolean::le_bits_to_fp_var(&r_bits)?;
398
399 // enforce that the input r_nonnat as bits matches the in-circuit computed r_bits
400 let r_nonnat_bits: Vec<Boolean<C1::ScalarField>> =
401 r_nonnat.to_bits_le()?.into_iter().take(N_BITS_RO).collect();
402 r_nonnat_bits.enforce_equal(&r_bits)?;
403
404 // Notice that NIFSGadget::verify is not checking the folding of cmE & cmW, since it will
405 // be done on the other curve.
406 let nifs_check = NIFSGadget::<C1>::verify(r, U_i.clone(), u_i.clone(), U_i1.clone())?;
407 nifs_check.conditional_enforce_equal(&Boolean::TRUE, &is_not_basecase)?;
408
409 // 4. u_{i+1}.x = H(i+1, z_0, z_i+1, U_{i+1}), this is the output of F'
410 let (u_i1_x, _) = U_i1.clone().hash(
411 &crh_params,
412 i + FpVar::<CF1<C1>>::one(),
413 z_0.clone(),
414 z_i1.clone(),
415 )?;
416
417 u_i1_x.enforce_equal(&x)?;
418
419 // CycleFold part
420 let cf_u_dummy_native = CommittedInstance::<C2>::dummy(CF_IO_LEN);
421 // cf W circuit data
422 let cf1_u_i = CycleFoldCommittedInstanceVar::<C2, GC2>::new_witness(cs.clone(), || {
423 Ok(self.cf1_u_i.unwrap_or_else(|| cf_u_dummy_native.clone()))
424 })?;
425 let cf2_u_i = CycleFoldCommittedInstanceVar::<C2, GC2>::new_witness(cs.clone(), || {
426 Ok(self.cf2_u_i.unwrap_or_else(|| cf_u_dummy_native.clone()))
427 })?;
428 let cf_U_i = CycleFoldCommittedInstanceVar::<C2, GC2>::new_witness(cs.clone(), || {
429 Ok(self.cf_U_i.unwrap_or_else(|| cf_u_dummy_native.clone()))
430 })?;
431 let cf1_U_i1 = CycleFoldCommittedInstanceVar::<C2, GC2>::new_witness(cs.clone(), || {
432 Ok(self.cf1_U_i1.unwrap_or_else(|| cf_u_dummy_native.clone()))
433 })?;
434 let cf_U_i1 = CycleFoldCommittedInstanceVar::<C2, GC2>::new_witness(cs.clone(), || {
435 Ok(self.cf_U_i1.unwrap_or_else(|| cf_u_dummy_native.clone()))
436 })?;
437 let cf1_cmT = GC2::new_witness(cs.clone(), || Ok(self.cf1_cmT.unwrap_or_else(C2::zero)))?;
438 let cf2_cmT = GC2::new_witness(cs.clone(), || Ok(self.cf2_cmT.unwrap_or_else(C2::zero)))?;
439 let cf1_r_nonnat =
440 NonNativeFieldVar::<C1::BaseField, C1::ScalarField>::new_witness(cs.clone(), || {
441 Ok(self.cf1_r_nonnat.unwrap_or_else(C2::ScalarField::zero))
442 })?;
443 let cf2_r_nonnat =
444 NonNativeFieldVar::<C1::BaseField, C1::ScalarField>::new_witness(cs.clone(), || {
445 Ok(self.cf2_r_nonnat.unwrap_or_else(C2::ScalarField::zero))
446 })?;
447
448 let cfW_x: Vec<NonNativeFieldVar<C1::BaseField, C1::ScalarField>> = vec![
449 r_nonnat.clone(),
450 U_i.cmW.x,
451 U_i.cmW.y,
452 u_i.cmW.x,
453 u_i.cmW.y,
454 U_i1.cmW.x,
455 U_i1.cmW.y,
456 ];
457 let cfE_x: Vec<NonNativeFieldVar<C1::BaseField, C1::ScalarField>> = vec![
458 r_nonnat, U_i.cmE.x, U_i.cmE.y, cmT.x, cmT.y, U_i1.cmE.x, U_i1.cmE.y,
459 ];
460
461 // ensure that cf1_u & cf2_u have as public inputs the cmW & cmE from main instances U_i,
462 // u_i, U_i+1 coordinates of the commitments
463 cf1_u_i
464 .x
465 .conditional_enforce_equal(&cfW_x, &is_not_basecase)?;
466 cf2_u_i
467 .x
468 .conditional_enforce_equal(&cfE_x, &is_not_basecase)?;
469
470 // cf_r_bits is denoted by rho* in the paper.
471 // assert that cf_r_bits == cf_r_nonnat converted to bits. cf_r_nonnat is just an auxiliary
472 // value used to compute RLC of NonNativeFieldVar values, since we can convert
473 // NonNativeFieldVar into Vec<Boolean>, but not in the other direction.
474 let cf1_r_bits = CycleFoldChallengeGadget::<C2, GC2>::get_challenge_gadget(
475 cs.clone(),
476 &self.poseidon_config,
477 cf_U_i.clone(),
478 cf1_u_i.clone(),
479 cf1_cmT.clone(),
480 )?;
481 let cf1_r_nonnat_bits = cf1_r_nonnat.to_bits_le()?;
482 cf1_r_bits.conditional_enforce_equal(&cf1_r_nonnat_bits[..N_BITS_RO], &is_not_basecase)?;
483 // same for cf2_r:
484 let cf2_r_bits = CycleFoldChallengeGadget::<C2, GC2>::get_challenge_gadget(
485 cs.clone(),
486 &self.poseidon_config,
487 cf1_U_i1.clone(),
488 cf2_u_i.clone(),
489 cf2_cmT.clone(),
490 )?;
491 let cf2_r_nonnat_bits = cf2_r_nonnat.to_bits_le()?;
492 cf2_r_bits.conditional_enforce_equal(&cf2_r_nonnat_bits[..N_BITS_RO], &is_not_basecase)?;
493
494 // check cf_u_i.cmE=0, cf_u_i.u=1
495 (cf1_u_i.cmE.is_zero()?).conditional_enforce_equal(&Boolean::TRUE, &is_not_basecase)?;
496 (cf1_u_i.u.is_one()?).conditional_enforce_equal(&Boolean::TRUE, &is_not_basecase)?;
497 (cf2_u_i.cmE.is_zero()?).conditional_enforce_equal(&Boolean::TRUE, &is_not_basecase)?;
498 (cf2_u_i.u.is_one()?).conditional_enforce_equal(&Boolean::TRUE, &is_not_basecase)?;
499
500 // check the fold of all the parameters of the CycleFold instances, where the elliptic
501 // curve points relations are checked natively in Curve1 circuit (this one)
502 let v1 = NIFSFullGadget::<C2, GC2>::verify(
503 cf1_r_bits,
504 cf1_r_nonnat,
505 cf1_cmT,
506 cf_U_i,
507 cf1_u_i,
508 cf1_U_i1.clone(),
509 )?;
510 v1.conditional_enforce_equal(&Boolean::TRUE, &is_not_basecase)?;
511 let v2 = NIFSFullGadget::<C2, GC2>::verify(
512 cf2_r_bits,
513 cf2_r_nonnat,
514 cf2_cmT,
515 cf1_U_i1, // the output from NIFS.V(cf1_r, cf_U, cfE_u)
516 cf2_u_i,
517 cf_U_i1,
518 )?;
519 v2.conditional_enforce_equal(&Boolean::TRUE, &is_not_basecase)?;
520
521 Ok(())
522 }
523}
524
525#[cfg(test)]
526pub mod tests {
527 use super::*;
528 use ark_bn254::{Fr, G1Projective as Projective};
529 use ark_ff::BigInteger;
530 use ark_r1cs_std::{alloc::AllocVar, R1CSVar};
531 use ark_relations::r1cs::ConstraintSystem;
532 use ark_std::UniformRand;
533
534 use crate::commitment::pedersen::Pedersen;
535 use crate::folding::nova::nifs::tests::prepare_simple_fold_inputs;
536 use crate::folding::nova::nifs::NIFS;
537 use crate::transcript::poseidon::poseidon_test_config;
538
539 #[test]
540 fn test_committed_instance_var() {
541 let mut rng = ark_std::test_rng();
542
543 let ci = CommittedInstance::<Projective> {
544 cmE: Projective::rand(&mut rng),
545 u: Fr::rand(&mut rng),
546 cmW: Projective::rand(&mut rng),
547 x: vec![Fr::rand(&mut rng); 1],
548 };
549
550 let cs = ConstraintSystem::<Fr>::new_ref();
551 let ciVar =
552 CommittedInstanceVar::<Projective>::new_witness(cs.clone(), || Ok(ci.clone())).unwrap();
553 assert_eq!(ciVar.u.value().unwrap(), ci.u);
554 assert_eq!(ciVar.x.value().unwrap(), ci.x);
555 // the values cmE and cmW are checked in the CycleFold's circuit
556 // CommittedInstanceInCycleFoldVar in
557 // nova::cyclefold::tests::test_committed_instance_cyclefold_var
558 }
559
560 #[test]
561 fn test_nifs_gadget() {
562 let (_, _, _, _, ci1, _, ci2, _, ci3, _, cmT, _, r_Fr) = prepare_simple_fold_inputs();
563
564 let ci3_verifier = NIFS::<Projective, Pedersen<Projective>>::verify(r_Fr, &ci1, &ci2, &cmT);
565 assert_eq!(ci3_verifier, ci3);
566
567 let cs = ConstraintSystem::<Fr>::new_ref();
568
569 let rVar = FpVar::<Fr>::new_witness(cs.clone(), || Ok(r_Fr)).unwrap();
570 let ci1Var =
571 CommittedInstanceVar::<Projective>::new_witness(cs.clone(), || Ok(ci1.clone()))
572 .unwrap();
573 let ci2Var =
574 CommittedInstanceVar::<Projective>::new_witness(cs.clone(), || Ok(ci2.clone()))
575 .unwrap();
576 let ci3Var =
577 CommittedInstanceVar::<Projective>::new_witness(cs.clone(), || Ok(ci3.clone()))
578 .unwrap();
579
580 let nifs_check = NIFSGadget::<Projective>::verify(
581 rVar.clone(),
582 ci1Var.clone(),
583 ci2Var.clone(),
584 ci3Var.clone(),
585 )
586 .unwrap();
587 nifs_check.enforce_equal(&Boolean::<Fr>::TRUE).unwrap();
588 assert!(cs.is_satisfied().unwrap());
589 }
590
591 #[test]
592 fn test_committed_instance_hash() {
593 let mut rng = ark_std::test_rng();
594 let poseidon_config = poseidon_test_config::<Fr>();
595
596 let i = Fr::from(3_u32);
597 let z_0 = vec![Fr::from(3_u32)];
598 let z_i = vec![Fr::from(3_u32)];
599 let ci = CommittedInstance::<Projective> {
600 cmE: Projective::rand(&mut rng),
601 u: Fr::rand(&mut rng),
602 cmW: Projective::rand(&mut rng),
603 x: vec![Fr::rand(&mut rng); 1],
604 };
605
606 // compute the CommittedInstance hash natively
607 let h = ci
608 .hash(&poseidon_config, i, z_0.clone(), z_i.clone())
609 .unwrap();
610
611 let cs = ConstraintSystem::<Fr>::new_ref();
612
613 let iVar = FpVar::<Fr>::new_witness(cs.clone(), || Ok(i)).unwrap();
614 let z_0Var = Vec::<FpVar<Fr>>::new_witness(cs.clone(), || Ok(z_0.clone())).unwrap();
615 let z_iVar = Vec::<FpVar<Fr>>::new_witness(cs.clone(), || Ok(z_i.clone())).unwrap();
616 let ciVar =
617 CommittedInstanceVar::<Projective>::new_witness(cs.clone(), || Ok(ci.clone())).unwrap();
618
619 let crh_params = CRHParametersVar::<Fr>::new_constant(cs.clone(), poseidon_config).unwrap();
620
621 // compute the CommittedInstance hash in-circuit
622 let (hVar, _) = ciVar.hash(&crh_params, iVar, z_0Var, z_iVar).unwrap();
623 assert!(cs.is_satisfied().unwrap());
624
625 // check that the natively computed and in-circuit computed hashes match
626 assert_eq!(hVar.value().unwrap(), h);
627 }
628
629 // checks that the gadget and native implementations of the challenge computation match
630 #[test]
631 fn test_challenge_gadget() {
632 let mut rng = ark_std::test_rng();
633 let poseidon_config = poseidon_test_config::<Fr>();
634
635 let u_i = CommittedInstance::<Projective> {
636 cmE: Projective::rand(&mut rng),
637 u: Fr::rand(&mut rng),
638 cmW: Projective::rand(&mut rng),
639 x: vec![Fr::rand(&mut rng); 1],
640 };
641 let U_i = CommittedInstance::<Projective> {
642 cmE: Projective::rand(&mut rng),
643 u: Fr::rand(&mut rng),
644 cmW: Projective::rand(&mut rng),
645 x: vec![Fr::rand(&mut rng); 1],
646 };
647 let cmT = Projective::rand(&mut rng);
648
649 // compute the challenge natively
650 let r_bits = ChallengeGadget::<Projective>::get_challenge_native(
651 &poseidon_config,
652 U_i.clone(),
653 u_i.clone(),
654 cmT,
655 )
656 .unwrap();
657 let r = Fr::from_bigint(BigInteger::from_bits_le(&r_bits)).unwrap();
658
659 let cs = ConstraintSystem::<Fr>::new_ref();
660 let u_iVar =
661 CommittedInstanceVar::<Projective>::new_witness(cs.clone(), || Ok(u_i.clone()))
662 .unwrap();
663 let U_iVar =
664 CommittedInstanceVar::<Projective>::new_witness(cs.clone(), || Ok(U_i.clone()))
665 .unwrap();
666 let cmTVar = NonNativeAffineVar::<Projective>::new_witness(cs.clone(), || Ok(cmT)).unwrap();
667
668 // compute the challenge in-circuit
669 let U_iVar_vec = [
670 vec![U_iVar.u.clone()],
671 U_iVar.x.clone(),
672 U_iVar.cmE.x.to_constraint_field().unwrap(),
673 U_iVar.cmE.y.to_constraint_field().unwrap(),
674 U_iVar.cmW.x.to_constraint_field().unwrap(),
675 U_iVar.cmW.y.to_constraint_field().unwrap(),
676 ]
677 .concat();
678 let r_bitsVar = ChallengeGadget::<Projective>::get_challenge_gadget(
679 cs.clone(),
680 &poseidon_config,
681 U_iVar_vec,
682 u_iVar,
683 cmTVar,
684 )
685 .unwrap();
686 assert!(cs.is_satisfied().unwrap());
687
688 // check that the natively computed and in-circuit computed hashes match
689 let rVar = Boolean::le_bits_to_fp_var(&r_bitsVar).unwrap();
690 assert_eq!(rVar.value().unwrap(), r);
691 assert_eq!(r_bitsVar.value().unwrap(), r_bits);
692 }
693}