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, PoseidonSponge},
  6    Absorb, CryptographicSponge,
  7};
  8use ark_ec::CurveGroup;
  9use ark_ff::PrimeField;
 10use ark_r1cs_std::{
 11    alloc::{AllocVar, AllocationMode},
 12    boolean::Boolean,
 13    eq::EqGadget,
 14    fields::fp::FpVar,
 15    prelude::CurveVar,
 16    ToConstraintFieldGadget,
 17};
 18use ark_relations::r1cs::{Namespace, SynthesisError};
 19use ark_std::{borrow::Borrow, log2, marker::PhantomData};
 20
 21use super::{
 22    circuits::{CCCSVar, LCCCSVar, NIMFSGadget, ProofVar as NIMFSProofVar},
 23    nimfs::{NIMFSProof, NIMFS},
 24    HyperNova, Witness, CCCS, LCCCS,
 25};
 26use crate::folding::circuits::{decider::on_chain::GenericOnchainDeciderCircuit, CF1, CF2};
 27use crate::folding::traits::{WitnessOps, WitnessVarOps};
 28use crate::frontend::FCircuit;
 29use crate::utils::gadgets::{eval_mle, MatrixGadget};
 30use crate::Error;
 31use crate::{
 32    arith::{
 33        ccs::{circuits::CCSMatricesVar, CCS},
 34        ArithGadget,
 35    },
 36    folding::circuits::decider::{EvalGadget, KZGChallengesGadget},
 37};
 38use crate::{
 39    commitment::{pedersen::Params as PedersenParams, CommitmentScheme},
 40    folding::circuits::decider::DeciderEnabledNIFS,
 41};
 42
 43impl<C: CurveGroup> ArithGadget<WitnessVar<CF1<C>>, LCCCSVar<C>> for CCSMatricesVar<CF1<C>> {
 44    type Evaluation = Vec<FpVar<CF1<C>>>;
 45
 46    fn eval_relation(
 47        &self,
 48        w: &WitnessVar<CF1<C>>,
 49        u: &LCCCSVar<C>,
 50    ) -> Result<Self::Evaluation, SynthesisError> {
 51        let z = [&[u.u.clone()][..], &u.x, &w.w].concat();
 52
 53        self.M
 54            .iter()
 55            .map(|M_j| {
 56                let s = log2(M_j.n_rows) as usize;
 57                let Mz = M_j.mul_vector(&z)?;
 58                Ok(eval_mle(s, Mz, u.r_x.clone()))
 59            })
 60            .collect()
 61    }
 62
 63    fn enforce_evaluation(
 64        _w: &WitnessVar<CF1<C>>,
 65        u: &LCCCSVar<C>,
 66        v: Self::Evaluation,
 67    ) -> Result<(), SynthesisError> {
 68        v.enforce_equal(&u.v)
 69    }
 70}
 71
 72/// In-circuit representation of the Witness associated to the CommittedInstance.
 73#[derive(Debug, Clone)]
 74pub struct WitnessVar<F: PrimeField> {
 75    pub w: Vec<FpVar<F>>,
 76    pub r_w: FpVar<F>,
 77}
 78
 79impl<F: PrimeField> AllocVar<Witness<F>, F> for WitnessVar<F> {
 80    fn new_variable<T: Borrow<Witness<F>>>(
 81        cs: impl Into<Namespace<F>>,
 82        f: impl FnOnce() -> Result<T, SynthesisError>,
 83        mode: AllocationMode,
 84    ) -> Result<Self, SynthesisError> {
 85        f().and_then(|val| {
 86            let cs = cs.into();
 87
 88            let w: Vec<FpVar<F>> =
 89                Vec::new_variable(cs.clone(), || Ok(val.borrow().w.clone()), mode)?;
 90            let r_w = FpVar::<F>::new_variable(cs.clone(), || Ok(val.borrow().r_w), mode)?;
 91
 92            Ok(Self { w, r_w })
 93        })
 94    }
 95}
 96
 97impl<F: PrimeField> WitnessVarOps<F> for WitnessVar<F> {
 98    fn get_openings(&self) -> Vec<(&[FpVar<F>], FpVar<F>)> {
 99        vec![(&self.w, self.r_w.clone())]
100    }
101}
102
103pub type DeciderEthCircuit<C1, C2, GC2> = GenericOnchainDeciderCircuit<
104    C1,
105    C2,
106    GC2,
107    LCCCS<C1>,
108    CCCS<C1>,
109    Witness<CF1<C1>>,
110    CCS<CF1<C1>>,
111    CCSMatricesVar<CF1<C1>>,
112    DeciderHyperNovaGadget,
113>;
114
115impl<
116        C1: CurveGroup,
117        GC1: CurveVar<C1, CF2<C1>> + ToConstraintFieldGadget<CF2<C1>>,
118        C2: CurveGroup,
119        GC2: CurveVar<C2, CF2<C2>> + ToConstraintFieldGadget<CF2<C2>>,
120        FC: FCircuit<C1::ScalarField>,
121        CS1: CommitmentScheme<C1, H>,
122        // enforce that the CS2 is Pedersen commitment scheme, since we're at Ethereum's EVM decider
123        CS2: CommitmentScheme<C2, H, ProverParams = PedersenParams<C2>>,
124        const MU: usize,
125        const NU: usize,
126        const H: bool,
127    > TryFrom<HyperNova<C1, GC1, C2, GC2, FC, CS1, CS2, MU, NU, H>>
128    for DeciderEthCircuit<C1, C2, GC2>
129where
130    CF1<C1>: Absorb,
131{
132    type Error = Error;
133
134    fn try_from(hn: HyperNova<C1, GC1, C2, GC2, FC, CS1, CS2, MU, NU, H>) -> Result<Self, Error> {
135        // compute the U_{i+1}, W_{i+1}, by folding the last running & incoming instances
136        let mut transcript = PoseidonSponge::<C1::ScalarField>::new(&hn.poseidon_config);
137        transcript.absorb(&hn.pp_hash);
138        let (nimfs_proof, U_i1, W_i1, rho) = NIMFS::<C1, PoseidonSponge<C1::ScalarField>>::prove(
139            &mut transcript,
140            &hn.ccs,
141            &[hn.U_i.clone()],
142            &[hn.u_i.clone()],
143            &[hn.W_i.clone()],
144            &[hn.w_i.clone()],
145        )?;
146
147        // compute the KZG challenges used as inputs in the circuit
148        let kzg_challenges = KZGChallengesGadget::get_challenges_native(&mut transcript, &U_i1);
149
150        // get KZG evals
151        let kzg_evaluations = W_i1
152            .get_openings()
153            .iter()
154            .zip(&kzg_challenges)
155            .map(|((v, _), &c)| EvalGadget::evaluate_native(v, c))
156            .collect::<Result<Vec<_>, _>>()?;
157
158        Ok(Self {
159            _gc2: PhantomData,
160            _avar: PhantomData,
161            arith: hn.ccs,
162            cf_arith: hn.cf_r1cs,
163            cf_pedersen_params: hn.cf_cs_pp,
164            poseidon_config: hn.poseidon_config,
165            pp_hash: hn.pp_hash,
166            i: hn.i,
167            z_0: hn.z_0,
168            z_i: hn.z_i,
169            U_i: hn.U_i,
170            W_i: hn.W_i,
171            u_i: hn.u_i,
172            w_i: hn.w_i,
173            U_i1,
174            W_i1,
175            proof: nimfs_proof,
176            randomness: rho,
177            cf_U_i: hn.cf_U_i,
178            cf_W_i: hn.cf_W_i,
179            kzg_challenges,
180            kzg_evaluations,
181        })
182    }
183}
184
185pub struct DeciderHyperNovaGadget;
186
187impl<C: CurveGroup> DeciderEnabledNIFS<C, LCCCS<C>, CCCS<C>, Witness<C::ScalarField>, CCS<CF1<C>>>
188    for DeciderHyperNovaGadget
189where
190    CF1<C>: Absorb,
191{
192    type ProofDummyCfg = (usize, usize, usize, usize);
193    type Proof = NIMFSProof<C>;
194    type Randomness = CF1<C>;
195    type RandomnessDummyCfg = ();
196
197    fn fold_field_elements_gadget(
198        arith: &CCS<CF1<C>>,
199        transcript: &mut PoseidonSpongeVar<CF1<C>>,
200        pp_hash: FpVar<CF1<C>>,
201        U: LCCCSVar<C>,
202        _U_vec: Vec<FpVar<CF1<C>>>,
203        u: CCCSVar<C>,
204        proof: Self::Proof,
205        randomness: Self::Randomness,
206    ) -> Result<LCCCSVar<C>, SynthesisError> {
207        let cs = transcript.cs();
208        transcript.absorb(&pp_hash)?;
209        let nimfs_proof = NIMFSProofVar::<C>::new_witness(cs.clone(), || Ok(proof))?;
210        let rho = FpVar::<CF1<C>>::new_input(cs.clone(), || Ok(randomness))?;
211        let (computed_U_i1, rho_bits) = NIMFSGadget::<C>::verify(
212            cs.clone(),
213            arith,
214            transcript,
215            &[U],
216            &[u],
217            nimfs_proof,
218            Boolean::TRUE, // enabled
219        )?;
220        Boolean::le_bits_to_fp_var(&rho_bits)?.enforce_equal(&rho)?;
221        Ok(computed_U_i1)
222    }
223
224    fn fold_group_elements_native(
225        U_commitments: &[C],
226        u_commitments: &[C],
227        _: Option<Self::Proof>,
228        r: Self::Randomness,
229    ) -> Result<Vec<C>, Error> {
230        let U_C = U_commitments[0];
231        let u_C = u_commitments[0];
232        let C = U_C + u_C.mul(r);
233        Ok(vec![C])
234    }
235}
236
237#[cfg(test)]
238pub mod tests {
239    use ark_bn254::{constraints::GVar, Fr, G1Projective as Projective};
240    use ark_grumpkin::{constraints::GVar as GVar2, Projective as Projective2};
241    use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystem};
242    use ark_std::{test_rng, UniformRand};
243
244    use super::*;
245    use crate::arith::r1cs::R1CS;
246    use crate::commitment::pedersen::Pedersen;
247    use crate::folding::nova::PreprocessorParam;
248    use crate::frontend::utils::CubicFCircuit;
249    use crate::transcript::poseidon::poseidon_canonical_config;
250    use crate::FoldingScheme;
251
252    #[test]
253    fn test_lcccs_checker_gadget() {
254        let mut rng = test_rng();
255        let n_rows = 2_u32.pow(5) as usize;
256        let n_cols = 2_u32.pow(5) as usize;
257        let r1cs = R1CS::<Fr>::rand(&mut rng, n_rows, n_cols);
258        let ccs = CCS::from(r1cs);
259        let z: Vec<Fr> = (0..n_cols).map(|_| Fr::rand(&mut rng)).collect();
260
261        let (pedersen_params, _) =
262            Pedersen::<Projective>::setup(&mut rng, ccs.n - ccs.l - 1).unwrap();
263
264        let (lcccs, w) = ccs
265            .to_lcccs::<_, Projective, Pedersen<Projective>, false>(&mut rng, &pedersen_params, &z)
266            .unwrap();
267
268        let cs = ConstraintSystem::<Fr>::new_ref();
269
270        // CCS's (sparse) matrices are constants in the circuit
271        let ccs_mat = CCSMatricesVar::<Fr>::new_constant(cs.clone(), ccs.clone()).unwrap();
272        let w_var = WitnessVar::new_witness(cs.clone(), || Ok(w)).unwrap();
273        let lcccs_var = LCCCSVar::new_input(cs.clone(), || Ok(lcccs)).unwrap();
274
275        ccs_mat.enforce_relation(&w_var, &lcccs_var).unwrap();
276
277        assert!(cs.is_satisfied().unwrap());
278    }
279
280    #[test]
281    fn test_decider_circuit() {
282        let mut rng = ark_std::test_rng();
283        let poseidon_config = poseidon_canonical_config::<Fr>();
284
285        let F_circuit = CubicFCircuit::<Fr>::new(()).unwrap();
286        let z_0 = vec![Fr::from(3_u32)];
287
288        const MU: usize = 1;
289        const NU: usize = 1;
290
291        type HN = HyperNova<
292            Projective,
293            GVar,
294            Projective2,
295            GVar2,
296            CubicFCircuit<Fr>,
297            Pedersen<Projective>,
298            Pedersen<Projective2>,
299            MU,
300            NU,
301            false,
302        >;
303        let prep_param = PreprocessorParam::<
304            Projective,
305            Projective2,
306            CubicFCircuit<Fr>,
307            Pedersen<Projective>,
308            Pedersen<Projective2>,
309            false,
310        >::new(poseidon_config, F_circuit);
311        let hn_params = HN::preprocess(&mut rng, &prep_param).unwrap();
312
313        // generate a Nova instance and do a step of it
314        let mut hypernova = HN::init(&hn_params, F_circuit, z_0.clone()).unwrap();
315        hypernova.prove_step(&mut rng, vec![], None).unwrap();
316
317        let ivc_proof = hypernova.ivc_proof();
318        HN::verify(hn_params.1, ivc_proof).unwrap();
319
320        // load the DeciderEthCircuit from the generated Nova instance
321        let decider_circuit =
322            DeciderEthCircuit::<Projective, Projective2, GVar2>::try_from(hypernova).unwrap();
323
324        let cs = ConstraintSystem::<Fr>::new_ref();
325
326        // generate the constraints and check that are satisfied by the inputs
327        decider_circuit.generate_constraints(cs.clone()).unwrap();
328        assert!(cs.is_satisfied().unwrap());
329        dbg!(cs.num_constraints());
330    }
331}