/// contains [Nova](https://eprint.iacr.org/2021/370.pdf) related circuits use ark_crypto_primitives::crh::{ poseidon::constraints::{CRHGadget, CRHParametersVar}, CRHSchemeGadget, }; use ark_crypto_primitives::sponge::{ constraints::CryptographicSpongeVar, poseidon::{constraints::PoseidonSpongeVar, PoseidonConfig, PoseidonSponge}, Absorb, CryptographicSponge, }; use ark_ec::{AffineRepr, CurveGroup, Group}; use ark_ff::{Field, PrimeField}; use ark_r1cs_std::{ alloc::{AllocVar, AllocationMode}, boolean::Boolean, eq::EqGadget, fields::{fp::FpVar, nonnative::NonNativeFieldVar, FieldVar}, groups::GroupOpsBounds, prelude::CurveVar, ToBitsGadget, ToConstraintFieldGadget, }; use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystemRef, Namespace, SynthesisError}; use ark_std::fmt::Debug; use ark_std::{One, Zero}; use core::{borrow::Borrow, marker::PhantomData}; use super::{ cyclefold::{ CycleFoldChallengeGadget, CycleFoldCommittedInstanceVar, NIFSFullGadget, CF_IO_LEN, }, CommittedInstance, }; use crate::constants::N_BITS_RO; use crate::folding::circuits::nonnative::{point_to_nonnative_limbs, NonNativeAffineVar}; use crate::frontend::FCircuit; /// CF1 represents the ConstraintField used for the main Nova circuit which is over E1::Fr, where /// E1 is the main curve where we do the folding. pub type CF1 = <::Affine as AffineRepr>::ScalarField; /// CF2 represents the ConstraintField used for the CycleFold circuit which is over E2::Fr=E1::Fq, /// where E2 is the auxiliary curve (from [CycleFold](https://eprint.iacr.org/2023/1192.pdf) /// approach) where we check the folding of the commitments (elliptic curve points). pub type CF2 = <::BaseField as Field>::BasePrimeField; /// CommittedInstanceVar contains the u, x, cmE and cmW values which are folded on the main Nova /// constraints field (E1::Fr, where E1 is the main curve). The peculiarity is that cmE and cmW are /// represented non-natively over the constraint field. #[derive(Debug, Clone)] pub struct CommittedInstanceVar where ::BaseField: ark_ff::PrimeField, { pub u: FpVar, pub x: Vec>, pub cmE: NonNativeAffineVar, pub cmW: NonNativeAffineVar, } impl AllocVar, CF1> for CommittedInstanceVar where C: CurveGroup, ::BaseField: PrimeField, { fn new_variable>>( cs: impl Into>>, f: impl FnOnce() -> Result, mode: AllocationMode, ) -> Result { f().and_then(|val| { let cs = cs.into(); let u = FpVar::::new_variable(cs.clone(), || Ok(val.borrow().u), mode)?; let x: Vec> = Vec::new_variable(cs.clone(), || Ok(val.borrow().x.clone()), mode)?; let cmE = NonNativeAffineVar::::new_variable(cs.clone(), || Ok(val.borrow().cmE), mode)?; let cmW = NonNativeAffineVar::::new_variable(cs.clone(), || Ok(val.borrow().cmW), mode)?; Ok(Self { u, x, cmE, cmW }) }) } } impl CommittedInstanceVar where C: CurveGroup, ::ScalarField: Absorb, ::BaseField: ark_ff::PrimeField, { /// hash implements the committed instance hash compatible with the native implementation from /// CommittedInstance.hash. /// Returns `H(i, z_0, z_i, U_i)`, where `i` can be `i` but also `i+1`, and `U` is the /// `CommittedInstance`. /// Additionally it returns the vector of the field elements from the self parameters, so they /// can be reused in other gadgets avoiding recalculating (reconstraining) them. #[allow(clippy::type_complexity)] pub fn hash( self, crh_params: &CRHParametersVar>, i: FpVar>, z_0: Vec>>, z_i: Vec>>, ) -> Result<(FpVar>, Vec>>), SynthesisError> { let U_vec = [ vec![self.u], self.x, self.cmE.x.to_constraint_field()?, self.cmE.y.to_constraint_field()?, self.cmW.x.to_constraint_field()?, self.cmW.y.to_constraint_field()?, ] .concat(); let input = [vec![i], z_0, z_i, U_vec.clone()].concat(); Ok(( CRHGadget::::evaluate(crh_params, &input)?, U_vec, )) } } /// Implements the circuit that does the checks of the Non-Interactive Folding Scheme Verifier /// described in section 4 of [Nova](https://eprint.iacr.org/2021/370.pdf), where the cmE & cmW checks are /// delegated to the NIFSCycleFoldGadget. pub struct NIFSGadget { _c: PhantomData, } impl NIFSGadget where C: CurveGroup, ::BaseField: ark_ff::PrimeField, { /// Implements the constraints for NIFS.V for u and x, since cm(E) and cm(W) are delegated to /// the CycleFold circuit. pub fn verify( r: FpVar>, ci1: CommittedInstanceVar, // U_i ci2: CommittedInstanceVar, // u_i ci3: CommittedInstanceVar, // U_{i+1} ) -> Result>, SynthesisError> { // ensure that: ci3.u == ci1.u + r * ci2.u let first_check = ci3.u.is_eq(&(ci1.u + r.clone() * ci2.u))?; // ensure that: ci3.x == ci1.x + r * ci2.x let x_rlc = ci1 .x .iter() .zip(ci2.x) .map(|(a, b)| a + &r * &b) .collect::>>>(); let second_check = x_rlc.is_eq(&ci3.x)?; first_check.and(&second_check) } } /// ChallengeGadget computes the RO challenge used for the Nova instances NIFS, it contains a /// rust-native and a in-circuit compatible versions. pub struct ChallengeGadget { _c: PhantomData, } impl ChallengeGadget where C: CurveGroup, ::BaseField: PrimeField, ::ScalarField: Absorb, { pub fn get_challenge_native( poseidon_config: &PoseidonConfig, U_i: CommittedInstance, u_i: CommittedInstance, cmT: C, ) -> Result, SynthesisError> { let (U_cmE_x, U_cmE_y) = point_to_nonnative_limbs::(U_i.cmE)?; let (U_cmW_x, U_cmW_y) = point_to_nonnative_limbs::(U_i.cmW)?; let (u_cmE_x, u_cmE_y) = point_to_nonnative_limbs::(u_i.cmE)?; let (u_cmW_x, u_cmW_y) = point_to_nonnative_limbs::(u_i.cmW)?; let (cmT_x, cmT_y) = point_to_nonnative_limbs::(cmT)?; let mut sponge = PoseidonSponge::::new(poseidon_config); let input = vec![ vec![U_i.u], U_i.x.clone(), U_cmE_x, U_cmE_y, U_cmW_x, U_cmW_y, vec![u_i.u], u_i.x.clone(), u_cmE_x, u_cmE_y, u_cmW_x, u_cmW_y, cmT_x, cmT_y, ] .concat(); sponge.absorb(&input); let bits = sponge.squeeze_bits(N_BITS_RO); Ok(bits) } // compatible with the native get_challenge_native pub fn get_challenge_gadget( cs: ConstraintSystemRef, poseidon_config: &PoseidonConfig, U_i_vec: Vec>>, // apready processed input, so we don't have to recompute these values u_i: CommittedInstanceVar, cmT: NonNativeAffineVar, ) -> Result>, SynthesisError> { let mut sponge = PoseidonSpongeVar::::new(cs, poseidon_config); let input: Vec> = vec![ U_i_vec, vec![u_i.u.clone()], u_i.x.clone(), u_i.cmE.x.to_constraint_field()?, u_i.cmE.y.to_constraint_field()?, u_i.cmW.x.to_constraint_field()?, u_i.cmW.y.to_constraint_field()?, cmT.x.to_constraint_field()?, cmT.y.to_constraint_field()?, ] .concat(); sponge.absorb(&input)?; let bits = sponge.squeeze_bits(N_BITS_RO)?; Ok(bits) } } /// AugmentedFCircuit implements the F' circuit (augmented F) defined in /// [Nova](https://eprint.iacr.org/2021/370.pdf) together with the extra constraints defined in /// [CycleFold](https://eprint.iacr.org/2023/1192.pdf). #[derive(Debug, Clone)] pub struct AugmentedFCircuit< C1: CurveGroup, C2: CurveGroup, GC2: CurveVar>, FC: FCircuit>, > where for<'a> &'a GC2: GroupOpsBounds<'a, C2, GC2>, { pub _gc2: PhantomData, pub poseidon_config: PoseidonConfig>, pub i: Option>, pub i_usize: Option, pub z_0: Option>, pub z_i: Option>, pub u_i: Option>, pub U_i: Option>, pub U_i1: Option>, pub cmT: Option, pub F: FC, // F circuit pub x: Option>, // public inputs (u_{i+1}.x) // cyclefold verifier on C1 // Here 'cf1, cf2' are for each of the CycleFold circuits, corresponding to the fold of cmW and // cmE respectively pub cf1_u_i: Option>, // input pub cf2_u_i: Option>, // input pub cf_U_i: Option>, // input pub cf1_U_i1: Option>, // intermediate pub cf_U_i1: Option>, // output pub cf1_cmT: Option, pub cf2_cmT: Option, pub cf1_r_nonnat: Option, pub cf2_r_nonnat: Option, } impl>, FC: FCircuit>> AugmentedFCircuit where for<'a> &'a GC2: GroupOpsBounds<'a, C2, GC2>, { pub fn empty(poseidon_config: &PoseidonConfig>, F_circuit: FC) -> Self { Self { _gc2: PhantomData, poseidon_config: poseidon_config.clone(), i: None, i_usize: None, z_0: None, z_i: None, u_i: None, U_i: None, U_i1: None, cmT: None, F: F_circuit, x: None, // cyclefold values cf1_u_i: None, cf2_u_i: None, cf_U_i: None, cf1_U_i1: None, cf_U_i1: None, cf1_cmT: None, cf2_cmT: None, cf1_r_nonnat: None, cf2_r_nonnat: None, } } } impl ConstraintSynthesizer> for AugmentedFCircuit where C1: CurveGroup, C2: CurveGroup, GC2: CurveVar>, FC: FCircuit>, ::BaseField: PrimeField, ::BaseField: PrimeField, ::ScalarField: Absorb, ::ScalarField: Absorb, C1: CurveGroup, for<'a> &'a GC2: GroupOpsBounds<'a, C2, GC2>, { fn generate_constraints(self, cs: ConstraintSystemRef>) -> Result<(), SynthesisError> { let i = FpVar::>::new_witness(cs.clone(), || { Ok(self.i.unwrap_or_else(CF1::::zero)) })?; let z_0 = Vec::>>::new_witness(cs.clone(), || { Ok(self .z_0 .unwrap_or(vec![CF1::::zero(); self.F.state_len()])) })?; let z_i = Vec::>>::new_witness(cs.clone(), || { Ok(self .z_i .unwrap_or(vec![CF1::::zero(); self.F.state_len()])) })?; let u_dummy_native = CommittedInstance::::dummy(1); let u_i = CommittedInstanceVar::::new_witness(cs.clone(), || { Ok(self.u_i.unwrap_or(u_dummy_native.clone())) })?; let U_i = CommittedInstanceVar::::new_witness(cs.clone(), || { Ok(self.U_i.unwrap_or(u_dummy_native.clone())) })?; let U_i1 = CommittedInstanceVar::::new_witness(cs.clone(), || { Ok(self.U_i1.unwrap_or(u_dummy_native.clone())) })?; let cmT = NonNativeAffineVar::new_witness(cs.clone(), || Ok(self.cmT.unwrap_or_else(C1::zero)))?; let x = FpVar::>::new_input(cs.clone(), || Ok(self.x.unwrap_or_else(CF1::::zero)))?; let crh_params = CRHParametersVar::::new_constant( cs.clone(), self.poseidon_config.clone(), )?; // get z_{i+1} from the F circuit let i_usize = self.i_usize.unwrap_or(0); let z_i1 = self .F .generate_step_constraints(cs.clone(), i_usize, z_i.clone())?; let zero = FpVar::>::new_constant(cs.clone(), CF1::::zero())?; let is_not_basecase = i.is_neq(&zero)?; // 1. u_i.x == H(i, z_0, z_i, U_i) let (u_i_x, U_i_vec) = U_i.clone() .hash(&crh_params, i.clone(), z_0.clone(), z_i.clone())?; // check that h == u_i.x (u_i.x[0]).conditional_enforce_equal(&u_i_x, &is_not_basecase)?; // 2. u_i.cmE==cm(0), u_i.u==1 let zero_x = NonNativeFieldVar::::new_constant( cs.clone(), C1::BaseField::zero(), )?; let zero_y = NonNativeFieldVar::::new_constant( cs.clone(), C1::BaseField::one(), )?; (u_i.cmE.x.is_eq(&zero_x)?).conditional_enforce_equal(&Boolean::TRUE, &is_not_basecase)?; (u_i.cmE.y.is_eq(&zero_y)?).conditional_enforce_equal(&Boolean::TRUE, &is_not_basecase)?; (u_i.u.is_one()?).conditional_enforce_equal(&Boolean::TRUE, &is_not_basecase)?; // 3. nifs.verify, checks that folding u_i & U_i obtains U_{i+1}. // compute r = H(u_i, U_i, cmT) let r_bits = ChallengeGadget::::get_challenge_gadget( cs.clone(), &self.poseidon_config, U_i_vec, u_i.clone(), cmT.clone(), )?; let r = Boolean::le_bits_to_fp_var(&r_bits)?; // Notice that NIFSGadget::verify is not checking the folding of cmE & cmW, since it will // be done on the other curve. let nifs_check = NIFSGadget::::verify(r, U_i.clone(), u_i.clone(), U_i1.clone())?; nifs_check.conditional_enforce_equal(&Boolean::TRUE, &is_not_basecase)?; // 4. u_{i+1}.x = H(i+1, z_0, z_i+1, U_{i+1}), this is the output of F' let (u_i1_x, _) = U_i1.clone().hash( &crh_params, i + FpVar::>::one(), z_0.clone(), z_i1.clone(), )?; u_i1_x.enforce_equal(&x)?; // CycleFold part let cf_u_dummy_native = CommittedInstance::::dummy(CF_IO_LEN); // cf W circuit data let cf1_u_i = CycleFoldCommittedInstanceVar::::new_witness(cs.clone(), || { Ok(self.cf1_u_i.unwrap_or_else(|| cf_u_dummy_native.clone())) })?; let cf2_u_i = CycleFoldCommittedInstanceVar::::new_witness(cs.clone(), || { Ok(self.cf2_u_i.unwrap_or_else(|| cf_u_dummy_native.clone())) })?; let cf_U_i = CycleFoldCommittedInstanceVar::::new_witness(cs.clone(), || { Ok(self.cf_U_i.unwrap_or_else(|| cf_u_dummy_native.clone())) })?; let cf1_U_i1 = CycleFoldCommittedInstanceVar::::new_witness(cs.clone(), || { Ok(self.cf1_U_i1.unwrap_or_else(|| cf_u_dummy_native.clone())) })?; let cf_U_i1 = CycleFoldCommittedInstanceVar::::new_witness(cs.clone(), || { Ok(self.cf_U_i1.unwrap_or_else(|| cf_u_dummy_native.clone())) })?; let cf1_cmT = GC2::new_witness(cs.clone(), || Ok(self.cf1_cmT.unwrap_or_else(C2::zero)))?; let cf2_cmT = GC2::new_witness(cs.clone(), || Ok(self.cf2_cmT.unwrap_or_else(C2::zero)))?; let cf1_r_nonnat = NonNativeFieldVar::::new_witness(cs.clone(), || { Ok(self.cf1_r_nonnat.unwrap_or_else(C2::ScalarField::zero)) })?; let cf2_r_nonnat = NonNativeFieldVar::::new_witness(cs.clone(), || { Ok(self.cf2_r_nonnat.unwrap_or_else(C2::ScalarField::zero)) })?; let cfW_x: Vec> = vec![ U_i.cmW.x, U_i.cmW.y, u_i.cmW.x, u_i.cmW.y, U_i1.cmW.x, U_i1.cmW.y, ]; let cfE_x: Vec> = vec![ U_i.cmE.x, U_i.cmE.y, u_i.cmE.x, u_i.cmE.y, U_i1.cmE.x, U_i1.cmE.y, ]; // ensure that cf1_u & cf2_u have as public inputs the cmW & cmE from main instances U_i, // u_i, U_i+1 coordinates of the commitments cf1_u_i .x .conditional_enforce_equal(&cfW_x, &is_not_basecase)?; cf2_u_i .x .conditional_enforce_equal(&cfE_x, &is_not_basecase)?; // cf_r_bits is denoted by rho* in the paper. // assert that cf_r_bits == cf_r_nonnat converted to bits. cf_r_nonnat is just an auxiliary // value used to compute RLC of NonNativeFieldVar values, since we can convert // NonNativeFieldVar into Vec, but not in the other direction. let cf1_r_bits = CycleFoldChallengeGadget::::get_challenge_gadget( cs.clone(), &self.poseidon_config, cf_U_i.clone(), cf1_u_i.clone(), cf1_cmT.clone(), )?; let cf1_r_nonnat_bits = cf1_r_nonnat.to_bits_le()?; cf1_r_bits.conditional_enforce_equal(&cf1_r_nonnat_bits[..N_BITS_RO], &is_not_basecase)?; // same for cf2_r: let cf2_r_bits = CycleFoldChallengeGadget::::get_challenge_gadget( cs.clone(), &self.poseidon_config, cf1_U_i1.clone(), cf2_u_i.clone(), cf2_cmT.clone(), )?; let cf2_r_nonnat_bits = cf2_r_nonnat.to_bits_le()?; cf2_r_bits.conditional_enforce_equal(&cf2_r_nonnat_bits[..N_BITS_RO], &is_not_basecase)?; // check cf_u_i.cmE=0, cf_u_i.u=1 (cf1_u_i.cmE.is_zero()?).conditional_enforce_equal(&Boolean::TRUE, &is_not_basecase)?; (cf1_u_i.u.is_one()?).conditional_enforce_equal(&Boolean::TRUE, &is_not_basecase)?; (cf2_u_i.cmE.is_zero()?).conditional_enforce_equal(&Boolean::TRUE, &is_not_basecase)?; (cf2_u_i.u.is_one()?).conditional_enforce_equal(&Boolean::TRUE, &is_not_basecase)?; // check the fold of all the parameters of the CycleFold instances, where the elliptic // curve points relations are checked natively in Curve1 circuit (this one) let v1 = NIFSFullGadget::::verify( cf1_r_bits, cf1_r_nonnat, cf1_cmT, cf_U_i, cf1_u_i, cf1_U_i1.clone(), )?; v1.conditional_enforce_equal(&Boolean::TRUE, &is_not_basecase)?; let v2 = NIFSFullGadget::::verify( cf2_r_bits, cf2_r_nonnat, cf2_cmT, cf1_U_i1, // the output from NIFS.V(cf1_r, cf_U, cfE_u) cf2_u_i, cf_U_i1, )?; v2.conditional_enforce_equal(&Boolean::TRUE, &is_not_basecase)?; Ok(()) } } #[cfg(test)] pub mod tests { use super::*; use ark_bn254::{Fr, G1Projective as Projective}; use ark_ff::BigInteger; use ark_r1cs_std::{alloc::AllocVar, R1CSVar}; use ark_relations::r1cs::ConstraintSystem; use ark_std::UniformRand; use crate::commitment::pedersen::Pedersen; use crate::folding::nova::nifs::tests::prepare_simple_fold_inputs; use crate::folding::nova::nifs::NIFS; use crate::transcript::poseidon::poseidon_test_config; #[test] fn test_committed_instance_var() { let mut rng = ark_std::test_rng(); let ci = CommittedInstance:: { cmE: Projective::rand(&mut rng), u: Fr::rand(&mut rng), cmW: Projective::rand(&mut rng), x: vec![Fr::rand(&mut rng); 1], }; let cs = ConstraintSystem::::new_ref(); let ciVar = CommittedInstanceVar::::new_witness(cs.clone(), || Ok(ci.clone())).unwrap(); assert_eq!(ciVar.u.value().unwrap(), ci.u); assert_eq!(ciVar.x.value().unwrap(), ci.x); // the values cmE and cmW are checked in the CycleFold's circuit // CommittedInstanceInCycleFoldVar in // nova::cyclefold::tests::test_committed_instance_cyclefold_var } #[test] fn test_nifs_gadget() { let (_, _, _, _, ci1, _, ci2, _, ci3, _, cmT, _, r_Fr) = prepare_simple_fold_inputs(); let ci3_verifier = NIFS::>::verify(r_Fr, &ci1, &ci2, &cmT); assert_eq!(ci3_verifier, ci3); let cs = ConstraintSystem::::new_ref(); let rVar = FpVar::::new_witness(cs.clone(), || Ok(r_Fr)).unwrap(); let ci1Var = CommittedInstanceVar::::new_witness(cs.clone(), || Ok(ci1.clone())) .unwrap(); let ci2Var = CommittedInstanceVar::::new_witness(cs.clone(), || Ok(ci2.clone())) .unwrap(); let ci3Var = CommittedInstanceVar::::new_witness(cs.clone(), || Ok(ci3.clone())) .unwrap(); let nifs_check = NIFSGadget::::verify( rVar.clone(), ci1Var.clone(), ci2Var.clone(), ci3Var.clone(), ) .unwrap(); nifs_check.enforce_equal(&Boolean::::TRUE).unwrap(); assert!(cs.is_satisfied().unwrap()); } #[test] fn test_committed_instance_hash() { let mut rng = ark_std::test_rng(); let poseidon_config = poseidon_test_config::(); let i = Fr::from(3_u32); let z_0 = vec![Fr::from(3_u32)]; let z_i = vec![Fr::from(3_u32)]; let ci = CommittedInstance:: { cmE: Projective::rand(&mut rng), u: Fr::rand(&mut rng), cmW: Projective::rand(&mut rng), x: vec![Fr::rand(&mut rng); 1], }; // compute the CommittedInstance hash natively let h = ci .hash(&poseidon_config, i, z_0.clone(), z_i.clone()) .unwrap(); let cs = ConstraintSystem::::new_ref(); let iVar = FpVar::::new_witness(cs.clone(), || Ok(i)).unwrap(); let z_0Var = Vec::>::new_witness(cs.clone(), || Ok(z_0.clone())).unwrap(); let z_iVar = Vec::>::new_witness(cs.clone(), || Ok(z_i.clone())).unwrap(); let ciVar = CommittedInstanceVar::::new_witness(cs.clone(), || Ok(ci.clone())).unwrap(); let crh_params = CRHParametersVar::::new_constant(cs.clone(), poseidon_config).unwrap(); // compute the CommittedInstance hash in-circuit let (hVar, _) = ciVar.hash(&crh_params, iVar, z_0Var, z_iVar).unwrap(); assert!(cs.is_satisfied().unwrap()); // check that the natively computed and in-circuit computed hashes match assert_eq!(hVar.value().unwrap(), h); } // checks that the gadget and native implementations of the challenge computation match #[test] fn test_challenge_gadget() { let mut rng = ark_std::test_rng(); let poseidon_config = poseidon_test_config::(); let u_i = CommittedInstance:: { cmE: Projective::rand(&mut rng), u: Fr::rand(&mut rng), cmW: Projective::rand(&mut rng), x: vec![Fr::rand(&mut rng); 1], }; let U_i = CommittedInstance:: { cmE: Projective::rand(&mut rng), u: Fr::rand(&mut rng), cmW: Projective::rand(&mut rng), x: vec![Fr::rand(&mut rng); 1], }; let cmT = Projective::rand(&mut rng); // compute the challenge natively let r_bits = ChallengeGadget::::get_challenge_native( &poseidon_config, U_i.clone(), u_i.clone(), cmT, ) .unwrap(); let r = Fr::from_bigint(BigInteger::from_bits_le(&r_bits)).unwrap(); let cs = ConstraintSystem::::new_ref(); let u_iVar = CommittedInstanceVar::::new_witness(cs.clone(), || Ok(u_i.clone())) .unwrap(); let U_iVar = CommittedInstanceVar::::new_witness(cs.clone(), || Ok(U_i.clone())) .unwrap(); let cmTVar = NonNativeAffineVar::::new_witness(cs.clone(), || Ok(cmT)).unwrap(); // compute the challenge in-circuit let U_iVar_vec = [ vec![U_iVar.u.clone()], U_iVar.x.clone(), U_iVar.cmE.x.to_constraint_field().unwrap(), U_iVar.cmE.y.to_constraint_field().unwrap(), U_iVar.cmW.x.to_constraint_field().unwrap(), U_iVar.cmW.y.to_constraint_field().unwrap(), ] .concat(); let r_bitsVar = ChallengeGadget::::get_challenge_gadget( cs.clone(), &poseidon_config, U_iVar_vec, u_iVar, cmTVar, ) .unwrap(); assert!(cs.is_satisfied().unwrap()); // check that the natively computed and in-circuit computed hashes match let rVar = Boolean::le_bits_to_fp_var(&r_bitsVar).unwrap(); assert_eq!(rVar.value().unwrap(), r); assert_eq!(r_bitsVar.value().unwrap(), r_bits); } }