arnaucube
arnaucube/sonobePublic Self-hosted repository
sonobe/folding-schemes/src/folding/nova/cyclefold.rs
folding-schemes/src/folding/nova/cyclefold.rsRaw
  1/// contains [CycleFold](https://eprint.iacr.org/2023/1192.pdf) related circuits
  2use ark_crypto_primitives::{
  3    crh::{
  4        poseidon::constraints::{CRHGadget, CRHParametersVar},
  5        CRHSchemeGadget,
  6    },
  7    sponge::{
  8        constraints::CryptographicSpongeVar,
  9        poseidon::{constraints::PoseidonSpongeVar, PoseidonConfig, PoseidonSponge},
 10        Absorb, CryptographicSponge,
 11    },
 12};
 13use ark_ec::{AffineRepr, CurveGroup};
 14use ark_ff::{Field, PrimeField, ToConstraintField};
 15use ark_r1cs_std::{
 16    alloc::{AllocVar, AllocationMode},
 17    boolean::Boolean,
 18    eq::EqGadget,
 19    fields::{fp::FpVar, FieldVar},
 20    groups::GroupOpsBounds,
 21    prelude::CurveVar,
 22    ToConstraintFieldGadget,
 23};
 24use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystemRef, Namespace, SynthesisError};
 25use ark_std::fmt::Debug;
 26use ark_std::{One, Zero};
 27use core::{borrow::Borrow, marker::PhantomData};
 28
 29use super::CommittedInstance;
 30use crate::constants::N_BITS_RO;
 31use crate::folding::circuits::{nonnative::uint::NonNativeUintVar, CF2};
 32use crate::Error;
 33
 34// public inputs length for the CycleFoldCircuit: |[r, p1.x,y, p2.x,y, p3.x,y]|
 35pub const CF_IO_LEN: usize = 7;
 36
 37/// CycleFoldCommittedInstanceVar is the CycleFold CommittedInstance representation in the Nova
 38/// circuit.
 39#[derive(Debug, Clone)]
 40pub struct CycleFoldCommittedInstanceVar<C: CurveGroup, GC: CurveVar<C, CF2<C>>>
 41where
 42    for<'a> &'a GC: GroupOpsBounds<'a, C, GC>,
 43{
 44    pub cmE: GC,
 45    pub u: NonNativeUintVar<CF2<C>>,
 46    pub cmW: GC,
 47    pub x: Vec<NonNativeUintVar<CF2<C>>>,
 48}
 49impl<C, GC> AllocVar<CommittedInstance<C>, CF2<C>> for CycleFoldCommittedInstanceVar<C, GC>
 50where
 51    C: CurveGroup,
 52    GC: CurveVar<C, CF2<C>>,
 53    <C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField,
 54    for<'a> &'a GC: GroupOpsBounds<'a, C, GC>,
 55{
 56    fn new_variable<T: Borrow<CommittedInstance<C>>>(
 57        cs: impl Into<Namespace<CF2<C>>>,
 58        f: impl FnOnce() -> Result<T, SynthesisError>,
 59        mode: AllocationMode,
 60    ) -> Result<Self, SynthesisError> {
 61        f().and_then(|val| {
 62            let cs = cs.into();
 63
 64            let cmE = GC::new_variable(cs.clone(), || Ok(val.borrow().cmE), mode)?;
 65            let cmW = GC::new_variable(cs.clone(), || Ok(val.borrow().cmW), mode)?;
 66            let u = NonNativeUintVar::new_variable(cs.clone(), || Ok(val.borrow().u), mode)?;
 67            let x = Vec::new_variable(cs.clone(), || Ok(val.borrow().x.clone()), mode)?;
 68
 69            Ok(Self { cmE, u, cmW, x })
 70        })
 71    }
 72}
 73
 74impl<C, GC> ToConstraintFieldGadget<CF2<C>> for CycleFoldCommittedInstanceVar<C, GC>
 75where
 76    C: CurveGroup,
 77    GC: CurveVar<C, CF2<C>> + ToConstraintFieldGadget<CF2<C>>,
 78    <C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField + Absorb,
 79    for<'a> &'a GC: GroupOpsBounds<'a, C, GC>,
 80{
 81    // Extract the underlying field elements from `CycleFoldCommittedInstanceVar`, in the order of
 82    // `u`, `x`, `cmE.x`, `cmE.y`, `cmW.x`, `cmW.y`, `cmE.is_inf || cmW.is_inf` (|| is for concat).
 83    fn to_constraint_field(&self) -> Result<Vec<FpVar<CF2<C>>>, SynthesisError> {
 84        let mut cmE_elems = self.cmE.to_constraint_field()?;
 85        let mut cmW_elems = self.cmW.to_constraint_field()?;
 86
 87        let cmE_is_inf = cmE_elems.pop().unwrap();
 88        let cmW_is_inf = cmW_elems.pop().unwrap();
 89        // Concatenate `cmE_is_inf` and `cmW_is_inf` to save constraints for CRHGadget::evaluate
 90        let is_inf = cmE_is_inf.double()? + cmW_is_inf;
 91
 92        Ok([
 93            self.u.to_constraint_field()?,
 94            self.x
 95                .iter()
 96                .map(|i| i.to_constraint_field())
 97                .collect::<Result<Vec<_>, _>>()?
 98                .concat(),
 99            cmE_elems,
100            cmW_elems,
101            vec![is_inf],
102        ]
103        .concat())
104    }
105}
106
107impl<C, GC> CycleFoldCommittedInstanceVar<C, GC>
108where
109    C: CurveGroup,
110    GC: CurveVar<C, CF2<C>> + ToConstraintFieldGadget<CF2<C>>,
111    <C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField + Absorb,
112    for<'a> &'a GC: GroupOpsBounds<'a, C, GC>,
113{
114    /// hash implements the committed instance hash compatible with the native implementation from
115    /// CommittedInstance.hash_cyclefold.
116    /// Returns `H(U_i)`, where `U` is the `CommittedInstance` for CycleFold.
117    /// Additionally it returns the vector of the field elements from the self parameters, so they
118    /// can be reused in other gadgets avoiding recalculating (reconstraining) them.
119    #[allow(clippy::type_complexity)]
120    pub fn hash(
121        self,
122        crh_params: &CRHParametersVar<CF2<C>>,
123    ) -> Result<(FpVar<CF2<C>>, Vec<FpVar<CF2<C>>>), SynthesisError> {
124        let U_vec = self.to_constraint_field()?;
125        Ok((CRHGadget::evaluate(crh_params, &U_vec)?, U_vec))
126    }
127}
128
129/// CommittedInstanceInCycleFoldVar represents the Nova CommittedInstance in the CycleFold circuit,
130/// where the commitments to E and W (cmW and cmW) from the CommittedInstance on the E2,
131/// represented as native points, which are folded on the auxiliary curve constraints field (E2::Fr
132/// = E1::Fq).
133#[derive(Debug, Clone)]
134pub struct CommittedInstanceInCycleFoldVar<C: CurveGroup, GC: CurveVar<C, CF2<C>>>
135where
136    for<'a> &'a GC: GroupOpsBounds<'a, C, GC>,
137{
138    _c: PhantomData<C>,
139    pub cmE: GC,
140    pub cmW: GC,
141}
142
143impl<C, GC> AllocVar<CommittedInstance<C>, CF2<C>> for CommittedInstanceInCycleFoldVar<C, GC>
144where
145    C: CurveGroup,
146    GC: CurveVar<C, CF2<C>>,
147    for<'a> &'a GC: GroupOpsBounds<'a, C, GC>,
148{
149    fn new_variable<T: Borrow<CommittedInstance<C>>>(
150        cs: impl Into<Namespace<CF2<C>>>,
151        f: impl FnOnce() -> Result<T, SynthesisError>,
152        mode: AllocationMode,
153    ) -> Result<Self, SynthesisError> {
154        f().and_then(|val| {
155            let cs = cs.into();
156
157            let cmE = GC::new_variable(cs.clone(), || Ok(val.borrow().cmE), mode)?;
158            let cmW = GC::new_variable(cs.clone(), || Ok(val.borrow().cmW), mode)?;
159
160            Ok(Self {
161                _c: PhantomData,
162                cmE,
163                cmW,
164            })
165        })
166    }
167}
168
169/// This is the gadget used in the AugmentedFCircuit to verify the CycleFold instances folding,
170/// which checks the correct RLC of u,x,cmE,cmW (hence the name containing 'Full', since it checks
171/// all the RLC values, not only the native ones). It assumes that ci2.cmE=0, ci2.u=1.
172pub struct NIFSFullGadget<C: CurveGroup, GC: CurveVar<C, CF2<C>>> {
173    _c: PhantomData<C>,
174    _gc: PhantomData<GC>,
175}
176
177impl<C: CurveGroup, GC: CurveVar<C, CF2<C>>> NIFSFullGadget<C, GC>
178where
179    C: CurveGroup,
180    GC: CurveVar<C, CF2<C>>,
181    <C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField,
182    for<'a> &'a GC: GroupOpsBounds<'a, C, GC>,
183{
184    pub fn fold_committed_instance(
185        // assumes that r_bits is equal to r_nonnat just that in a different format
186        r_bits: Vec<Boolean<CF2<C>>>,
187        r_nonnat: NonNativeUintVar<CF2<C>>,
188        cmT: GC,
189        ci1: CycleFoldCommittedInstanceVar<C, GC>,
190        // ci2 is assumed to be always with cmE=0, u=1 (checks done previous to this method)
191        ci2: CycleFoldCommittedInstanceVar<C, GC>,
192    ) -> Result<CycleFoldCommittedInstanceVar<C, GC>, SynthesisError> {
193        Ok(CycleFoldCommittedInstanceVar {
194            cmE: cmT.scalar_mul_le(r_bits.iter())? + ci1.cmE,
195            cmW: ci1.cmW + ci2.cmW.scalar_mul_le(r_bits.iter())?,
196            u: ci1.u.add_no_align(&r_nonnat).modulo::<C::ScalarField>()?,
197            x: ci1
198                .x
199                .iter()
200                .zip(ci2.x)
201                .map(|(a, b)| {
202                    a.add_no_align(&r_nonnat.mul_no_align(&b)?)
203                        .modulo::<C::ScalarField>()
204                })
205                .collect::<Result<Vec<_>, _>>()?,
206        })
207    }
208
209    pub fn verify(
210        // assumes that r_bits is equal to r_nonnat just that in a different format
211        r_bits: Vec<Boolean<CF2<C>>>,
212        r_nonnat: NonNativeUintVar<CF2<C>>,
213        cmT: GC,
214        ci1: CycleFoldCommittedInstanceVar<C, GC>,
215        // ci2 is assumed to be always with cmE=0, u=1 (checks done previous to this method)
216        ci2: CycleFoldCommittedInstanceVar<C, GC>,
217        ci3: CycleFoldCommittedInstanceVar<C, GC>,
218    ) -> Result<(), SynthesisError> {
219        let ci = Self::fold_committed_instance(r_bits, r_nonnat, cmT, ci1, ci2)?;
220
221        ci.cmE.enforce_equal(&ci3.cmE)?;
222        ci.u.enforce_equal_unaligned(&ci3.u)?;
223        ci.cmW.enforce_equal(&ci3.cmW)?;
224        for (x, y) in ci.x.iter().zip(ci3.x.iter()) {
225            x.enforce_equal_unaligned(y)?;
226        }
227
228        Ok(())
229    }
230}
231
232/// CycleFoldChallengeGadget computes the RO challenge used for the CycleFold instances NIFS, it contains a
233/// rust-native and a in-circuit compatible versions.
234pub struct CycleFoldChallengeGadget<C: CurveGroup, GC: CurveVar<C, CF2<C>>> {
235    _c: PhantomData<C>, // Nova's Curve2, the one used for the CycleFold circuit
236    _gc: PhantomData<GC>,
237}
238impl<C, GC> CycleFoldChallengeGadget<C, GC>
239where
240    C: CurveGroup,
241    GC: CurveVar<C, CF2<C>> + ToConstraintFieldGadget<CF2<C>>,
242    <C as CurveGroup>::BaseField: PrimeField,
243    <C as CurveGroup>::BaseField: Absorb,
244    for<'a> &'a GC: GroupOpsBounds<'a, C, GC>,
245{
246    pub fn get_challenge_native(
247        poseidon_config: &PoseidonConfig<C::BaseField>,
248        U_i: CommittedInstance<C>,
249        u_i: CommittedInstance<C>,
250        cmT: C,
251    ) -> Result<Vec<bool>, Error> {
252        let mut sponge = PoseidonSponge::<C::BaseField>::new(poseidon_config);
253
254        let mut U_vec = U_i.to_field_elements().unwrap();
255        let mut u_vec = u_i.to_field_elements().unwrap();
256        let (cmT_x, cmT_y, cmT_is_inf) = match cmT.into_affine().xy() {
257            Some((&x, &y)) => (x, y, C::BaseField::zero()),
258            None => (
259                C::BaseField::zero(),
260                C::BaseField::zero(),
261                C::BaseField::one(),
262            ),
263        };
264
265        let U_cm_is_inf = U_vec.pop().unwrap();
266        let u_cm_is_inf = u_vec.pop().unwrap();
267
268        // Concatenate `U_i.cmE_is_inf`, `U_i.cmW_is_inf`, `u_i.cmE_is_inf`, `u_i.cmW_is_inf`, `cmT_is_inf`
269        // to save constraints for sponge.squeeze_bits in the corresponding circuit
270        let is_inf = U_cm_is_inf * CF2::<C>::from(8u8) + u_cm_is_inf.double() + cmT_is_inf;
271
272        let input = [U_vec, u_vec, vec![cmT_x, cmT_y, is_inf]].concat();
273        sponge.absorb(&input);
274        let bits = sponge.squeeze_bits(N_BITS_RO);
275        Ok(bits)
276    }
277
278    // compatible with the native get_challenge_native
279    pub fn get_challenge_gadget(
280        cs: ConstraintSystemRef<C::BaseField>,
281        poseidon_config: &PoseidonConfig<C::BaseField>,
282        mut U_i_vec: Vec<FpVar<C::BaseField>>,
283        u_i: CycleFoldCommittedInstanceVar<C, GC>,
284        cmT: GC,
285    ) -> Result<Vec<Boolean<C::BaseField>>, SynthesisError> {
286        let mut sponge = PoseidonSpongeVar::<C::BaseField>::new(cs, poseidon_config);
287
288        let mut u_i_vec = u_i.to_constraint_field()?;
289        let mut cmT_vec = cmT.to_constraint_field()?;
290
291        let U_cm_is_inf = U_i_vec.pop().unwrap();
292        let u_cm_is_inf = u_i_vec.pop().unwrap();
293        let cmT_is_inf = cmT_vec.pop().unwrap();
294
295        // Concatenate `U_i.cmE_is_inf`, `U_i.cmW_is_inf`, `u_i.cmE_is_inf`, `u_i.cmW_is_inf`, `cmT_is_inf`
296        // to save constraints for sponge.squeeze_bits
297        let is_inf = U_cm_is_inf * CF2::<C>::from(8u8) + u_cm_is_inf.double()? + cmT_is_inf;
298
299        let input = [U_i_vec, u_i_vec, cmT_vec, vec![is_inf]].concat();
300        sponge.absorb(&input)?;
301        let bits = sponge.squeeze_bits(N_BITS_RO)?;
302        Ok(bits)
303    }
304}
305
306/// CycleFoldCircuit contains the constraints that check the correct fold of the committed
307/// instances from Curve1. Namely, it checks the random linear combinations of the elliptic curve
308/// (Curve1) points of u_i, U_i leading to U_{i+1}
309#[derive(Debug, Clone)]
310pub struct CycleFoldCircuit<C: CurveGroup, GC: CurveVar<C, CF2<C>>> {
311    pub _gc: PhantomData<GC>,
312    pub r_bits: Option<Vec<bool>>,
313    pub p1: Option<C>,
314    pub p2: Option<C>,
315    pub x: Option<Vec<CF2<C>>>, // public inputs (cf_u_{i+1}.x)
316}
317impl<C: CurveGroup, GC: CurveVar<C, CF2<C>>> CycleFoldCircuit<C, GC> {
318    pub fn empty() -> Self {
319        Self {
320            _gc: PhantomData,
321            r_bits: None,
322            p1: None,
323            p2: None,
324            x: None,
325        }
326    }
327}
328impl<C, GC> ConstraintSynthesizer<CF2<C>> for CycleFoldCircuit<C, GC>
329where
330    C: CurveGroup,
331    GC: CurveVar<C, CF2<C>> + ToConstraintFieldGadget<CF2<C>>,
332    <C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField,
333    for<'a> &'a GC: GroupOpsBounds<'a, C, GC>,
334{
335    fn generate_constraints(self, cs: ConstraintSystemRef<CF2<C>>) -> Result<(), SynthesisError> {
336        let r_bits: Vec<Boolean<CF2<C>>> = Vec::new_witness(cs.clone(), || {
337            Ok(self.r_bits.unwrap_or(vec![false; N_BITS_RO]))
338        })?;
339        let p1 = GC::new_witness(cs.clone(), || Ok(self.p1.unwrap_or(C::zero())))?;
340        let p2 = GC::new_witness(cs.clone(), || Ok(self.p2.unwrap_or(C::zero())))?;
341        // Fold the original Nova instances natively in CycleFold
342        // For the cmW we're computing: U_i1.cmW = U_i.cmW + r * u_i.cmW
343        // For the cmE we're computing: U_i1.cmE = U_i.cmE + r * cmT + r^2 * u_i.cmE, where u_i.cmE
344        // is assumed to be 0, so, U_i1.cmE = U_i.cmE + r * cmT
345        let p3 = &p1 + p2.scalar_mul_le(r_bits.iter())?;
346
347        let x = Vec::<FpVar<CF2<C>>>::new_input(cs.clone(), || {
348            Ok(self.x.unwrap_or(vec![CF2::<C>::zero(); CF_IO_LEN]))
349        })?;
350        #[cfg(test)]
351        assert_eq!(x.len(), CF_IO_LEN); // non-constrained sanity check
352
353        // check that the points coordinates are placed as the public input x: x == [r, p1, p2, p3]
354        let r: FpVar<CF2<C>> = Boolean::le_bits_to_fp_var(&r_bits)?;
355        let points_coords: Vec<FpVar<CF2<C>>> = [
356            vec![r],
357            p1.to_constraint_field()?[..2].to_vec(),
358            p2.to_constraint_field()?[..2].to_vec(),
359            p3.to_constraint_field()?[..2].to_vec(),
360        ]
361        .concat();
362        points_coords.enforce_equal(&x)?;
363
364        Ok(())
365    }
366}
367
368#[cfg(test)]
369pub mod tests {
370    use super::*;
371    use ark_bn254::{constraints::GVar, Fq, Fr, G1Projective as Projective};
372    use ark_ff::BigInteger;
373    use ark_r1cs_std::R1CSVar;
374    use ark_relations::r1cs::ConstraintSystem;
375    use ark_std::UniformRand;
376
377    use crate::folding::nova::get_cm_coordinates;
378    use crate::folding::nova::nifs::tests::prepare_simple_fold_inputs;
379    use crate::transcript::poseidon::poseidon_canonical_config;
380
381    #[test]
382    fn test_committed_instance_cyclefold_var() {
383        let mut rng = ark_std::test_rng();
384
385        let ci = CommittedInstance::<Projective> {
386            cmE: Projective::rand(&mut rng),
387            u: Fr::rand(&mut rng),
388            cmW: Projective::rand(&mut rng),
389            x: vec![Fr::rand(&mut rng); 1],
390        };
391
392        // check the instantiation of the CycleFold side:
393        let cs = ConstraintSystem::<Fq>::new_ref();
394        let ciVar =
395            CommittedInstanceInCycleFoldVar::<Projective, GVar>::new_witness(cs.clone(), || {
396                Ok(ci.clone())
397            })
398            .unwrap();
399        assert_eq!(ciVar.cmE.value().unwrap(), ci.cmE);
400        assert_eq!(ciVar.cmW.value().unwrap(), ci.cmW);
401    }
402
403    #[test]
404    fn test_CycleFoldCircuit_constraints() {
405        let (_, _, _, _, ci1, _, ci2, _, ci3, _, cmT, r_bits, _) = prepare_simple_fold_inputs();
406        let r_Fq = Fq::from_bigint(BigInteger::from_bits_le(&r_bits)).unwrap();
407
408        // cs is the Constraint System on the Curve Cycle auxiliary curve constraints field
409        // (E1::Fq=E2::Fr)
410        let cs = ConstraintSystem::<Fq>::new_ref();
411
412        let cfW_u_i_x: Vec<Fq> = [
413            vec![r_Fq],
414            get_cm_coordinates(&ci1.cmW),
415            get_cm_coordinates(&ci2.cmW),
416            get_cm_coordinates(&ci3.cmW),
417        ]
418        .concat();
419        let cfW_circuit = CycleFoldCircuit::<Projective, GVar> {
420            _gc: PhantomData,
421            r_bits: Some(r_bits.clone()),
422            p1: Some(ci1.clone().cmW),
423            p2: Some(ci2.clone().cmW),
424            x: Some(cfW_u_i_x.clone()),
425        };
426        cfW_circuit.generate_constraints(cs.clone()).unwrap();
427        assert!(cs.is_satisfied().unwrap());
428
429        // same for E:
430        let cs = ConstraintSystem::<Fq>::new_ref();
431        let cfE_u_i_x = [
432            vec![r_Fq],
433            get_cm_coordinates(&ci1.cmE),
434            get_cm_coordinates(&cmT),
435            get_cm_coordinates(&ci3.cmE),
436        ]
437        .concat();
438        let cfE_circuit = CycleFoldCircuit::<Projective, GVar> {
439            _gc: PhantomData,
440            r_bits: Some(r_bits.clone()),
441            p1: Some(ci1.clone().cmE),
442            p2: Some(cmT),
443            x: Some(cfE_u_i_x.clone()),
444        };
445        cfE_circuit.generate_constraints(cs.clone()).unwrap();
446        assert!(cs.is_satisfied().unwrap());
447    }
448
449    #[test]
450    fn test_nifs_full_gadget() {
451        let (_, _, _, _, ci1, _, ci2, _, ci3, _, cmT, r_bits, r_Fr) = prepare_simple_fold_inputs();
452
453        let cs = ConstraintSystem::<Fq>::new_ref();
454
455        let r_nonnatVar = NonNativeUintVar::<Fq>::new_witness(cs.clone(), || Ok(r_Fr)).unwrap();
456        let r_bitsVar = Vec::<Boolean<Fq>>::new_witness(cs.clone(), || Ok(r_bits)).unwrap();
457
458        let ci1Var =
459            CycleFoldCommittedInstanceVar::<Projective, GVar>::new_witness(cs.clone(), || {
460                Ok(ci1.clone())
461            })
462            .unwrap();
463        let ci2Var =
464            CycleFoldCommittedInstanceVar::<Projective, GVar>::new_witness(cs.clone(), || {
465                Ok(ci2.clone())
466            })
467            .unwrap();
468        let ci3Var =
469            CycleFoldCommittedInstanceVar::<Projective, GVar>::new_witness(cs.clone(), || {
470                Ok(ci3.clone())
471            })
472            .unwrap();
473        let cmTVar = GVar::new_witness(cs.clone(), || Ok(cmT)).unwrap();
474
475        NIFSFullGadget::<Projective, GVar>::verify(
476            r_bitsVar,
477            r_nonnatVar,
478            cmTVar,
479            ci1Var,
480            ci2Var,
481            ci3Var,
482        )
483        .unwrap();
484        assert!(cs.is_satisfied().unwrap());
485    }
486
487    #[test]
488    fn test_cyclefold_challenge_gadget() {
489        let mut rng = ark_std::test_rng();
490        let poseidon_config = poseidon_canonical_config::<Fq>();
491
492        let u_i = CommittedInstance::<Projective> {
493            cmE: Projective::zero(), // zero on purpose, so we test also the zero point case
494            u: Fr::zero(),
495            cmW: Projective::rand(&mut rng),
496            x: std::iter::repeat_with(|| Fr::rand(&mut rng))
497                .take(CF_IO_LEN)
498                .collect(),
499        };
500        let U_i = CommittedInstance::<Projective> {
501            cmE: Projective::rand(&mut rng),
502            u: Fr::rand(&mut rng),
503            cmW: Projective::rand(&mut rng),
504            x: std::iter::repeat_with(|| Fr::rand(&mut rng))
505                .take(CF_IO_LEN)
506                .collect(),
507        };
508        let cmT = Projective::rand(&mut rng);
509
510        // compute the challenge natively
511        let r_bits = CycleFoldChallengeGadget::<Projective, GVar>::get_challenge_native(
512            &poseidon_config,
513            U_i.clone(),
514            u_i.clone(),
515            cmT,
516        )
517        .unwrap();
518
519        let cs = ConstraintSystem::<Fq>::new_ref();
520        let u_iVar =
521            CycleFoldCommittedInstanceVar::<Projective, GVar>::new_witness(cs.clone(), || {
522                Ok(u_i.clone())
523            })
524            .unwrap();
525        let U_iVar =
526            CycleFoldCommittedInstanceVar::<Projective, GVar>::new_witness(cs.clone(), || {
527                Ok(U_i.clone())
528            })
529            .unwrap();
530        let cmTVar = GVar::new_witness(cs.clone(), || Ok(cmT)).unwrap();
531
532        let r_bitsVar = CycleFoldChallengeGadget::<Projective, GVar>::get_challenge_gadget(
533            cs.clone(),
534            &poseidon_config,
535            U_iVar.to_constraint_field().unwrap(),
536            u_iVar,
537            cmTVar,
538        )
539        .unwrap();
540        assert!(cs.is_satisfied().unwrap());
541
542        // check that the natively computed and in-circuit computed hashes match
543        let rVar = Boolean::le_bits_to_fp_var(&r_bitsVar).unwrap();
544        let r = Fq::from_bigint(BigInteger::from_bits_le(&r_bits)).unwrap();
545        assert_eq!(rVar.value().unwrap(), r);
546        assert_eq!(r_bitsVar.value().unwrap(), r_bits);
547    }
548
549    #[test]
550    fn test_cyclefold_hash_gadget() {
551        let mut rng = ark_std::test_rng();
552        let poseidon_config = poseidon_canonical_config::<Fq>();
553
554        let U_i = CommittedInstance::<Projective> {
555            cmE: Projective::rand(&mut rng),
556            u: Fr::rand(&mut rng),
557            cmW: Projective::rand(&mut rng),
558            x: std::iter::repeat_with(|| Fr::rand(&mut rng))
559                .take(CF_IO_LEN)
560                .collect(),
561        };
562        let h = U_i.hash_cyclefold(&poseidon_config).unwrap();
563
564        let cs = ConstraintSystem::<Fq>::new_ref();
565        let U_iVar =
566            CycleFoldCommittedInstanceVar::<Projective, GVar>::new_witness(cs.clone(), || {
567                Ok(U_i.clone())
568            })
569            .unwrap();
570        let (hVar, _) = U_iVar
571            .hash(&CRHParametersVar::new_constant(cs.clone(), poseidon_config).unwrap())
572            .unwrap();
573        hVar.enforce_equal(&FpVar::new_witness(cs.clone(), || Ok(h)).unwrap())
574            .unwrap();
575        assert!(cs.is_satisfied().unwrap());
576    }
577}