arnaucube
arnaucube/sonobePublic Self-hosted repository
sonobe/folding-schemes/src/folding/protogalaxy/circuits.rs
folding-schemes/src/folding/protogalaxy/circuits.rsRaw
  1use ark_crypto_primitives::sponge::{
  2    constraints::CryptographicSpongeVar,
  3    poseidon::{constraints::PoseidonSpongeVar, PoseidonConfig, PoseidonSponge},
  4    Absorb, CryptographicSponge,
  5};
  6use ark_ec::CurveGroup;
  7use ark_ff::PrimeField;
  8use ark_poly::{univariate::DensePolynomial, EvaluationDomain, GeneralEvaluationDomain};
  9use ark_r1cs_std::{
 10    alloc::AllocVar,
 11    boolean::Boolean,
 12    eq::EqGadget,
 13    fields::{fp::FpVar, FieldVar},
 14    groups::{CurveVar, GroupOpsBounds},
 15    poly::polynomial::univariate::dense::DensePolynomialVar,
 16    R1CSVar, ToBitsGadget, ToConstraintFieldGadget,
 17};
 18use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystemRef, SynthesisError};
 19use ark_std::{fmt::Debug, marker::PhantomData, One, Zero};
 20
 21use super::{
 22    folding::lagrange_polys,
 23    utils::{all_powers_var, betas_star_var, exponential_powers_var},
 24    CommittedInstance, CommittedInstanceVar, ProtoGalaxyCycleFoldConfig,
 25};
 26use crate::{
 27    folding::{
 28        circuits::{
 29            cyclefold::{
 30                CycleFoldChallengeGadget, CycleFoldCommittedInstance,
 31                CycleFoldCommittedInstanceVar, CycleFoldConfig, NIFSFullGadget,
 32            },
 33            nonnative::{affine::NonNativeAffineVar, uint::NonNativeUintVar},
 34            CF1, CF2,
 35        },
 36        traits::{CommittedInstanceVarOps, Dummy},
 37    },
 38    frontend::FCircuit,
 39    transcript::{AbsorbNonNativeGadget, TranscriptVar},
 40    utils::gadgets::VectorGadget,
 41};
 42
 43pub struct FoldingGadget {}
 44
 45impl FoldingGadget {
 46    #[allow(clippy::type_complexity)]
 47    pub fn fold_committed_instance<C: CurveGroup, S: CryptographicSponge>(
 48        transcript: &mut impl TranscriptVar<C::ScalarField, S>,
 49        // running instance
 50        instance: &CommittedInstanceVar<C, true>,
 51        // incoming instances
 52        vec_instances: &[CommittedInstanceVar<C, false>],
 53        // polys from P
 54        F_coeffs: Vec<FpVar<C::ScalarField>>,
 55        K_coeffs: Vec<FpVar<C::ScalarField>>,
 56    ) -> Result<(CommittedInstanceVar<C, true>, Vec<FpVar<C::ScalarField>>), SynthesisError> {
 57        let t = instance.betas.len();
 58
 59        // absorb the committed instances
 60        transcript.absorb(instance)?;
 61        transcript.absorb(&vec_instances)?;
 62
 63        let delta = transcript.get_challenge()?;
 64        let deltas = exponential_powers_var(delta, t);
 65
 66        transcript.absorb(&F_coeffs)?;
 67
 68        let alpha = transcript.get_challenge()?;
 69        let alphas = all_powers_var(alpha.clone(), t);
 70
 71        // F(alpha) = e + \sum_t F_i * alpha^i
 72        let mut F_alpha = instance.e.clone();
 73        for (i, F_i) in F_coeffs.iter().skip(1).enumerate() {
 74            F_alpha += F_i * &alphas[i + 1];
 75        }
 76
 77        let betas_star = betas_star_var(&instance.betas, &deltas, &alpha);
 78
 79        let k = vec_instances.len();
 80        let H = GeneralEvaluationDomain::new(k + 1).unwrap();
 81        let L_X = lagrange_polys(H)
 82            .into_iter()
 83            .map(|poly| {
 84                DensePolynomialVar::from_coefficients_vec(
 85                    poly.coeffs
 86                        .into_iter()
 87                        .map(FpVar::constant)
 88                        .collect::<Vec<_>>(),
 89                )
 90            })
 91            .collect::<Vec<_>>();
 92        let Z_X = DensePolynomialVar::from_coefficients_vec(
 93            DensePolynomial::from(H.vanishing_polynomial())
 94                .coeffs
 95                .into_iter()
 96                .map(FpVar::constant)
 97                .collect::<Vec<_>>(),
 98        );
 99        let K_X = DensePolynomialVar { coeffs: K_coeffs };
100
101        transcript.absorb(&K_X.coeffs)?;
102
103        let gamma = transcript.get_challenge()?;
104
105        let L_X_evals = L_X
106            .iter()
107            .take(k + 1)
108            .map(|L| L.evaluate(&gamma))
109            .collect::<Result<Vec<_>, _>>()?;
110
111        let e_star = F_alpha * &L_X_evals[0] + Z_X.evaluate(&gamma)? * K_X.evaluate(&gamma)?;
112
113        let mut x_star = instance.x.mul_scalar(&L_X_evals[0])?;
114        for i in 0..k {
115            x_star = x_star.add(&vec_instances[i].x.mul_scalar(&L_X_evals[i + 1])?)?;
116        }
117
118        // return the folded instance
119        Ok((
120            CommittedInstanceVar {
121                betas: betas_star,
122                // phi will be computed in CycleFold
123                phi: NonNativeAffineVar::new_constant(ConstraintSystemRef::None, C::zero())?,
124                e: e_star,
125                x: x_star,
126            },
127            L_X_evals,
128        ))
129    }
130}
131
132pub struct AugmentationGadget;
133
134impl AugmentationGadget {
135    #[allow(clippy::type_complexity)]
136    pub fn prepare_and_fold_primary<C: CurveGroup, S: CryptographicSponge>(
137        transcript: &mut impl TranscriptVar<CF1<C>, S>,
138        U: CommittedInstanceVar<C, true>,
139        u_phis: Vec<NonNativeAffineVar<C>>,
140        u_xs: Vec<Vec<FpVar<CF1<C>>>>,
141        new_U_phi: NonNativeAffineVar<C>,
142        F_coeffs: Vec<FpVar<CF1<C>>>,
143        K_coeffs: Vec<FpVar<CF1<C>>>,
144    ) -> Result<(CommittedInstanceVar<C, true>, Vec<FpVar<CF1<C>>>), SynthesisError> {
145        assert_eq!(u_phis.len(), u_xs.len());
146
147        // Prepare the incoming instances.
148        // For each instance `u`, we have `u.betas = []`, `u.e = 0`.
149        let us = u_phis
150            .into_iter()
151            .zip(u_xs)
152            .map(|(phi, x)| CommittedInstanceVar {
153                phi,
154                betas: vec![],
155                e: FpVar::zero(),
156                x,
157            })
158            .collect::<Vec<_>>();
159
160        // Fold the incoming instances `us` into the running instance `U`.
161        let (mut U, L_X_evals) =
162            FoldingGadget::fold_committed_instance(transcript, &U, &us, F_coeffs, K_coeffs)?;
163        // Notice that FoldingGadget::fold_committed_instance does not fold phi.
164        // We set `U.phi` to unconstrained witnesses `U_phi` here, whose
165        // correctness will be checked on the other curve.
166        U.phi = new_U_phi;
167
168        Ok((U, L_X_evals))
169    }
170
171    pub fn prepare_and_fold_cyclefold<
172        C1: CurveGroup<BaseField = C2::ScalarField, ScalarField = C2::BaseField>,
173        C2: CurveGroup,
174        GC2: CurveVar<C2, CF2<C2>> + ToConstraintFieldGadget<CF2<C2>>,
175        S: CryptographicSponge,
176    >(
177        transcript: &mut PoseidonSpongeVar<CF1<C1>>,
178        pp_hash: FpVar<CF1<C1>>,
179        mut cf_U: CycleFoldCommittedInstanceVar<C2, GC2>,
180        cf_u_cmWs: Vec<GC2>,
181        cf_u_xs: Vec<Vec<NonNativeUintVar<CF1<C1>>>>,
182        cf_cmTs: Vec<GC2>,
183    ) -> Result<CycleFoldCommittedInstanceVar<C2, GC2>, SynthesisError>
184    where
185        C2::BaseField: PrimeField + Absorb,
186        for<'a> &'a GC2: GroupOpsBounds<'a, C2, GC2>,
187    {
188        assert_eq!(cf_u_cmWs.len(), cf_u_xs.len());
189        assert_eq!(cf_u_xs.len(), cf_cmTs.len());
190
191        // Fold the incoming CycleFold instances into the running CycleFold
192        // instance in a iterative way, since `NIFSFullGadget` only supports
193        // folding one incoming instance at a time.
194        for ((cmW, x), cmT) in cf_u_cmWs.into_iter().zip(cf_u_xs).zip(cf_cmTs) {
195            // Prepare the incoming CycleFold instance `cf_u` for the current
196            // iteration.
197            // For each CycleFold instance `cf_u`, we have `cf_u.cmE = 0`, and
198            // `cf_u.u = 1`.
199            let cf_u = CycleFoldCommittedInstanceVar {
200                cmE: GC2::zero(),
201                u: NonNativeUintVar::new_constant(ConstraintSystemRef::None, C1::BaseField::one())?,
202                cmW,
203                x,
204            };
205
206            let cf_r_bits = CycleFoldChallengeGadget::get_challenge_gadget(
207                transcript,
208                pp_hash.clone(),
209                cf_U.to_native_sponge_field_elements()?,
210                cf_u.clone(),
211                cmT.clone(),
212            )?;
213            // Fold the current incoming CycleFold instance `cf_u` into the
214            // running CycleFold instance `cf_U`.
215            cf_U = NIFSFullGadget::fold_committed_instance(cf_r_bits, cmT, cf_U, cf_u)?;
216        }
217
218        Ok(cf_U)
219    }
220}
221
222/// `AugmentedFCircuit` enhances the original step function `F`, so that it can
223/// be used in recursive arguments such as IVC.
224///
225/// The method for converting `F` to `AugmentedFCircuit` (`F'`) is defined in
226/// [Nova](https://eprint.iacr.org/2021/370.pdf), where `AugmentedFCircuit` not
227/// only invokes `F`, but also adds additional constraints for verifying the
228/// correct folding of primary instances (i.e., the instances over `C1`).
229/// In the paper, the primary instances are Nova's `CommittedInstance`, but we
230/// extend this method to support using ProtoGalaxy's `CommittedInstance` as
231/// primary instances.
232///
233/// Furthermore, to reduce circuit size over `C2`, we implement the constraints
234/// defined in [CycleFold](https://eprint.iacr.org/2023/1192.pdf). These extra
235/// constraints verify the correct folding of CycleFold instances.
236#[derive(Debug, Clone)]
237pub struct AugmentedFCircuit<
238    C1: CurveGroup,
239    C2: CurveGroup,
240    GC2: CurveVar<C2, CF2<C2>>,
241    FC: FCircuit<CF1<C1>>,
242> {
243    pub(super) _gc2: PhantomData<GC2>,
244    pub(super) poseidon_config: PoseidonConfig<CF1<C1>>,
245    pub(super) pp_hash: CF1<C1>,
246    pub(super) i: CF1<C1>,
247    pub(super) i_usize: usize,
248    pub(super) z_0: Vec<CF1<C1>>,
249    pub(super) z_i: Vec<CF1<C1>>,
250    pub(super) external_inputs: Vec<CF1<C1>>,
251    pub(super) F: FC, // F circuit
252    pub(super) u_i_phi: C1,
253    pub(super) U_i: CommittedInstance<C1, true>,
254    pub(super) U_i1_phi: C1,
255    pub(super) F_coeffs: Vec<CF1<C1>>,
256    pub(super) K_coeffs: Vec<CF1<C1>>,
257    pub(super) x: Option<CF1<C1>>, // public input (u_{i+1}.x[0])
258
259    pub(super) phi_stars: Vec<C1>,
260
261    pub(super) cf1_u_i_cmW: C2,                        // input
262    pub(super) cf2_u_i_cmW: C2,                        // input
263    pub(super) cf_U_i: CycleFoldCommittedInstance<C2>, // input
264    pub(super) cf1_cmT: C2,
265    pub(super) cf2_cmT: C2,
266    pub(super) cf_x: Option<CF1<C1>>, // public input (u_{i+1}.x[1])
267}
268
269impl<C1: CurveGroup, C2: CurveGroup, GC2: CurveVar<C2, CF2<C2>>, FC: FCircuit<CF1<C1>>>
270    AugmentedFCircuit<C1, C2, GC2, FC>
271where
272    for<'a> &'a GC2: GroupOpsBounds<'a, C2, GC2>,
273{
274    pub fn empty(
275        poseidon_config: &PoseidonConfig<CF1<C1>>,
276        F_circuit: FC,
277        t: usize,
278        d: usize,
279        k: usize,
280    ) -> Self {
281        let u_dummy = CommittedInstance::dummy((2, t));
282        let cf_u_dummy =
283            CycleFoldCommittedInstance::dummy(ProtoGalaxyCycleFoldConfig::<C1>::IO_LEN);
284
285        Self {
286            _gc2: PhantomData,
287            poseidon_config: poseidon_config.clone(),
288            pp_hash: CF1::<C1>::zero(),
289            i: CF1::<C1>::zero(),
290            i_usize: 0,
291            z_0: vec![CF1::<C1>::zero(); F_circuit.state_len()],
292            z_i: vec![CF1::<C1>::zero(); F_circuit.state_len()],
293            external_inputs: vec![CF1::<C1>::zero(); F_circuit.external_inputs_len()],
294            u_i_phi: C1::zero(),
295            U_i: u_dummy,
296            U_i1_phi: C1::zero(),
297            F_coeffs: vec![CF1::<C1>::zero(); t],
298            K_coeffs: vec![CF1::<C1>::zero(); d * k + 1],
299            phi_stars: vec![C1::zero(); k],
300            F: F_circuit,
301            x: None,
302            // cyclefold values
303            cf1_u_i_cmW: C2::zero(),
304            cf2_u_i_cmW: C2::zero(),
305            cf_U_i: cf_u_dummy,
306            cf1_cmT: C2::zero(),
307            cf2_cmT: C2::zero(),
308            cf_x: None,
309        }
310    }
311}
312
313impl<C1, C2, GC2, FC> ConstraintSynthesizer<CF1<C1>> for AugmentedFCircuit<C1, C2, GC2, FC>
314where
315    C1: CurveGroup<BaseField = C2::ScalarField, ScalarField = C2::BaseField>,
316    C2: CurveGroup,
317    GC2: CurveVar<C2, CF2<C2>> + ToConstraintFieldGadget<CF2<C2>>,
318    FC: FCircuit<CF1<C1>>,
319    C2::BaseField: PrimeField + Absorb,
320    for<'a> &'a GC2: GroupOpsBounds<'a, C2, GC2>,
321{
322    fn generate_constraints(self, cs: ConstraintSystemRef<CF1<C1>>) -> Result<(), SynthesisError> {
323        let pp_hash = FpVar::<CF1<C1>>::new_witness(cs.clone(), || Ok(self.pp_hash))?;
324        let i = FpVar::<CF1<C1>>::new_witness(cs.clone(), || Ok(self.i))?;
325        let z_0 = Vec::<FpVar<CF1<C1>>>::new_witness(cs.clone(), || Ok(self.z_0))?;
326        let z_i = Vec::<FpVar<CF1<C1>>>::new_witness(cs.clone(), || Ok(self.z_i))?;
327        let external_inputs =
328            Vec::<FpVar<CF1<C1>>>::new_witness(cs.clone(), || Ok(self.external_inputs))?;
329
330        let u_dummy = CommittedInstance::<C1, true>::dummy((2, self.U_i.betas.len()));
331        let U_i = CommittedInstanceVar::<C1, true>::new_witness(cs.clone(), || Ok(self.U_i))?;
332        let u_i_phi = NonNativeAffineVar::new_witness(cs.clone(), || Ok(self.u_i_phi))?;
333        let U_i1_phi = NonNativeAffineVar::new_witness(cs.clone(), || Ok(self.U_i1_phi))?;
334        let phi_stars =
335            Vec::<NonNativeAffineVar<C1>>::new_witness(cs.clone(), || Ok(self.phi_stars))?;
336
337        let cf_u_dummy =
338            CycleFoldCommittedInstance::dummy(ProtoGalaxyCycleFoldConfig::<C1>::IO_LEN);
339        let cf_U_i =
340            CycleFoldCommittedInstanceVar::<C2, GC2>::new_witness(cs.clone(), || Ok(self.cf_U_i))?;
341        let cf1_cmT = GC2::new_witness(cs.clone(), || Ok(self.cf1_cmT))?;
342        let cf2_cmT = GC2::new_witness(cs.clone(), || Ok(self.cf2_cmT))?;
343
344        let F_coeffs = Vec::new_witness(cs.clone(), || Ok(self.F_coeffs))?;
345        let K_coeffs = Vec::new_witness(cs.clone(), || Ok(self.K_coeffs))?;
346
347        // `sponge` is for digest computation.
348        let sponge = PoseidonSpongeVar::<C1::ScalarField>::new(cs.clone(), &self.poseidon_config);
349        // `transcript` is for challenge generation.
350        let mut transcript = sponge.clone();
351
352        let is_basecase = i.is_zero()?;
353
354        // Primary Part
355        // P.1. Compute u_i.x
356        // u_i.x[0] = H(i, z_0, z_i, U_i)
357        let (u_i_x, _) = U_i.clone().hash(&sponge, &pp_hash, &i, &z_0, &z_i)?;
358        // u_i.x[1] = H(cf_U_i)
359        let (cf_u_i_x, _) = cf_U_i.clone().hash(&sponge, pp_hash.clone())?;
360
361        // P.2. Prepare incoming primary instances
362        // P.3. Fold incoming primary instances into the running instance
363        let (U_i1, r) = AugmentationGadget::prepare_and_fold_primary(
364            &mut transcript,
365            U_i.clone(),
366            vec![u_i_phi.clone()],
367            vec![vec![u_i_x, cf_u_i_x]],
368            U_i1_phi,
369            F_coeffs,
370            K_coeffs,
371        )?;
372
373        // P.4.a compute and check the first output of F'
374
375        // get z_{i+1} from the F circuit
376        let z_i1 =
377            self.F
378                .generate_step_constraints(cs.clone(), self.i_usize, z_i, external_inputs)?;
379
380        // Base case: u_{i+1}.x[0] == H((i+1, z_0, z_{i+1}, U_{\bot})
381        // Non-base case: u_{i+1}.x[0] == H((i+1, z_0, z_{i+1}, U_{i+1})
382        let (u_i1_x, _) = U_i1.clone().hash(
383            &sponge,
384            &pp_hash,
385            &(i + FpVar::<CF1<C1>>::one()),
386            &z_0,
387            &z_i1,
388        )?;
389        let (u_i1_x_base, _) = CommittedInstanceVar::new_constant(cs.clone(), u_dummy)?.hash(
390            &sponge,
391            &pp_hash,
392            &FpVar::<CF1<C1>>::one(),
393            &z_0,
394            &z_i1,
395        )?;
396        let x = FpVar::new_input(cs.clone(), || Ok(self.x.unwrap_or(u_i1_x_base.value()?)))?;
397        x.enforce_equal(&is_basecase.select(&u_i1_x_base, &u_i1_x)?)?;
398
399        // CycleFold part
400        // C.1. Compute cf1_u_i.x and cf2_u_i.x
401        let mut r0_bits = r[0].to_bits_le()?;
402        let mut r1_bits = r[1].to_bits_le()?;
403        r0_bits.resize(C1::ScalarField::MODULUS_BIT_SIZE as usize, Boolean::FALSE);
404        r1_bits.resize(C1::ScalarField::MODULUS_BIT_SIZE as usize, Boolean::FALSE);
405        let cf1_x = [
406            r0_bits
407                .chunks(C1::BaseField::MODULUS_BIT_SIZE as usize - 1)
408                .map(|bits| {
409                    let mut bits = bits.to_vec();
410                    bits.resize(C1::BaseField::MODULUS_BIT_SIZE as usize, Boolean::FALSE);
411                    NonNativeUintVar::from(&bits)
412                })
413                .collect::<Vec<_>>(),
414            vec![
415                NonNativeUintVar::new_constant(cs.clone(), C1::BaseField::zero())?,
416                NonNativeUintVar::new_constant(cs.clone(), C1::BaseField::zero())?,
417                U_i.phi.x.clone(),
418                U_i.phi.y.clone(),
419                phi_stars[0].x.clone(),
420                phi_stars[0].y.clone(),
421            ],
422        ]
423        .concat();
424        let cf2_x = [
425            r1_bits
426                .chunks(C1::BaseField::MODULUS_BIT_SIZE as usize - 1)
427                .map(|bits| {
428                    let mut bits = bits.to_vec();
429                    bits.resize(C1::BaseField::MODULUS_BIT_SIZE as usize, Boolean::FALSE);
430                    NonNativeUintVar::from(&bits)
431                })
432                .collect::<Vec<_>>(),
433            vec![
434                phi_stars[0].x.clone(),
435                phi_stars[0].y.clone(),
436                u_i_phi.x.clone(),
437                u_i_phi.y.clone(),
438                U_i1.phi.x.clone(),
439                U_i1.phi.y.clone(),
440            ],
441        ]
442        .concat();
443
444        // C.2. Prepare incoming CycleFold instances
445        // C.3. Fold incoming CycleFold instances into the running instance
446        let cf_U_i1 =
447            AugmentationGadget::prepare_and_fold_cyclefold::<C1, C2, GC2, PoseidonSponge<CF1<C1>>>(
448                &mut transcript,
449                pp_hash.clone(),
450                cf_U_i,
451                vec![
452                    GC2::new_witness(cs.clone(), || Ok(self.cf1_u_i_cmW))?,
453                    GC2::new_witness(cs.clone(), || Ok(self.cf2_u_i_cmW))?,
454                ],
455                vec![cf1_x, cf2_x],
456                vec![cf1_cmT, cf2_cmT],
457            )?;
458
459        // Back to Primary Part
460        // P.4.b compute and check the second output of F'
461        // Base case: u_{i+1}.x[1] == H(cf_U_{\bot})
462        // Non-base case: u_{i+1}.x[1] == H(cf_U_{i+1})
463        let (cf_u_i1_x, _) = cf_U_i1.clone().hash(&sponge, pp_hash.clone())?;
464        let (cf_u_i1_x_base, _) =
465            CycleFoldCommittedInstanceVar::<C2, GC2>::new_constant(cs.clone(), cf_u_dummy)?
466                .hash(&sponge, pp_hash.clone())?;
467        let cf_x = FpVar::new_input(cs.clone(), || {
468            Ok(self.cf_x.unwrap_or(cf_u_i1_x_base.value()?))
469        })?;
470        cf_x.enforce_equal(&is_basecase.select(&cf_u_i1_x_base, &cf_u_i1_x)?)?;
471
472        Ok(())
473    }
474}
475
476#[cfg(test)]
477mod tests {
478    use std::error::Error;
479
480    use super::*;
481    use crate::{
482        arith::r1cs::tests::get_test_r1cs,
483        folding::protogalaxy::folding::{tests::prepare_inputs, Folding},
484        transcript::poseidon::poseidon_canonical_config,
485    };
486
487    use ark_bn254::{Fr, G1Projective as Projective};
488    use ark_relations::r1cs::ConstraintSystem;
489
490    #[test]
491    fn test_folding_gadget() -> Result<(), Box<dyn Error>> {
492        let k = 7;
493        let (witness, instance, witnesses, instances) = prepare_inputs(k);
494        let r1cs = get_test_r1cs::<Fr>();
495
496        // init Prover & Verifier's transcript
497        let poseidon_config = poseidon_canonical_config::<Fr>();
498        let mut transcript_p = PoseidonSponge::new(&poseidon_config);
499        let mut transcript_v = PoseidonSponge::new(&poseidon_config);
500
501        let (_, _, F_coeffs, K_coeffs, _, _) = Folding::<Projective>::prove(
502            &mut transcript_p,
503            &r1cs,
504            &instance,
505            &witness,
506            &instances,
507            &witnesses,
508        )?;
509
510        let folded_instance = Folding::<Projective>::verify(
511            &mut transcript_v,
512            &instance,
513            &instances,
514            F_coeffs.clone(),
515            K_coeffs.clone(),
516        )?;
517
518        let cs = ConstraintSystem::new_ref();
519        let mut transcript_var = PoseidonSpongeVar::new(cs.clone(), &poseidon_config);
520        let instance_var = CommittedInstanceVar::new_witness(cs.clone(), || Ok(instance))?;
521        let instances_var = Vec::new_witness(cs.clone(), || Ok(instances))?;
522        let F_coeffs_var = Vec::new_witness(cs.clone(), || Ok(F_coeffs))?;
523        let K_coeffs_var = Vec::new_witness(cs.clone(), || Ok(K_coeffs))?;
524
525        let (folded_instance_var, _) = FoldingGadget::fold_committed_instance(
526            &mut transcript_var,
527            &instance_var,
528            &instances_var,
529            F_coeffs_var,
530            K_coeffs_var,
531        )?;
532        assert_eq!(folded_instance.betas, folded_instance_var.betas.value()?);
533        assert_eq!(folded_instance.e, folded_instance_var.e.value()?);
534        assert_eq!(folded_instance.x, folded_instance_var.x.value()?);
535        assert!(cs.is_satisfied()?);
536
537        Ok(())
538    }
539}