arnaucube
arnaucube/sonobePublic Self-hosted repository
sonobe/folding-schemes/src/folding/hypernova/decider_eth_circuit.rs
folding-schemes/src/folding/hypernova/decider_eth_circuit.rsRaw
  1/// This file implements the onchain (Ethereum's EVM) decider circuit. For non-ethereum use cases,
  2/// other more efficient approaches can be used.
  3use ark_crypto_primitives::sponge::{
  4    constraints::CryptographicSpongeVar,
  5    poseidon::{constraints::PoseidonSpongeVar, PoseidonConfig, PoseidonSponge},
  6    Absorb, CryptographicSponge,
  7};
  8use ark_ec::{CurveGroup, Group};
  9use ark_ff::PrimeField;
 10use ark_poly::Polynomial;
 11use ark_r1cs_std::{
 12    alloc::{AllocVar, AllocationMode},
 13    boolean::Boolean,
 14    eq::EqGadget,
 15    fields::fp::FpVar,
 16    groups::GroupOpsBounds,
 17    prelude::CurveVar,
 18    ToConstraintFieldGadget,
 19};
 20use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystemRef, Namespace, SynthesisError};
 21use ark_std::Zero;
 22use core::{borrow::Borrow, marker::PhantomData};
 23
 24use super::{
 25    circuits::{CCCSVar, LCCCSVar, NIMFSGadget, ProofVar as NIMFSProofVar},
 26    nimfs::{NIMFSProof, NIMFS},
 27    HyperNova, Witness, CCCS, LCCCS,
 28};
 29use crate::commitment::{pedersen::Params as PedersenParams, CommitmentScheme};
 30use crate::folding::circuits::{
 31    cyclefold::{CycleFoldCommittedInstance, CycleFoldWitness},
 32    CF1, CF2,
 33};
 34use crate::frontend::FCircuit;
 35use crate::transcript::{Transcript, TranscriptVar};
 36use crate::utils::{
 37    gadgets::{eval_mle, MatrixGadget, SparseMatrixVar},
 38    vec::poly_from_vec,
 39};
 40use crate::Error;
 41use crate::{
 42    arith::{ccs::CCS, r1cs::R1CS},
 43    folding::traits::{CommittedInstanceVarOps, Dummy, WitnessVarOps},
 44};
 45
 46/// In-circuit representation of the Witness associated to the CommittedInstance.
 47#[derive(Debug, Clone)]
 48pub struct WitnessVar<F: PrimeField> {
 49    pub w: Vec<FpVar<F>>,
 50    pub r_w: FpVar<F>,
 51}
 52
 53impl<F: PrimeField> AllocVar<Witness<F>, F> for WitnessVar<F> {
 54    fn new_variable<T: Borrow<Witness<F>>>(
 55        cs: impl Into<Namespace<F>>,
 56        f: impl FnOnce() -> Result<T, SynthesisError>,
 57        mode: AllocationMode,
 58    ) -> Result<Self, SynthesisError> {
 59        f().and_then(|val| {
 60            let cs = cs.into();
 61
 62            let w: Vec<FpVar<F>> =
 63                Vec::new_variable(cs.clone(), || Ok(val.borrow().w.clone()), mode)?;
 64            let r_w = FpVar::<F>::new_variable(cs.clone(), || Ok(val.borrow().r_w), mode)?;
 65
 66            Ok(Self { w, r_w })
 67        })
 68    }
 69}
 70
 71impl<F: PrimeField> WitnessVarOps<F> for WitnessVar<F> {
 72    fn get_openings(&self) -> Vec<(&[FpVar<F>], FpVar<F>)> {
 73        vec![(&self.w, self.r_w.clone())]
 74    }
 75}
 76
 77/// CCSMatricesVar contains the matrices 'M' of the CCS without the rest of CCS parameters.
 78#[derive(Debug, Clone)]
 79pub struct CCSMatricesVar<F: PrimeField> {
 80    // we only need native representation, so the constraint field==F
 81    pub M: Vec<SparseMatrixVar<F, F, FpVar<F>>>,
 82}
 83
 84impl<F> AllocVar<CCS<F>, F> for CCSMatricesVar<F>
 85where
 86    F: PrimeField,
 87{
 88    fn new_variable<T: Borrow<CCS<F>>>(
 89        cs: impl Into<Namespace<F>>,
 90        f: impl FnOnce() -> Result<T, SynthesisError>,
 91        _mode: AllocationMode,
 92    ) -> Result<Self, SynthesisError> {
 93        f().and_then(|val| {
 94            let cs = cs.into();
 95
 96            let M: Vec<SparseMatrixVar<F, F, FpVar<F>>> = val
 97                .borrow()
 98                .M
 99                .iter()
100                .map(|M| SparseMatrixVar::<F, F, FpVar<F>>::new_constant(cs.clone(), M.clone()))
101                .collect::<Result<_, SynthesisError>>()?;
102
103            Ok(Self { M })
104        })
105    }
106}
107
108/// Gadget to check the LCCCS relation both over the native constraint field and over the
109/// non-native constraint field.
110#[derive(Debug, Clone)]
111pub struct LCCCSCheckerGadget {}
112impl LCCCSCheckerGadget {
113    /// performs in-circuit the RelaxedR1CS check for native variables (Az∘Bz==uCz+E)
114    pub fn check<F: PrimeField>(
115        s: usize,
116        ccs_mat: CCSMatricesVar<F>,
117        z: Vec<FpVar<F>>,
118        // LCCCS values
119        r_x: Vec<FpVar<F>>,
120        v: Vec<FpVar<F>>,
121    ) -> Result<(), SynthesisError> {
122        let computed_v: Vec<FpVar<F>> = ccs_mat
123            .M
124            .iter()
125            .map(|M_j| {
126                let Mz = M_j.mul_vector(&z)?;
127                Ok(eval_mle(s, Mz, r_x.clone()))
128            })
129            .collect::<Result<Vec<FpVar<F>>, SynthesisError>>()?;
130
131        (computed_v).enforce_equal(&v)?;
132        Ok(())
133    }
134}
135
136/// Circuit that implements the in-circuit checks needed for the HyperNova's onchain (Ethereum's
137/// EVM) verification.
138#[derive(Clone, Debug)]
139pub struct DeciderEthCircuit<C1, GC1, C2, GC2, CS1, CS2, const H: bool = false>
140where
141    C1: CurveGroup,
142    GC1: CurveVar<C1, CF2<C1>>,
143    C2: CurveGroup,
144    GC2: CurveVar<C2, CF2<C2>>,
145    CS1: CommitmentScheme<C1, H>,
146    CS2: CommitmentScheme<C2, H>,
147{
148    _c1: PhantomData<C1>,
149    _gc1: PhantomData<GC1>,
150    _c2: PhantomData<C2>,
151    _gc2: PhantomData<GC2>,
152    _cs1: PhantomData<CS1>,
153    _cs2: PhantomData<CS2>,
154
155    /// E vector's length of the CycleFold instance witness
156    pub cf_E_len: usize,
157    /// CCS of the Augmented Function circuit
158    pub ccs: CCS<C1::ScalarField>,
159    /// R1CS of the CycleFold circuit
160    pub cf_r1cs: R1CS<C2::ScalarField>,
161    /// CycleFold PedersenParams over C2
162    pub cf_pedersen_params: PedersenParams<C2>,
163    pub poseidon_config: PoseidonConfig<CF1<C1>>,
164    /// public params hash
165    pub pp_hash: Option<C1::ScalarField>,
166    pub i: Option<CF1<C1>>,
167    /// initial state
168    pub z_0: Option<Vec<C1::ScalarField>>,
169    /// current i-th state
170    pub z_i: Option<Vec<C1::ScalarField>>,
171    /// Nova instances
172    pub U_i: Option<LCCCS<C1>>,
173    pub W_i: Option<Witness<C1::ScalarField>>,
174    pub u_i: Option<CCCS<C1>>,
175    pub w_i: Option<Witness<C1::ScalarField>>,
176    pub U_i1: Option<LCCCS<C1>>,
177    pub W_i1: Option<Witness<C1::ScalarField>>,
178    pub nimfs_proof: Option<NIMFSProof<C1>>,
179    // rho is the 'random' value used for the fold of the last 2 instances
180    pub rho: Option<C1::ScalarField>,
181    /// CycleFold running instance
182    pub cf_U_i: Option<CycleFoldCommittedInstance<C2>>,
183    pub cf_W_i: Option<CycleFoldWitness<C2>>,
184
185    /// KZG challenge & eval
186    pub kzg_challenge: Option<C1::ScalarField>,
187    pub eval_W: Option<C1::ScalarField>,
188}
189
190impl<C1, GC1, C2, GC2, CS1, CS2, const H: bool> DeciderEthCircuit<C1, GC1, C2, GC2, CS1, CS2, H>
191where
192    C1: CurveGroup,
193    C2: CurveGroup,
194    GC1: CurveVar<C1, CF2<C1>> + ToConstraintFieldGadget<CF2<C1>>,
195    GC2: CurveVar<C2, CF2<C2>> + ToConstraintFieldGadget<CF2<C2>>,
196    CS1: CommitmentScheme<C1, H>,
197    // enforce that the CS2 is Pedersen commitment scheme, since we're at Ethereum's EVM decider
198    CS2: CommitmentScheme<C2, H, ProverParams = PedersenParams<C2>>,
199    <C1 as Group>::ScalarField: Absorb,
200    <C1 as CurveGroup>::BaseField: PrimeField,
201{
202    /// returns an instance of the DeciderEthCircuit from the given HyperNova struct
203    pub fn from_hypernova<FC: FCircuit<C1::ScalarField>, const MU: usize, const NU: usize>(
204        hn: HyperNova<C1, GC1, C2, GC2, FC, CS1, CS2, MU, NU, H>,
205    ) -> Result<Self, Error> {
206        // compute the U_{i+1}, W_{i+1}, by folding the last running & incoming instances
207        let mut transcript = PoseidonSponge::<C1::ScalarField>::new(&hn.poseidon_config);
208        transcript.absorb(&hn.pp_hash);
209        let (nimfs_proof, U_i1, W_i1, rho) = NIMFS::<C1, PoseidonSponge<C1::ScalarField>>::prove(
210            &mut transcript,
211            &hn.ccs,
212            &[hn.U_i.clone()],
213            &[hn.u_i.clone()],
214            &[hn.W_i.clone()],
215            &[hn.w_i.clone()],
216        )?;
217
218        // compute the KZG challenges used as inputs in the circuit
219        let kzg_challenge =
220            KZGChallengeGadget::<C1>::get_challenge_native(&mut transcript, U_i1.clone())?;
221
222        // get KZG evals
223        let mut W = W_i1.w.clone();
224        W.extend(
225            std::iter::repeat(C1::ScalarField::zero())
226                .take(W_i1.w.len().next_power_of_two() - W_i1.w.len()),
227        );
228        let p_W = poly_from_vec(W.to_vec())?;
229        let eval_W = p_W.evaluate(&kzg_challenge);
230
231        Ok(Self {
232            _c1: PhantomData,
233            _gc1: PhantomData,
234            _c2: PhantomData,
235            _gc2: PhantomData,
236            _cs1: PhantomData,
237            _cs2: PhantomData,
238
239            cf_E_len: hn.cf_W_i.E.len(),
240            ccs: hn.ccs,
241            cf_r1cs: hn.cf_r1cs,
242            cf_pedersen_params: hn.cf_cs_pp,
243            poseidon_config: hn.poseidon_config,
244            pp_hash: Some(hn.pp_hash),
245            i: Some(hn.i),
246            z_0: Some(hn.z_0),
247            z_i: Some(hn.z_i),
248            U_i: Some(hn.U_i),
249            W_i: Some(hn.W_i),
250            u_i: Some(hn.u_i),
251            w_i: Some(hn.w_i),
252            U_i1: Some(U_i1),
253            W_i1: Some(W_i1),
254            nimfs_proof: Some(nimfs_proof),
255            rho: Some(rho),
256            cf_U_i: Some(hn.cf_U_i),
257            cf_W_i: Some(hn.cf_W_i),
258            kzg_challenge: Some(kzg_challenge),
259            eval_W: Some(eval_W),
260        })
261    }
262}
263
264impl<C1, GC1, C2, GC2, CS1, CS2> ConstraintSynthesizer<CF1<C1>>
265    for DeciderEthCircuit<C1, GC1, C2, GC2, CS1, CS2>
266where
267    C1: CurveGroup,
268    C2: CurveGroup,
269    GC1: CurveVar<C1, CF2<C1>>,
270    GC2: CurveVar<C2, CF2<C2>> + ToConstraintFieldGadget<CF2<C2>>,
271    CS1: CommitmentScheme<C1>,
272    CS2: CommitmentScheme<C2>,
273    C1::ScalarField: PrimeField,
274    <C1 as CurveGroup>::BaseField: PrimeField,
275    <C2 as CurveGroup>::BaseField: PrimeField,
276    <C1 as Group>::ScalarField: Absorb,
277    <C2 as Group>::ScalarField: Absorb,
278    C1: CurveGroup<BaseField = C2::ScalarField, ScalarField = C2::BaseField>,
279    for<'b> &'b GC2: GroupOpsBounds<'b, C2, GC2>,
280{
281    fn generate_constraints(self, cs: ConstraintSystemRef<CF1<C1>>) -> Result<(), SynthesisError> {
282        let ccs_matrices =
283            CCSMatricesVar::<C1::ScalarField>::new_witness(cs.clone(), || Ok(self.ccs.clone()))?;
284
285        let pp_hash = FpVar::<CF1<C1>>::new_input(cs.clone(), || {
286            Ok(self.pp_hash.unwrap_or_else(CF1::<C1>::zero))
287        })?;
288        let i =
289            FpVar::<CF1<C1>>::new_input(cs.clone(), || Ok(self.i.unwrap_or_else(CF1::<C1>::zero)))?;
290        let z_0 = Vec::<FpVar<CF1<C1>>>::new_input(cs.clone(), || {
291            Ok(self.z_0.unwrap_or(vec![CF1::<C1>::zero()]))
292        })?;
293        let z_i = Vec::<FpVar<CF1<C1>>>::new_input(cs.clone(), || {
294            Ok(self.z_i.unwrap_or(vec![CF1::<C1>::zero()]))
295        })?;
296
297        let U_dummy_native = LCCCS::<C1>::dummy(&self.ccs);
298        let u_dummy_native = CCCS::<C1>::dummy(&self.ccs);
299        let w_dummy_native = Witness::<C1::ScalarField>::new(
300            vec![C1::ScalarField::zero(); self.ccs.n - 3 /* (3=2+1, since u_i.x.len=2) */],
301        );
302
303        let U_i = LCCCSVar::<C1>::new_witness(cs.clone(), || {
304            Ok(self.U_i.unwrap_or(U_dummy_native.clone()))
305        })?;
306        let u_i = CCCSVar::<C1>::new_witness(cs.clone(), || {
307            Ok(self.u_i.unwrap_or(u_dummy_native.clone()))
308        })?;
309        // here (U_i1, W_i1) = NIFS.P( (U_i,W_i), (u_i,w_i))
310        let U_i1 = LCCCSVar::<C1>::new_input(cs.clone(), || {
311            Ok(self.U_i1.unwrap_or(U_dummy_native.clone()))
312        })?;
313        let W_i1 = WitnessVar::<C1::ScalarField>::new_witness(cs.clone(), || {
314            Ok(self.W_i1.unwrap_or(w_dummy_native.clone()))
315        })?;
316        let nimfs_proof_dummy = NIMFSProof::<C1>::dummy((&self.ccs, 1, 1)); // mu=1 & nu=1 because the last fold is 2-to-1
317        let nimfs_proof = NIMFSProofVar::<C1>::new_witness(cs.clone(), || {
318            Ok(self.nimfs_proof.unwrap_or(nimfs_proof_dummy))
319        })?;
320
321        // allocate the inputs for the check 6
322        let kzg_challenge = FpVar::<CF1<C1>>::new_input(cs.clone(), || {
323            Ok(self.kzg_challenge.unwrap_or_else(CF1::<C1>::zero))
324        })?;
325        let _eval_W = FpVar::<CF1<C1>>::new_input(cs.clone(), || {
326            Ok(self.eval_W.unwrap_or_else(CF1::<C1>::zero))
327        })?;
328
329        // `sponge` is for digest computation.
330        let sponge = PoseidonSpongeVar::<C1::ScalarField>::new(cs.clone(), &self.poseidon_config);
331        // `transcript` is for challenge generation.
332        let mut transcript = sponge.clone();
333        transcript.absorb(&pp_hash)?;
334
335        // NOTE: we use the same enumeration as in Nova's DeciderEthCircuit described at
336        // https://privacy-scaling-explorations.github.io/sonobe-docs/design/nova-decider-onchain.html
337        // in order to make it easier to reason about.
338
339        // 1. check LCCCS relation of U_{i+1}
340        let z_U1: Vec<FpVar<CF1<C1>>> =
341            [vec![U_i1.u.clone()], U_i1.x.to_vec(), W_i1.w.to_vec()].concat();
342        LCCCSCheckerGadget::check(
343            self.ccs.s,
344            ccs_matrices,
345            z_U1,
346            U_i1.r_x.clone(),
347            U_i1.v.clone(),
348        )?;
349
350        // 3.a u_i.x[0] == H(i, z_0, z_i, U_i)
351        let (u_i_x, _) = U_i.clone().hash(&sponge, &pp_hash, &i, &z_0, &z_i)?;
352        (u_i.x[0]).enforce_equal(&u_i_x)?;
353
354        #[cfg(feature = "light-test")]
355        log::warn!("[WARNING]: Running with the 'light-test' feature, skipping the big part of the DeciderEthCircuit.\n           Only for testing purposes.");
356
357        // The following two checks (and their respective allocations) are disabled for normal
358        // tests since they take several millions of constraints and would take several minutes
359        // (and RAM) to run the test. It is active by default, and not active only when
360        // 'light-test' feature is used.
361        #[cfg(not(feature = "light-test"))]
362        {
363            // imports here instead of at the top of the file, so we avoid having multiple
364            // `#[cfg(not(test))]`
365            use crate::commitment::pedersen::PedersenGadget;
366            use crate::folding::circuits::nonnative::uint::NonNativeUintVar;
367            use crate::folding::nova::decider_eth_circuit::{R1CSVar, RelaxedR1CSGadget};
368            use crate::folding::{
369                circuits::cyclefold::{
370                    CycleFoldCommittedInstanceVar, CycleFoldConfig, CycleFoldWitnessVar,
371                },
372                nova::NovaCycleFoldConfig,
373            };
374            use ark_r1cs_std::ToBitsGadget;
375
376            let cf_u_dummy_native =
377                CycleFoldCommittedInstance::<C2>::dummy(NovaCycleFoldConfig::<C1>::IO_LEN);
378            let cf_w_dummy_native = CycleFoldWitness::<C2>::dummy(&self.cf_r1cs);
379            let cf_U_i = CycleFoldCommittedInstanceVar::<C2, GC2>::new_witness(cs.clone(), || {
380                Ok(self.cf_U_i.unwrap_or_else(|| cf_u_dummy_native.clone()))
381            })?;
382            let cf_W_i = CycleFoldWitnessVar::<C2>::new_witness(cs.clone(), || {
383                Ok(self.cf_W_i.unwrap_or(cf_w_dummy_native.clone()))
384            })?;
385
386            // 3.b u_i.x[1] == H(cf_U_i)
387            let (cf_u_i_x, _) = cf_U_i.clone().hash(&sponge, pp_hash.clone())?;
388            (u_i.x[1]).enforce_equal(&cf_u_i_x)?;
389
390            // 4. check Pedersen commitments of cf_U_i.{cmE, cmW}
391            let H = GC2::new_constant(cs.clone(), self.cf_pedersen_params.h)?;
392            let G = Vec::<GC2>::new_constant(cs.clone(), self.cf_pedersen_params.generators)?;
393            let cf_W_i_E_bits: Result<Vec<Vec<Boolean<CF1<C1>>>>, SynthesisError> =
394                cf_W_i.E.iter().map(|E_i| E_i.to_bits_le()).collect();
395            let cf_W_i_W_bits: Result<Vec<Vec<Boolean<CF1<C1>>>>, SynthesisError> =
396                cf_W_i.W.iter().map(|W_i| W_i.to_bits_le()).collect();
397
398            let computed_cmE = PedersenGadget::<C2, GC2>::commit(
399                H.clone(),
400                G.clone(),
401                cf_W_i_E_bits?,
402                cf_W_i.rE.to_bits_le()?,
403            )?;
404            cf_U_i.cmE.enforce_equal(&computed_cmE)?;
405            let computed_cmW =
406                PedersenGadget::<C2, GC2>::commit(H, G, cf_W_i_W_bits?, cf_W_i.rW.to_bits_le()?)?;
407            cf_U_i.cmW.enforce_equal(&computed_cmW)?;
408
409            let cf_r1cs =
410                R1CSVar::<C1::BaseField, CF1<C1>, NonNativeUintVar<CF1<C1>>>::new_witness(
411                    cs.clone(),
412                    || Ok(self.cf_r1cs.clone()),
413                )?;
414
415            // 5. check RelaxedR1CS of cf_U_i
416            let cf_z_U = [vec![cf_U_i.u.clone()], cf_U_i.x.to_vec(), cf_W_i.W.to_vec()].concat();
417            RelaxedR1CSGadget::check_nonnative(cf_r1cs, cf_W_i.E, cf_U_i.u.clone(), cf_z_U)?;
418        }
419
420        // The following steps are in non-increasing order because the `computed_U_i1` is computed
421        // at step 8, and later used at step 6. Notice that in Nova, we compute U_i1 outside of the
422        // circuit, in the smart contract, but here we're computing it in-circuit, and we reuse the
423        // `rho_bits` computed along the way of computing `computed_U_i1` for the later `rho_powers`
424        // check (6.b).
425
426        // Check 7 is temporary disabled due
427        // https://github.com/privacy-scaling-explorations/sonobe/issues/80
428        log::warn!("[WARNING]: issue #80 (https://github.com/privacy-scaling-explorations/sonobe/issues/80) is not resolved yet.");
429        //
430        // 7. check eval_W==p_W(c_W)
431        // let incircuit_eval_W = evaluate_gadget::<CF1<C1>>(W_i1.W, incircuit_c_W)?;
432        // incircuit_eval_W.enforce_equal(&eval_W)?;
433
434        // 8.a verify the NIMFS.V of the final fold, and check that the obtained rho_powers from the
435        // transcript match the one from the public input (so we avoid the onchain logic of the
436        // verifier computing it).
437        // Notice that the NIMFSGadget performs all the logic except of checking the fold of the
438        // instances C parameter, which would require non-native arithmetic, henceforth we perform
439        // that check outside the circuit.
440        let (computed_U_i1, rho_bits) = NIMFSGadget::<C1>::verify(
441            cs.clone(),
442            &self.ccs.clone(),
443            &mut transcript,
444            &[U_i],
445            &[u_i],
446            nimfs_proof,
447            Boolean::TRUE, // enabled
448        )?;
449
450        // 6.a check KZG challenges
451        // Notice that this step is done after the NIMFS.V, to follow the transcript absorbs order
452        // done outside the circuit, where to compute the challenge it needs first to compute the
453        // U_{i+1} through the NIMFS.V
454        let incircuit_challenge =
455            KZGChallengeGadget::<C1>::get_challenge_gadget(&mut transcript, U_i1.clone())?;
456        incircuit_challenge.enforce_equal(&kzg_challenge)?;
457
458        // 6.b check that the obtained U_{i+1} from the NIMFS.V matches the U_{i+1} from the input,
459        // except for the C parameter, which to compute its folding would require non-native logic
460        // in-circuit, and we defer it to outside the circuit.
461        computed_U_i1.u.enforce_equal(&U_i1.u)?;
462        computed_U_i1.r_x.enforce_equal(&U_i1.r_x)?;
463        computed_U_i1.v.enforce_equal(&U_i1.v)?;
464
465        // 8.b check that the in-circuit computed r is equal to the inputted r.
466
467        let rho = Boolean::le_bits_to_fp_var(&rho_bits)?;
468        let external_rho =
469            FpVar::<CF1<C1>>::new_input(cs.clone(), || Ok(self.rho.unwrap_or(CF1::<C1>::zero())))?;
470        rho.enforce_equal(&external_rho)?;
471
472        Ok(())
473    }
474}
475
476/// Gadget that computes the KZG challenges, also offers the rust native implementation compatible
477/// with the gadget.
478pub struct KZGChallengeGadget<C: CurveGroup> {
479    _c: PhantomData<C>,
480}
481#[allow(clippy::type_complexity)]
482impl<C> KZGChallengeGadget<C>
483where
484    C: CurveGroup,
485    C::ScalarField: PrimeField,
486    <C as CurveGroup>::BaseField: PrimeField,
487    C::ScalarField: Absorb,
488{
489    pub fn get_challenge_native<T: Transcript<C::ScalarField>>(
490        transcript: &mut T,
491        U_i: LCCCS<C>,
492    ) -> Result<C::ScalarField, Error> {
493        // compute the KZG challenges, which are computed in-circuit and checked that it matches
494        // the inputted one
495        transcript.absorb_nonnative(&U_i.C);
496        Ok(transcript.get_challenge())
497    }
498    // compatible with the native get_challenge_native
499    pub fn get_challenge_gadget<S: CryptographicSponge, T: TranscriptVar<CF1<C>, S>>(
500        transcript: &mut T,
501        U_i: LCCCSVar<C>,
502    ) -> Result<FpVar<C::ScalarField>, SynthesisError> {
503        transcript.absorb(&U_i.C.to_constraint_field()?)?;
504        transcript.get_challenge()
505    }
506}
507
508#[cfg(test)]
509pub mod tests {
510    use ark_bn254::{constraints::GVar, Fr, G1Projective as Projective};
511    use ark_grumpkin::{constraints::GVar as GVar2, Projective as Projective2};
512    use ark_relations::r1cs::ConstraintSystem;
513    use ark_std::One;
514    use ark_std::{test_rng, UniformRand};
515
516    use super::*;
517    use crate::commitment::pedersen::Pedersen;
518    use crate::folding::nova::PreprocessorParam;
519    use crate::frontend::utils::CubicFCircuit;
520    use crate::transcript::poseidon::poseidon_canonical_config;
521    use crate::FoldingScheme;
522
523    #[test]
524    fn test_lcccs_checker_gadget() {
525        let mut rng = test_rng();
526        let n_rows = 2_u32.pow(5) as usize;
527        let n_cols = 2_u32.pow(5) as usize;
528        let r1cs = R1CS::<Fr>::rand(&mut rng, n_rows, n_cols);
529        let ccs = CCS::from(r1cs);
530        let z: Vec<Fr> = (0..n_cols).map(|_| Fr::rand(&mut rng)).collect();
531
532        let (pedersen_params, _) =
533            Pedersen::<Projective>::setup(&mut rng, ccs.n - ccs.l - 1).unwrap();
534
535        let (lcccs, _) = ccs
536            .to_lcccs::<_, Projective, Pedersen<Projective>, false>(&mut rng, &pedersen_params, &z)
537            .unwrap();
538
539        let cs = ConstraintSystem::<Fr>::new_ref();
540
541        // CCS's (sparse) matrices are constants in the circuit
542        let ccs_mat = CCSMatricesVar::<Fr>::new_constant(cs.clone(), ccs.clone()).unwrap();
543        let zVar = Vec::<FpVar<Fr>>::new_input(cs.clone(), || Ok(z)).unwrap();
544        let r_xVar = Vec::<FpVar<Fr>>::new_input(cs.clone(), || Ok(lcccs.r_x)).unwrap();
545        let vVar = Vec::<FpVar<Fr>>::new_input(cs.clone(), || Ok(lcccs.v)).unwrap();
546
547        LCCCSCheckerGadget::check(ccs.s, ccs_mat, zVar, r_xVar, vVar).unwrap();
548
549        assert!(cs.is_satisfied().unwrap());
550    }
551
552    #[test]
553    fn test_decider_circuit() {
554        let mut rng = ark_std::test_rng();
555        let poseidon_config = poseidon_canonical_config::<Fr>();
556
557        let F_circuit = CubicFCircuit::<Fr>::new(()).unwrap();
558        let z_0 = vec![Fr::from(3_u32)];
559
560        const MU: usize = 1;
561        const NU: usize = 1;
562
563        type HN = HyperNova<
564            Projective,
565            GVar,
566            Projective2,
567            GVar2,
568            CubicFCircuit<Fr>,
569            Pedersen<Projective>,
570            Pedersen<Projective2>,
571            MU,
572            NU,
573            false,
574        >;
575        let prep_param = PreprocessorParam::<
576            Projective,
577            Projective2,
578            CubicFCircuit<Fr>,
579            Pedersen<Projective>,
580            Pedersen<Projective2>,
581            false,
582        >::new(poseidon_config, F_circuit);
583        let hn_params = HN::preprocess(&mut rng, &prep_param).unwrap();
584
585        // generate a Nova instance and do a step of it
586        let mut hypernova = HN::init(&hn_params, F_circuit, z_0.clone()).unwrap();
587        hypernova
588            .prove_step(&mut rng, vec![], Some((vec![], vec![])))
589            .unwrap();
590
591        let ivc_v = hypernova.clone();
592        let (running_instance, incoming_instance, cyclefold_instance) = ivc_v.instances();
593        HN::verify(
594            hn_params.1, // HN's verifier_params
595            z_0,
596            ivc_v.z_i,
597            Fr::one(),
598            running_instance,
599            incoming_instance,
600            cyclefold_instance,
601        )
602        .unwrap();
603
604        // load the DeciderEthCircuit from the generated Nova instance
605        let decider_circuit = DeciderEthCircuit::<
606            Projective,
607            GVar,
608            Projective2,
609            GVar2,
610            Pedersen<Projective>,
611            Pedersen<Projective2>,
612        >::from_hypernova(hypernova)
613        .unwrap();
614
615        let cs = ConstraintSystem::<Fr>::new_ref();
616
617        // generate the constraints and check that are satisfied by the inputs
618        decider_circuit.generate_constraints(cs.clone()).unwrap();
619        assert!(cs.is_satisfied().unwrap());
620        dbg!(cs.num_constraints());
621    }
622}