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