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, ToConstraintFieldGadget,
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// public inputs length for the CycleFoldCircuit: |[r, p1.x,y, p2.x,y, p3.x,y]|
31pub const CF_IO_LEN: usize = 7;
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>> + ToConstraintFieldGadget<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 // check that the points coordinates are placed as the public input x: x == [r, p1, p2, p3]
340 let r: FpVar<CF2<C>> = Boolean::le_bits_to_fp_var(&r_bits)?;
341 let points_coords: Vec<FpVar<CF2<C>>> = [
342 vec![r],
343 p1.clone().to_constraint_field()?[..2].to_vec(),
344 p2.clone().to_constraint_field()?[..2].to_vec(),
345 p3.clone().to_constraint_field()?[..2].to_vec(),
346 ]
347 .concat();
348 points_coords.enforce_equal(&x)?;
349
350 // Fold the original Nova instances natively in CycleFold
351 // For the cmW we're checking: U_i1.cmW == U_i.cmW + r * u_i.cmW
352 // For the cmE we're checking: U_i1.cmE == U_i.cmE + r * cmT + r^2 * u_i.cmE, where u_i.cmE
353 // is assumed to be 0, so, U_i1.cmE == U_i.cmE + r * cmT
354 p3.enforce_equal(&(p1 + p2.scalar_mul_le(r_bits.iter())?))?;
355
356 Ok(())
357 }
358}
359
360#[cfg(test)]
361pub mod tests {
362 use super::*;
363 use ark_bn254::{constraints::GVar, Fq, Fr, G1Projective as Projective};
364 use ark_ff::BigInteger;
365 use ark_r1cs_std::{alloc::AllocVar, R1CSVar};
366 use ark_relations::r1cs::ConstraintSystem;
367 use ark_std::UniformRand;
368
369 use crate::folding::nova::get_cm_coordinates;
370 use crate::folding::nova::nifs::tests::prepare_simple_fold_inputs;
371 use crate::transcript::poseidon::poseidon_test_config;
372
373 #[test]
374 fn test_committed_instance_cyclefold_var() {
375 let mut rng = ark_std::test_rng();
376
377 let ci = CommittedInstance::<Projective> {
378 cmE: Projective::rand(&mut rng),
379 u: Fr::rand(&mut rng),
380 cmW: Projective::rand(&mut rng),
381 x: vec![Fr::rand(&mut rng); 1],
382 };
383
384 // check the instantiation of the CycleFold side:
385 let cs = ConstraintSystem::<Fq>::new_ref();
386 let ciVar =
387 CommittedInstanceInCycleFoldVar::<Projective, GVar>::new_witness(cs.clone(), || {
388 Ok(ci.clone())
389 })
390 .unwrap();
391 assert_eq!(ciVar.cmE.value().unwrap(), ci.cmE);
392 assert_eq!(ciVar.cmW.value().unwrap(), ci.cmW);
393 }
394
395 #[test]
396 fn test_CycleFoldCircuit_constraints() {
397 let (_, _, _, _, ci1, _, ci2, _, ci3, _, cmT, r_bits, _) = prepare_simple_fold_inputs();
398 let r_Fq = Fq::from_bigint(BigInteger::from_bits_le(&r_bits)).unwrap();
399
400 // cs is the Constraint System on the Curve Cycle auxiliary curve constraints field
401 // (E1::Fq=E2::Fr)
402 let cs = ConstraintSystem::<Fq>::new_ref();
403
404 let cfW_u_i_x: Vec<Fq> = [
405 vec![r_Fq],
406 get_cm_coordinates(&ci1.cmW),
407 get_cm_coordinates(&ci2.cmW),
408 get_cm_coordinates(&ci3.cmW),
409 ]
410 .concat();
411 let cfW_circuit = CycleFoldCircuit::<Projective, GVar> {
412 _gc: PhantomData,
413 r_bits: Some(r_bits.clone()),
414 p1: Some(ci1.clone().cmW),
415 p2: Some(ci2.clone().cmW),
416 p3: Some(ci3.clone().cmW),
417 x: Some(cfW_u_i_x.clone()),
418 };
419 cfW_circuit.generate_constraints(cs.clone()).unwrap();
420 assert!(cs.is_satisfied().unwrap());
421
422 // same for E:
423 let cs = ConstraintSystem::<Fq>::new_ref();
424 let cfE_u_i_x = [
425 vec![r_Fq],
426 get_cm_coordinates(&ci1.cmE),
427 get_cm_coordinates(&cmT),
428 get_cm_coordinates(&ci3.cmE),
429 ]
430 .concat();
431 let cfE_circuit = CycleFoldCircuit::<Projective, GVar> {
432 _gc: PhantomData,
433 r_bits: Some(r_bits.clone()),
434 p1: Some(ci1.clone().cmE),
435 p2: Some(cmT),
436 p3: Some(ci3.clone().cmE),
437 x: Some(cfE_u_i_x.clone()),
438 };
439 cfE_circuit.generate_constraints(cs.clone()).unwrap();
440 assert!(cs.is_satisfied().unwrap());
441 }
442
443 #[test]
444 fn test_nifs_full_gadget() {
445 let (_, _, _, _, ci1, _, ci2, _, ci3, _, cmT, r_bits, r_Fr) = prepare_simple_fold_inputs();
446
447 let cs = ConstraintSystem::<Fq>::new_ref();
448
449 let r_nonnatVar =
450 NonNativeFieldVar::<Fr, Fq>::new_witness(cs.clone(), || Ok(r_Fr)).unwrap();
451 let r_bitsVar = Vec::<Boolean<Fq>>::new_witness(cs.clone(), || Ok(r_bits)).unwrap();
452
453 let ci1Var =
454 CycleFoldCommittedInstanceVar::<Projective, GVar>::new_witness(cs.clone(), || {
455 Ok(ci1.clone())
456 })
457 .unwrap();
458 let ci2Var =
459 CycleFoldCommittedInstanceVar::<Projective, GVar>::new_witness(cs.clone(), || {
460 Ok(ci2.clone())
461 })
462 .unwrap();
463 let ci3Var =
464 CycleFoldCommittedInstanceVar::<Projective, GVar>::new_witness(cs.clone(), || {
465 Ok(ci3.clone())
466 })
467 .unwrap();
468 let cmTVar = GVar::new_witness(cs.clone(), || Ok(cmT)).unwrap();
469
470 let nifs_check = NIFSFullGadget::<Projective, GVar>::verify(
471 r_bitsVar,
472 r_nonnatVar,
473 cmTVar,
474 ci1Var,
475 ci2Var,
476 ci3Var,
477 )
478 .unwrap();
479 nifs_check.enforce_equal(&Boolean::<Fq>::TRUE).unwrap();
480 assert!(cs.is_satisfied().unwrap());
481 }
482
483 #[test]
484 fn test_point_bytes() {
485 let mut rng = ark_std::test_rng();
486
487 let p = Projective::rand(&mut rng);
488 let p_bytes = point_to_bytes(p).unwrap();
489
490 let cs = ConstraintSystem::<Fq>::new_ref();
491 let pVar = GVar::new_witness(cs.clone(), || Ok(p)).unwrap();
492 assert_eq!(pVar.value().unwrap(), p);
493
494 let p_bytesVar = &pointvar_to_bytes(pVar).unwrap();
495 assert_eq!(p_bytesVar.value().unwrap(), p_bytes);
496 }
497
498 #[test]
499 fn test_cyclefold_challenge_gadget() {
500 let mut rng = ark_std::test_rng();
501 let poseidon_config = poseidon_test_config::<Fq>();
502
503 let u_i = CommittedInstance::<Projective> {
504 cmE: Projective::zero(), // zero on purpose, so we test also the zero point case
505 u: Fr::zero(),
506 cmW: Projective::rand(&mut rng),
507 x: std::iter::repeat_with(|| Fr::rand(&mut rng))
508 .take(CF_IO_LEN)
509 .collect(),
510 };
511 let U_i = CommittedInstance::<Projective> {
512 cmE: Projective::rand(&mut rng),
513 u: Fr::rand(&mut rng),
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 cmT = Projective::rand(&mut rng);
520
521 // compute the challenge natively
522 let r_bits = CycleFoldChallengeGadget::<Projective, GVar>::get_challenge_native(
523 &poseidon_config,
524 U_i.clone(),
525 u_i.clone(),
526 cmT,
527 )
528 .unwrap();
529
530 let cs = ConstraintSystem::<Fq>::new_ref();
531 let u_iVar =
532 CycleFoldCommittedInstanceVar::<Projective, GVar>::new_witness(cs.clone(), || {
533 Ok(u_i.clone())
534 })
535 .unwrap();
536 let U_iVar =
537 CycleFoldCommittedInstanceVar::<Projective, GVar>::new_witness(cs.clone(), || {
538 Ok(U_i.clone())
539 })
540 .unwrap();
541 let cmTVar = GVar::new_witness(cs.clone(), || Ok(cmT)).unwrap();
542
543 let r_bitsVar = CycleFoldChallengeGadget::<Projective, GVar>::get_challenge_gadget(
544 cs.clone(),
545 &poseidon_config,
546 U_iVar,
547 u_iVar,
548 cmTVar,
549 )
550 .unwrap();
551 assert!(cs.is_satisfied().unwrap());
552
553 // check that the natively computed and in-circuit computed hashes match
554 let rVar = Boolean::le_bits_to_fp_var(&r_bitsVar).unwrap();
555 let r = Fq::from_bigint(BigInteger::from_bits_le(&r_bits)).unwrap();
556 assert_eq!(rVar.value().unwrap(), r);
557 assert_eq!(r_bitsVar.value().unwrap(), r_bits);
558 }
559}