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::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, |[p1.x,y, p2.x,y, p3.x,y]|
 31pub const CF_IO_LEN: usize = 6;
 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/// This is the gadget used in the AugmentedFCircuit to verify the CycleFold instances folding,
126/// which checks the correct RLC of u,x,cmE,cmW (hence the name containing 'Full', since it checks
127/// all the RLC values, not only the native ones). It assumes that ci2.cmE=0, ci2.u=1.
128pub struct NIFSFullGadget<C: CurveGroup, GC: CurveVar<C, CF2<C>>> {
129    _c: PhantomData<C>,
130    _gc: PhantomData<GC>,
131}
132impl<C: CurveGroup, GC: CurveVar<C, CF2<C>>> NIFSFullGadget<C, GC>
133where
134    C: CurveGroup,
135    GC: CurveVar<C, CF2<C>>,
136    <C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField,
137    for<'a> &'a GC: GroupOpsBounds<'a, C, GC>,
138{
139    pub fn verify(
140        // assumes that r_bits is equal to r_nonnat just that in a different format
141        r_bits: Vec<Boolean<CF2<C>>>,
142        r_nonnat: NonNativeFieldVar<C::ScalarField, CF2<C>>,
143        cmT: GC,
144        // ci1 is assumed to be always with cmE=0, u=1 (checks done previous to this method)
145        ci1: CycleFoldCommittedInstanceVar<C, GC>,
146        ci2: CycleFoldCommittedInstanceVar<C, GC>,
147        ci3: CycleFoldCommittedInstanceVar<C, GC>,
148    ) -> Result<Boolean<CF2<C>>, SynthesisError> {
149        // cm(E) check: ci3.cmE == ci1.cmE + r * cmT (ci2.cmE=0)
150        let first_check = ci3
151            .cmE
152            .is_eq(&(cmT.scalar_mul_le(r_bits.iter())? + ci1.cmE))?;
153
154        // cm(W) check: ci3.cmW == ci1.cmW + r * ci2.cmW
155        let second_check = ci3
156            .cmW
157            .is_eq(&(ci1.cmW + ci2.cmW.scalar_mul_le(r_bits.iter())?))?;
158
159        let u_rlc: NonNativeFieldVar<C::ScalarField, CF2<C>> = ci1.u + r_nonnat.clone();
160        let third_check = u_rlc.is_eq(&ci3.u)?;
161
162        // ensure that: ci3.x == ci1.x + r * ci2.x
163        let x_rlc: Vec<NonNativeFieldVar<C::ScalarField, CF2<C>>> = ci1
164            .x
165            .iter()
166            .zip(ci2.x)
167            .map(|(a, b)| a + &r_nonnat * &b)
168            .collect::<Vec<NonNativeFieldVar<C::ScalarField, CF2<C>>>>();
169        let fourth_check = x_rlc.is_eq(&ci3.x)?;
170
171        first_check
172            .and(&second_check)?
173            .and(&third_check)?
174            .and(&fourth_check)
175    }
176}
177
178/// CycleFoldChallengeGadget computes the RO challenge used for the CycleFold instances NIFS, it contains a
179/// rust-native and a in-circuit compatible versions.
180pub struct CycleFoldChallengeGadget<C: CurveGroup, GC: CurveVar<C, CF2<C>>> {
181    _c: PhantomData<C>, // Nova's Curve2, the one used for the CycleFold circuit
182    _gc: PhantomData<GC>,
183}
184impl<C, GC> CycleFoldChallengeGadget<C, GC>
185where
186    C: CurveGroup,
187    GC: CurveVar<C, CF2<C>>,
188    <C as CurveGroup>::BaseField: PrimeField,
189    <C as CurveGroup>::BaseField: Absorb,
190    for<'a> &'a GC: GroupOpsBounds<'a, C, GC>,
191{
192    pub fn get_challenge_native(
193        poseidon_config: &PoseidonConfig<C::BaseField>,
194        U_i: CommittedInstance<C>,
195        u_i: CommittedInstance<C>,
196        cmT: C,
197    ) -> Result<Vec<bool>, Error> {
198        let mut sponge = PoseidonSponge::<C::BaseField>::new(poseidon_config);
199
200        let U_i_cmE_bytes = point_to_bytes(U_i.cmE)?;
201        let U_i_cmW_bytes = point_to_bytes(U_i.cmW)?;
202        let u_i_cmE_bytes = point_to_bytes(u_i.cmE)?;
203        let u_i_cmW_bytes = point_to_bytes(u_i.cmW)?;
204        let cmT_bytes = point_to_bytes(cmT)?;
205
206        let mut U_i_u_bytes = Vec::new();
207        U_i.u.serialize_uncompressed(&mut U_i_u_bytes)?;
208        let mut U_i_x_bytes = Vec::new();
209        U_i.x.serialize_uncompressed(&mut U_i_x_bytes)?;
210        U_i_x_bytes = U_i_x_bytes[8..].to_vec();
211        let mut u_i_u_bytes = Vec::new();
212        u_i.u.serialize_uncompressed(&mut u_i_u_bytes)?;
213        let mut u_i_x_bytes = Vec::new();
214        u_i.x.serialize_uncompressed(&mut u_i_x_bytes)?;
215        u_i_x_bytes = u_i_x_bytes[8..].to_vec();
216
217        let input: Vec<u8> = [
218            U_i_cmE_bytes,
219            U_i_u_bytes,
220            U_i_cmW_bytes,
221            U_i_x_bytes,
222            u_i_cmE_bytes,
223            u_i_u_bytes,
224            u_i_cmW_bytes,
225            u_i_x_bytes,
226            cmT_bytes,
227        ]
228        .concat();
229        sponge.absorb(&input);
230        let bits = sponge.squeeze_bits(N_BITS_RO);
231        Ok(bits)
232    }
233    // compatible with the native get_challenge_native
234    pub fn get_challenge_gadget(
235        cs: ConstraintSystemRef<C::BaseField>,
236        poseidon_config: &PoseidonConfig<C::BaseField>,
237        U_i: CycleFoldCommittedInstanceVar<C, GC>,
238        u_i: CycleFoldCommittedInstanceVar<C, GC>,
239        cmT: GC,
240    ) -> Result<Vec<Boolean<C::BaseField>>, SynthesisError> {
241        let mut sponge = PoseidonSpongeVar::<C::BaseField>::new(cs, poseidon_config);
242
243        let U_i_x_bytes: Vec<UInt8<CF2<C>>> = U_i
244            .x
245            .iter()
246            .flat_map(|e| e.to_bytes().unwrap_or(vec![]))
247            .collect::<Vec<UInt8<CF2<C>>>>();
248        let u_i_x_bytes: Vec<UInt8<CF2<C>>> = u_i
249            .x
250            .iter()
251            .flat_map(|e| e.to_bytes().unwrap_or(vec![]))
252            .collect::<Vec<UInt8<CF2<C>>>>();
253
254        let input: Vec<UInt8<CF2<C>>> = [
255            pointvar_to_bytes(U_i.cmE)?,
256            U_i.u.to_bytes()?,
257            pointvar_to_bytes(U_i.cmW)?,
258            U_i_x_bytes,
259            pointvar_to_bytes(u_i.cmE)?,
260            u_i.u.to_bytes()?,
261            pointvar_to_bytes(u_i.cmW)?,
262            u_i_x_bytes,
263            pointvar_to_bytes(cmT)?,
264        ]
265        .concat();
266        sponge.absorb(&input)?;
267        let bits = sponge.squeeze_bits(N_BITS_RO)?;
268        Ok(bits)
269    }
270}
271
272/// returns the bytes being compatible with the pointvar_to_bytes method.
273/// These methods are temporary once arkworks has the fix to prevent different to_bytes behaviour
274/// across different curves. Eg, in pasta and bn254: pasta returns 65 bytes both native and gadget,
275/// whereas bn254 returns 64 bytes native and 65 in gadget, also the penultimate byte is different
276/// natively than in gadget.
277fn point_to_bytes<C: CurveGroup>(p: C) -> Result<Vec<u8>, Error> {
278    let l = p.uncompressed_size();
279    let mut b = Vec::new();
280    p.serialize_uncompressed(&mut b)?;
281    if p.is_zero() {
282        b[l / 2] = 1;
283        b[l - 1] = 1;
284    }
285    Ok(b[..63].to_vec())
286}
287fn pointvar_to_bytes<C: CurveGroup, GC: CurveVar<C, CF2<C>>>(
288    p: GC,
289) -> Result<Vec<UInt8<CF2<C>>>, SynthesisError> {
290    let b = p.to_bytes()?;
291    Ok(b[..63].to_vec())
292}
293
294/// CycleFoldCircuit contains the constraints that check the correct fold of the committed
295/// instances from Curve1. Namely, it checks the random linear combinations of the elliptic curve
296/// (Curve1) points of u_i, U_i leading to U_{i+1}
297#[derive(Debug, Clone)]
298pub struct CycleFoldCircuit<C: CurveGroup, GC: CurveVar<C, CF2<C>>> {
299    pub _gc: PhantomData<GC>,
300    pub r_bits: Option<Vec<bool>>,
301    pub p1: Option<C>,
302    pub p2: Option<C>,
303    pub p3: Option<C>,
304    pub x: Option<Vec<CF2<C>>>, // public inputs (cf_u_{i+1}.x)
305}
306impl<C: CurveGroup, GC: CurveVar<C, CF2<C>>> CycleFoldCircuit<C, GC> {
307    pub fn empty() -> Self {
308        Self {
309            _gc: PhantomData,
310            r_bits: None,
311            p1: None,
312            p2: None,
313            p3: None,
314            x: None,
315        }
316    }
317}
318impl<C, GC> ConstraintSynthesizer<CF2<C>> for CycleFoldCircuit<C, GC>
319where
320    C: CurveGroup,
321    GC: CurveVar<C, CF2<C>>,
322    <C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField,
323    for<'a> &'a GC: GroupOpsBounds<'a, C, GC>,
324{
325    fn generate_constraints(self, cs: ConstraintSystemRef<CF2<C>>) -> Result<(), SynthesisError> {
326        let r_bits: Vec<Boolean<CF2<C>>> = Vec::new_witness(cs.clone(), || {
327            Ok(self.r_bits.unwrap_or(vec![false; N_BITS_RO]))
328        })?;
329        let p1 = GC::new_witness(cs.clone(), || Ok(self.p1.unwrap_or(C::zero())))?;
330        let p2 = GC::new_witness(cs.clone(), || Ok(self.p2.unwrap_or(C::zero())))?;
331        let p3 = GC::new_witness(cs.clone(), || Ok(self.p3.unwrap_or(C::zero())))?;
332
333        let _x = Vec::<FpVar<CF2<C>>>::new_input(cs.clone(), || {
334            Ok(self.x.unwrap_or(vec![CF2::<C>::zero(); CF_IO_LEN]))
335        })?;
336        #[cfg(test)]
337        assert_eq!(_x.len(), CF_IO_LEN); // non-constrained sanity check
338
339        // Fold the original Nova instances natively in CycleFold
340        // For the cmW we're checking: U_i1.cmW == U_i.cmW + r * u_i.cmW
341        // For the cmE we're checking: U_i1.cmE == U_i.cmE + r * cmT + r^2 * u_i.cmE, where u_i.cmE
342        // is assumed to be 0, so, U_i1.cmE == U_i.cmE + r * cmT
343        p3.enforce_equal(&(p1 + p2.scalar_mul_le(r_bits.iter())?))?;
344
345        // check that x == [u_i, U_i, U_{i+1}], check that the cmW & cmW from u_i, U_i, U_{i+1} in
346        // the CycleFoldCircuit are the sames used in the public inputs 'x', which come from the
347        // AugmentedFCircuit.
348        // TODO: Issue to keep track of this: https://github.com/privacy-scaling-explorations/folding-schemes/issues/44
349        // and https://github.com/privacy-scaling-explorations/folding-schemes/issues/48
350
351        Ok(())
352    }
353}
354
355#[cfg(test)]
356pub mod tests {
357    use super::*;
358    use ark_bn254::{constraints::GVar, Fq, Fr, G1Projective as Projective};
359    use ark_ff::BigInteger;
360    use ark_r1cs_std::{alloc::AllocVar, R1CSVar};
361    use ark_relations::r1cs::ConstraintSystem;
362    use ark_std::UniformRand;
363
364    use crate::folding::nova::get_cm_coordinates;
365    use crate::folding::nova::nifs::tests::prepare_simple_fold_inputs;
366    use crate::transcript::poseidon::poseidon_test_config;
367
368    #[test]
369    fn test_committed_instance_cyclefold_var() {
370        let mut rng = ark_std::test_rng();
371
372        let ci = CommittedInstance::<Projective> {
373            cmE: Projective::rand(&mut rng),
374            u: Fr::rand(&mut rng),
375            cmW: Projective::rand(&mut rng),
376            x: vec![Fr::rand(&mut rng); 1],
377        };
378
379        // check the instantiation of the CycleFold side:
380        let cs = ConstraintSystem::<Fq>::new_ref();
381        let ciVar =
382            CommittedInstanceInCycleFoldVar::<Projective, GVar>::new_witness(cs.clone(), || {
383                Ok(ci.clone())
384            })
385            .unwrap();
386        assert_eq!(ciVar.cmE.value().unwrap(), ci.cmE);
387        assert_eq!(ciVar.cmW.value().unwrap(), ci.cmW);
388    }
389
390    #[test]
391    fn test_CycleFoldCircuit_constraints() {
392        let (_, _, _, _, ci1, _, ci2, _, ci3, _, cmT, r_bits, _) = prepare_simple_fold_inputs();
393
394        // cs is the Constraint System on the Curve Cycle auxiliary curve constraints field
395        // (E1::Fq=E2::Fr)
396        let cs = ConstraintSystem::<Fq>::new_ref();
397
398        let cfW_u_i_x = [
399            get_cm_coordinates(&ci1.cmW),
400            get_cm_coordinates(&ci2.cmW),
401            get_cm_coordinates(&ci3.cmW),
402        ]
403        .concat();
404        let cfW_circuit = CycleFoldCircuit::<Projective, GVar> {
405            _gc: PhantomData,
406            r_bits: Some(r_bits.clone()),
407            p1: Some(ci1.clone().cmW),
408            p2: Some(ci2.clone().cmW),
409            p3: Some(ci3.clone().cmW),
410            x: Some(cfW_u_i_x.clone()),
411        };
412        cfW_circuit.generate_constraints(cs.clone()).unwrap();
413        assert!(cs.is_satisfied().unwrap());
414        dbg!(cs.num_constraints());
415
416        // same for E:
417        let cs = ConstraintSystem::<Fq>::new_ref();
418        let cfE_u_i_x = [
419            get_cm_coordinates(&ci1.cmE),
420            get_cm_coordinates(&ci2.cmE),
421            get_cm_coordinates(&ci3.cmE),
422        ]
423        .concat();
424        let cfE_circuit = CycleFoldCircuit::<Projective, GVar> {
425            _gc: PhantomData,
426            r_bits: Some(r_bits.clone()),
427            p1: Some(ci1.clone().cmE),
428            p2: Some(cmT),
429            p3: Some(ci3.clone().cmE),
430            x: Some(cfE_u_i_x.clone()),
431        };
432        cfE_circuit.generate_constraints(cs.clone()).unwrap();
433        assert!(cs.is_satisfied().unwrap());
434    }
435
436    #[test]
437    fn test_nifs_full_gadget() {
438        let (_, _, _, _, ci1, _, ci2, _, ci3, _, cmT, r_bits, r_Fr) = prepare_simple_fold_inputs();
439
440        let cs = ConstraintSystem::<Fq>::new_ref();
441
442        let r_nonnatVar =
443            NonNativeFieldVar::<Fr, Fq>::new_witness(cs.clone(), || Ok(r_Fr)).unwrap();
444        let r_bitsVar = Vec::<Boolean<Fq>>::new_witness(cs.clone(), || Ok(r_bits)).unwrap();
445
446        let ci1Var =
447            CycleFoldCommittedInstanceVar::<Projective, GVar>::new_witness(cs.clone(), || {
448                Ok(ci1.clone())
449            })
450            .unwrap();
451        let ci2Var =
452            CycleFoldCommittedInstanceVar::<Projective, GVar>::new_witness(cs.clone(), || {
453                Ok(ci2.clone())
454            })
455            .unwrap();
456        let ci3Var =
457            CycleFoldCommittedInstanceVar::<Projective, GVar>::new_witness(cs.clone(), || {
458                Ok(ci3.clone())
459            })
460            .unwrap();
461        let cmTVar = GVar::new_witness(cs.clone(), || Ok(cmT)).unwrap();
462
463        let nifs_check = NIFSFullGadget::<Projective, GVar>::verify(
464            r_bitsVar,
465            r_nonnatVar,
466            cmTVar,
467            ci1Var,
468            ci2Var,
469            ci3Var,
470        )
471        .unwrap();
472        nifs_check.enforce_equal(&Boolean::<Fq>::TRUE).unwrap();
473        assert!(cs.is_satisfied().unwrap());
474    }
475
476    #[test]
477    fn test_point_bytes() {
478        let mut rng = ark_std::test_rng();
479
480        let p = Projective::rand(&mut rng);
481        let p_bytes = point_to_bytes(p).unwrap();
482
483        let cs = ConstraintSystem::<Fq>::new_ref();
484        let pVar = GVar::new_witness(cs.clone(), || Ok(p)).unwrap();
485        assert_eq!(pVar.value().unwrap(), p);
486
487        let p_bytesVar = &pointvar_to_bytes(pVar).unwrap();
488        assert_eq!(p_bytesVar.value().unwrap(), p_bytes);
489    }
490
491    #[test]
492    fn test_cyclefold_challenge_gadget() {
493        let mut rng = ark_std::test_rng();
494        let poseidon_config = poseidon_test_config::<Fq>();
495
496        let u_i = CommittedInstance::<Projective> {
497            cmE: Projective::zero(), // zero on purpose, so we test also the zero point case
498            u: Fr::zero(),
499            cmW: Projective::rand(&mut rng),
500            x: std::iter::repeat_with(|| Fr::rand(&mut rng))
501                .take(CF_IO_LEN)
502                .collect(),
503        };
504        let U_i = CommittedInstance::<Projective> {
505            cmE: Projective::rand(&mut rng),
506            u: Fr::rand(&mut rng),
507            cmW: Projective::rand(&mut rng),
508            x: std::iter::repeat_with(|| Fr::rand(&mut rng))
509                .take(CF_IO_LEN)
510                .collect(),
511        };
512        let cmT = Projective::rand(&mut rng);
513
514        // compute the challenge natively
515        let r_bits = CycleFoldChallengeGadget::<Projective, GVar>::get_challenge_native(
516            &poseidon_config,
517            U_i.clone(),
518            u_i.clone(),
519            cmT,
520        )
521        .unwrap();
522
523        let cs = ConstraintSystem::<Fq>::new_ref();
524        let u_iVar =
525            CycleFoldCommittedInstanceVar::<Projective, GVar>::new_witness(cs.clone(), || {
526                Ok(u_i.clone())
527            })
528            .unwrap();
529        let U_iVar =
530            CycleFoldCommittedInstanceVar::<Projective, GVar>::new_witness(cs.clone(), || {
531                Ok(U_i.clone())
532            })
533            .unwrap();
534        let cmTVar = GVar::new_witness(cs.clone(), || Ok(cmT)).unwrap();
535
536        let r_bitsVar = CycleFoldChallengeGadget::<Projective, GVar>::get_challenge_gadget(
537            cs.clone(),
538            &poseidon_config,
539            U_iVar,
540            u_iVar,
541            cmTVar,
542        )
543        .unwrap();
544        assert!(cs.is_satisfied().unwrap());
545
546        // check that the natively computed and in-circuit computed hashes match
547        let rVar = Boolean::le_bits_to_fp_var(&r_bitsVar).unwrap();
548        let r = Fq::from_bigint(BigInteger::from_bits_le(&r_bits)).unwrap();
549        assert_eq!(rVar.value().unwrap(), r);
550        assert_eq!(r_bitsVar.value().unwrap(), r_bits);
551    }
552}