arnaucube
arnaucube/sonobePublic Self-hosted repository
sonobe/folding-schemes/src/folding/nova/decider_eth.rs
folding-schemes/src/folding/nova/decider_eth.rsRaw
  1/// This file implements the onchain (Ethereum's EVM) decider.
  2use ark_bn254::Bn254;
  3use ark_crypto_primitives::sponge::Absorb;
  4use ark_ec::{AffineRepr, CurveGroup, Group};
  5use ark_ff::{BigInteger, PrimeField};
  6use ark_groth16::Groth16;
  7use ark_r1cs_std::{groups::GroupOpsBounds, prelude::CurveVar, ToConstraintFieldGadget};
  8use ark_snark::SNARK;
  9use ark_std::rand::{CryptoRng, RngCore};
 10use ark_std::{One, Zero};
 11use core::marker::PhantomData;
 12
 13pub use super::decider_eth_circuit::{DeciderEthCircuit, KZGChallengesGadget};
 14use super::{nifs::NIFS, CommittedInstance, Nova};
 15use crate::commitment::{
 16    kzg::{Proof as KZGProof, KZG},
 17    pedersen::Params as PedersenParams,
 18    CommitmentScheme,
 19};
 20use crate::folding::circuits::{nonnative::affine::NonNativeAffineVar, CF2};
 21use crate::frontend::FCircuit;
 22use crate::Error;
 23use crate::{Decider as DeciderTrait, FoldingScheme};
 24
 25#[derive(Debug, Clone, Eq, PartialEq)]
 26pub struct Proof<C1, CS1, S>
 27where
 28    C1: CurveGroup,
 29    CS1: CommitmentScheme<C1, ProverChallenge = C1::ScalarField, Challenge = C1::ScalarField>,
 30    S: SNARK<C1::ScalarField>,
 31{
 32    snark_proof: S::Proof,
 33    kzg_proofs: [CS1::Proof; 2],
 34    // cmT and r are values for the last fold, U_{i+1}=NIFS.V(r, U_i, u_i, cmT), and they are
 35    // checked in-circuit
 36    cmT: C1,
 37    r: C1::ScalarField,
 38    // the KZG challenges are provided by the prover, but in-circuit they are checked to match
 39    // the in-circuit computed computed ones.
 40    kzg_challenges: [C1::ScalarField; 2],
 41}
 42
 43/// Onchain Decider, for ethereum use cases
 44#[derive(Clone, Debug)]
 45pub struct Decider<C1, GC1, C2, GC2, FC, CS1, CS2, S, FS> {
 46    _c1: PhantomData<C1>,
 47    _gc1: PhantomData<GC1>,
 48    _c2: PhantomData<C2>,
 49    _gc2: PhantomData<GC2>,
 50    _fc: PhantomData<FC>,
 51    _cs1: PhantomData<CS1>,
 52    _cs2: PhantomData<CS2>,
 53    _s: PhantomData<S>,
 54    _fs: PhantomData<FS>,
 55}
 56
 57impl<C1, GC1, C2, GC2, FC, CS1, CS2, S, FS> DeciderTrait<C1, C2, FC, FS>
 58    for Decider<C1, GC1, C2, GC2, FC, CS1, CS2, S, FS>
 59where
 60    C1: CurveGroup,
 61    C2: CurveGroup,
 62    GC1: CurveVar<C1, CF2<C1>> + ToConstraintFieldGadget<CF2<C1>>,
 63    GC2: CurveVar<C2, CF2<C2>> + ToConstraintFieldGadget<CF2<C2>>,
 64    FC: FCircuit<C1::ScalarField>,
 65    // CS1 is a KZG commitment, where challenge is C1::Fr elem
 66    CS1: CommitmentScheme<
 67        C1,
 68        ProverChallenge = C1::ScalarField,
 69        Challenge = C1::ScalarField,
 70        Proof = KZGProof<C1>,
 71    >,
 72    // enforce that the CS2 is Pedersen commitment scheme, since we're at Ethereum's EVM decider
 73    CS2: CommitmentScheme<C2, ProverParams = PedersenParams<C2>>,
 74    S: SNARK<C1::ScalarField>,
 75    FS: FoldingScheme<C1, C2, FC>,
 76    <C1 as CurveGroup>::BaseField: PrimeField,
 77    <C2 as CurveGroup>::BaseField: PrimeField,
 78    <C1 as Group>::ScalarField: Absorb,
 79    <C2 as Group>::ScalarField: Absorb,
 80    C1: CurveGroup<BaseField = C2::ScalarField, ScalarField = C2::BaseField>,
 81    for<'b> &'b GC1: GroupOpsBounds<'b, C1, GC1>,
 82    for<'b> &'b GC2: GroupOpsBounds<'b, C2, GC2>,
 83    // constrain FS into Nova, since this is a Decider specifically for Nova
 84    Nova<C1, GC1, C2, GC2, FC, CS1, CS2>: From<FS>,
 85    crate::folding::nova::ProverParams<C1, C2, CS1, CS2>:
 86        From<<FS as FoldingScheme<C1, C2, FC>>::ProverParam>,
 87    crate::folding::nova::VerifierParams<C1, C2, CS1, CS2>:
 88        From<<FS as FoldingScheme<C1, C2, FC>>::VerifierParam>,
 89{
 90    type PreprocessorParam = (FS::ProverParam, FS::VerifierParam);
 91    type ProverParam = (S::ProvingKey, CS1::ProverParams);
 92    type Proof = Proof<C1, CS1, S>;
 93    /// VerifierParam = (pp_hash, snark::vk, commitment_scheme::vk)
 94    type VerifierParam = (C1::ScalarField, S::VerifyingKey, CS1::VerifierParams);
 95    type PublicInput = Vec<C1::ScalarField>;
 96    type CommittedInstance = CommittedInstance<C1>;
 97
 98    fn preprocess(
 99        mut rng: impl RngCore + CryptoRng,
100        prep_param: &Self::PreprocessorParam,
101        fs: FS,
102    ) -> Result<(Self::ProverParam, Self::VerifierParam), Error> {
103        let circuit =
104            DeciderEthCircuit::<C1, GC1, C2, GC2, CS1, CS2>::from_nova::<FC>(fs.into()).unwrap();
105
106        // get the Groth16 specific setup for the circuit
107        let (g16_pk, g16_vk) = S::circuit_specific_setup(circuit, &mut rng).unwrap();
108
109        // get the FoldingScheme prover & verifier params from Nova
110        #[allow(clippy::type_complexity)]
111        let nova_pp:
112            <Nova<C1, GC1, C2, GC2, FC, CS1, CS2> as FoldingScheme<C1, C2, FC>>::ProverParam =
113                prep_param.0.clone().into()
114            ;
115        #[allow(clippy::type_complexity)]
116        let nova_vp:
117            <Nova<C1, GC1, C2, GC2, FC, CS1, CS2> as FoldingScheme<C1, C2, FC>>::VerifierParam =
118                prep_param.1.clone().into();
119        let pp_hash = nova_vp.pp_hash()?;
120
121        let pp = (g16_pk, nova_pp.cs_pp);
122        let vp = (pp_hash, g16_vk, nova_vp.cs_vp);
123        Ok((pp, vp))
124    }
125
126    fn prove(
127        mut rng: impl RngCore + CryptoRng,
128        pp: Self::ProverParam,
129        folding_scheme: FS,
130    ) -> Result<Self::Proof, Error> {
131        let (snark_pk, cs_pk): (S::ProvingKey, CS1::ProverParams) = pp;
132
133        let circuit = DeciderEthCircuit::<C1, GC1, C2, GC2, CS1, CS2>::from_nova::<FC>(
134            folding_scheme.into(),
135        )?;
136
137        let snark_proof = S::prove(&snark_pk, circuit.clone(), &mut rng)
138            .map_err(|e| Error::Other(e.to_string()))?;
139
140        let cmT = circuit.cmT.unwrap();
141        let r_Fr = circuit.r.unwrap();
142        let W_i1 = circuit.W_i1.unwrap();
143
144        // get the challenges that have been already computed when preparing the circuit inputs in
145        // the above `from_nova` call
146        let challenge_W = circuit
147            .kzg_c_W
148            .ok_or(Error::MissingValue("kzg_c_W".to_string()))?;
149        let challenge_E = circuit
150            .kzg_c_E
151            .ok_or(Error::MissingValue("kzg_c_E".to_string()))?;
152
153        // generate KZG proofs
154        let U_cmW_proof = CS1::prove_with_challenge(
155            &cs_pk,
156            challenge_W,
157            &W_i1.W,
158            &C1::ScalarField::zero(),
159            None,
160        )?;
161        let U_cmE_proof = CS1::prove_with_challenge(
162            &cs_pk,
163            challenge_E,
164            &W_i1.E,
165            &C1::ScalarField::zero(),
166            None,
167        )?;
168
169        Ok(Self::Proof {
170            snark_proof,
171            kzg_proofs: [U_cmW_proof, U_cmE_proof],
172            cmT,
173            r: r_Fr,
174            kzg_challenges: [challenge_W, challenge_E],
175        })
176    }
177
178    fn verify(
179        vp: Self::VerifierParam,
180        i: C1::ScalarField,
181        z_0: Vec<C1::ScalarField>,
182        z_i: Vec<C1::ScalarField>,
183        running_instance: &Self::CommittedInstance,
184        incoming_instance: &Self::CommittedInstance,
185        proof: &Self::Proof,
186    ) -> Result<bool, Error> {
187        if i <= C1::ScalarField::one() {
188            return Err(Error::NotEnoughSteps);
189        }
190
191        let (pp_hash, snark_vk, cs_vk): (C1::ScalarField, S::VerifyingKey, CS1::VerifierParams) =
192            vp;
193
194        // compute U = U_{d+1}= NIFS.V(U_d, u_d, cmT)
195        let U = NIFS::<C1, CS1>::verify(proof.r, running_instance, incoming_instance, &proof.cmT);
196
197        let (cmE_x, cmE_y) = NonNativeAffineVar::inputize(U.cmE)?;
198        let (cmW_x, cmW_y) = NonNativeAffineVar::inputize(U.cmW)?;
199        let (cmT_x, cmT_y) = NonNativeAffineVar::inputize(proof.cmT)?;
200
201        let public_input: Vec<C1::ScalarField> = vec![
202            vec![pp_hash, i],
203            z_0,
204            z_i,
205            vec![U.u],
206            U.x.clone(),
207            cmE_x,
208            cmE_y,
209            cmW_x,
210            cmW_y,
211            proof.kzg_challenges.to_vec(),
212            vec![
213                proof.kzg_proofs[0].eval, // eval_W
214                proof.kzg_proofs[1].eval, // eval_E
215            ],
216            cmT_x,
217            cmT_y,
218            vec![proof.r],
219        ]
220        .concat();
221
222        let snark_v = S::verify(&snark_vk, &public_input, &proof.snark_proof)
223            .map_err(|e| Error::Other(e.to_string()))?;
224        if !snark_v {
225            return Err(Error::SNARKVerificationFail);
226        }
227
228        // we're at the Ethereum EVM case, so the CS1 is KZG commitments
229        CS1::verify_with_challenge(
230            &cs_vk,
231            proof.kzg_challenges[0],
232            &U.cmW,
233            &proof.kzg_proofs[0],
234        )?;
235        CS1::verify_with_challenge(
236            &cs_vk,
237            proof.kzg_challenges[1],
238            &U.cmE,
239            &proof.kzg_proofs[1],
240        )?;
241
242        Ok(true)
243    }
244}
245
246/// Prepares solidity calldata for calling the NovaDecider contract
247pub fn prepare_calldata(
248    function_signature_check: [u8; 4],
249    i: ark_bn254::Fr,
250    z_0: Vec<ark_bn254::Fr>,
251    z_i: Vec<ark_bn254::Fr>,
252    running_instance: &CommittedInstance<ark_bn254::G1Projective>,
253    incoming_instance: &CommittedInstance<ark_bn254::G1Projective>,
254    proof: Proof<ark_bn254::G1Projective, KZG<'static, Bn254>, Groth16<Bn254>>,
255) -> Result<Vec<u8>, Error> {
256    Ok(vec![
257        function_signature_check.to_vec(),
258        i.into_bigint().to_bytes_be(), // i
259        z_0.iter()
260            .flat_map(|v| v.into_bigint().to_bytes_be())
261            .collect::<Vec<u8>>(), // z_0
262        z_i.iter()
263            .flat_map(|v| v.into_bigint().to_bytes_be())
264            .collect::<Vec<u8>>(), // z_i
265        point_to_eth_format(running_instance.cmW.into_affine())?, // U_i_cmW
266        point_to_eth_format(running_instance.cmE.into_affine())?, // U_i_cmE
267        running_instance.u.into_bigint().to_bytes_be(), // U_i_u
268        incoming_instance.u.into_bigint().to_bytes_be(), // u_i_u
269        proof.r.into_bigint().to_bytes_be(), // r
270        running_instance
271            .x
272            .iter()
273            .flat_map(|v| v.into_bigint().to_bytes_be())
274            .collect::<Vec<u8>>(), // U_i_x
275        point_to_eth_format(incoming_instance.cmW.into_affine())?, // u_i_cmW
276        incoming_instance
277            .x
278            .iter()
279            .flat_map(|v| v.into_bigint().to_bytes_be())
280            .collect::<Vec<u8>>(), // u_i_x
281        point_to_eth_format(proof.cmT.into_affine())?, // cmT
282        point_to_eth_format(proof.snark_proof.a)?, // pA
283        point2_to_eth_format(proof.snark_proof.b)?, // pB
284        point_to_eth_format(proof.snark_proof.c)?, // pC
285        proof.kzg_challenges[0].into_bigint().to_bytes_be(), // challenge_W
286        proof.kzg_challenges[1].into_bigint().to_bytes_be(), // challenge_E
287        proof.kzg_proofs[0].eval.into_bigint().to_bytes_be(), // eval W
288        proof.kzg_proofs[1].eval.into_bigint().to_bytes_be(), // eval E
289        point_to_eth_format(proof.kzg_proofs[0].proof.into_affine())?, // W kzg_proof
290        point_to_eth_format(proof.kzg_proofs[1].proof.into_affine())?, // E kzg_proof
291    ]
292    .concat())
293}
294
295fn point_to_eth_format<C: AffineRepr>(p: C) -> Result<Vec<u8>, Error>
296where
297    C::BaseField: PrimeField,
298{
299    // the encoding of the additive identity is [0, 0] on the EVM
300    let zero_point = (&C::BaseField::zero(), &C::BaseField::zero());
301    let (x, y) = p.xy().unwrap_or(zero_point);
302
303    Ok([x.into_bigint().to_bytes_be(), y.into_bigint().to_bytes_be()].concat())
304}
305fn point2_to_eth_format(p: ark_bn254::G2Affine) -> Result<Vec<u8>, Error> {
306    let zero_point = (&ark_bn254::Fq2::zero(), &ark_bn254::Fq2::zero());
307    let (x, y) = p.xy().unwrap_or(zero_point);
308
309    Ok([
310        x.c1.into_bigint().to_bytes_be(),
311        x.c0.into_bigint().to_bytes_be(),
312        y.c1.into_bigint().to_bytes_be(),
313        y.c0.into_bigint().to_bytes_be(),
314    ]
315    .concat())
316}
317
318#[cfg(test)]
319pub mod tests {
320    use ark_bn254::{constraints::GVar, Bn254, Fr, G1Projective as Projective};
321    use ark_groth16::Groth16;
322    use ark_grumpkin::{constraints::GVar as GVar2, Projective as Projective2};
323    use std::time::Instant;
324
325    use super::*;
326    use crate::commitment::kzg::KZG;
327    use crate::commitment::pedersen::Pedersen;
328    use crate::folding::nova::PreprocessorParam;
329    use crate::frontend::tests::CubicFCircuit;
330    use crate::transcript::poseidon::poseidon_canonical_config;
331
332    #[test]
333    fn test_decider() {
334        // use Nova as FoldingScheme
335        type N = Nova<
336            Projective,
337            GVar,
338            Projective2,
339            GVar2,
340            CubicFCircuit<Fr>,
341            KZG<'static, Bn254>,
342            Pedersen<Projective2>,
343        >;
344        type D = Decider<
345            Projective,
346            GVar,
347            Projective2,
348            GVar2,
349            CubicFCircuit<Fr>,
350            KZG<'static, Bn254>,
351            Pedersen<Projective2>,
352            Groth16<Bn254>, // here we define the Snark to use in the decider
353            N,              // here we define the FoldingScheme to use
354        >;
355
356        let mut rng = ark_std::test_rng();
357        let poseidon_config = poseidon_canonical_config::<Fr>();
358
359        let F_circuit = CubicFCircuit::<Fr>::new(()).unwrap();
360        let z_0 = vec![Fr::from(3_u32)];
361
362        let prep_param = PreprocessorParam::new(poseidon_config, F_circuit);
363        let nova_params = N::preprocess(&mut rng, &prep_param).unwrap();
364
365        let start = Instant::now();
366        let mut nova = N::init(nova_params.clone(), F_circuit, z_0.clone()).unwrap();
367        println!("Nova initialized, {:?}", start.elapsed());
368        let start = Instant::now();
369        nova.prove_step(&mut rng, vec![]).unwrap();
370        println!("prove_step, {:?}", start.elapsed());
371        nova.prove_step(&mut rng, vec![]).unwrap(); // do a 2nd step
372
373        let mut rng = rand::rngs::OsRng;
374
375        // prepare the Decider prover & verifier params
376        let (decider_pp, decider_vp) = D::preprocess(&mut rng, &nova_params, nova.clone()).unwrap();
377
378        // decider proof generation
379        let start = Instant::now();
380        let proof = D::prove(rng, decider_pp, nova.clone()).unwrap();
381        println!("Decider prove, {:?}", start.elapsed());
382
383        // decider proof verification
384        let start = Instant::now();
385        let verified = D::verify(
386            decider_vp, nova.i, nova.z_0, nova.z_i, &nova.U_i, &nova.u_i, &proof,
387        )
388        .unwrap();
389        assert!(verified);
390        println!("Decider verify, {:?}", start.elapsed());
391    }
392}