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}