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