arnaucube
arnaucube/sonobePublic Self-hosted repository
sonobe/folding-schemes/src/folding/nova/decider_eth_circuit.rs
folding-schemes/src/folding/nova/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.
  3/// More details can be found at the documentation page:
  4/// https://privacy-scaling-explorations.github.io/sonobe-docs/design/nova-decider-onchain.html
  5use ark_crypto_primitives::sponge::{
  6    constraints::CryptographicSpongeVar,
  7    poseidon::{constraints::PoseidonSpongeVar, PoseidonSponge},
  8    Absorb, CryptographicSponge,
  9};
 10use ark_ec::CurveGroup;
 11use ark_ff::{BigInteger, PrimeField};
 12use ark_r1cs_std::{
 13    alloc::{AllocVar, AllocationMode},
 14    fields::fp::FpVar,
 15    prelude::CurveVar,
 16    ToConstraintFieldGadget,
 17};
 18use ark_relations::r1cs::{Namespace, SynthesisError};
 19use ark_std::{borrow::Borrow, marker::PhantomData};
 20
 21use super::{
 22    nifs::nova_circuits::{CommittedInstanceVar, NIFSGadget},
 23    nifs::{nova::NIFS, NIFSGadgetTrait, NIFSTrait},
 24    CommittedInstance, Nova, Witness,
 25};
 26use crate::commitment::{pedersen::Params as PedersenParams, CommitmentScheme};
 27use crate::folding::{
 28    circuits::{
 29        decider::on_chain::GenericOnchainDeciderCircuit, nonnative::affine::NonNativeAffineVar,
 30        CF1, CF2,
 31    },
 32    traits::{WitnessOps, WitnessVarOps},
 33};
 34use crate::frontend::FCircuit;
 35use crate::Error;
 36use crate::{
 37    arith::r1cs::{circuits::R1CSMatricesVar, R1CS},
 38    folding::circuits::decider::{DeciderEnabledNIFS, EvalGadget, KZGChallengesGadget},
 39};
 40
 41/// In-circuit representation of the Witness associated to the CommittedInstance.
 42#[derive(Debug, Clone)]
 43pub struct WitnessVar<C: CurveGroup> {
 44    pub E: Vec<FpVar<C::ScalarField>>,
 45    pub rE: FpVar<C::ScalarField>,
 46    pub W: Vec<FpVar<C::ScalarField>>,
 47    pub rW: FpVar<C::ScalarField>,
 48}
 49
 50impl<C> AllocVar<Witness<C>, CF1<C>> for WitnessVar<C>
 51where
 52    C: CurveGroup,
 53{
 54    fn new_variable<T: Borrow<Witness<C>>>(
 55        cs: impl Into<Namespace<CF1<C>>>,
 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 E: Vec<FpVar<C::ScalarField>> =
 63                Vec::new_variable(cs.clone(), || Ok(val.borrow().E.clone()), mode)?;
 64            let rE =
 65                FpVar::<C::ScalarField>::new_variable(cs.clone(), || Ok(val.borrow().rE), mode)?;
 66
 67            let W: Vec<FpVar<C::ScalarField>> =
 68                Vec::new_variable(cs.clone(), || Ok(val.borrow().W.clone()), mode)?;
 69            let rW =
 70                FpVar::<C::ScalarField>::new_variable(cs.clone(), || Ok(val.borrow().rW), mode)?;
 71
 72            Ok(Self { E, rE, W, rW })
 73        })
 74    }
 75}
 76
 77impl<C: CurveGroup> WitnessVarOps<C::ScalarField> for WitnessVar<C> {
 78    fn get_openings(&self) -> Vec<(&[FpVar<C::ScalarField>], FpVar<C::ScalarField>)> {
 79        vec![(&self.W, self.rW.clone()), (&self.E, self.rE.clone())]
 80    }
 81}
 82
 83pub type DeciderEthCircuit<C1, C2, GC2> = GenericOnchainDeciderCircuit<
 84    C1,
 85    C2,
 86    GC2,
 87    CommittedInstance<C1>,
 88    CommittedInstance<C1>,
 89    Witness<C1>,
 90    R1CS<CF1<C1>>,
 91    R1CSMatricesVar<CF1<C1>, FpVar<CF1<C1>>>,
 92    DeciderNovaGadget,
 93>;
 94
 95/// returns an instance of the DeciderEthCircuit from the given Nova struct
 96impl<
 97        C1: CurveGroup,
 98        GC1: CurveVar<C1, CF2<C1>> + ToConstraintFieldGadget<CF2<C1>>,
 99        C2: CurveGroup,
100        GC2: CurveVar<C2, CF2<C2>> + ToConstraintFieldGadget<CF2<C2>>,
101        FC: FCircuit<C1::ScalarField>,
102        CS1: CommitmentScheme<C1, H>,
103        // enforce that the CS2 is Pedersen commitment scheme, since we're at Ethereum's EVM decider
104        CS2: CommitmentScheme<C2, H, ProverParams = PedersenParams<C2>>,
105        const H: bool,
106    > TryFrom<Nova<C1, GC1, C2, GC2, FC, CS1, CS2, H>> for DeciderEthCircuit<C1, C2, GC2>
107where
108    CF1<C1>: Absorb,
109    <C1 as CurveGroup>::BaseField: PrimeField,
110{
111    type Error = Error;
112
113    fn try_from(nova: Nova<C1, GC1, C2, GC2, FC, CS1, CS2, H>) -> Result<Self, Error> {
114        let mut transcript = PoseidonSponge::<C1::ScalarField>::new(&nova.poseidon_config);
115
116        // compute the U_{i+1}, W_{i+1}
117        let (W_i1, U_i1, cmT, r_bits) = NIFS::<C1, CS1, PoseidonSponge<C1::ScalarField>, H>::prove(
118            &nova.cs_pp,
119            &nova.r1cs.clone(),
120            &mut transcript,
121            nova.pp_hash,
122            &nova.W_i,
123            &nova.U_i,
124            &nova.w_i,
125            &nova.u_i,
126        )?;
127        let r_Fr = C1::ScalarField::from_bigint(BigInteger::from_bits_le(&r_bits))
128            .ok_or(Error::OutOfBounds)?;
129
130        // compute the KZG challenges used as inputs in the circuit
131        let kzg_challenges = KZGChallengesGadget::get_challenges_native(&mut transcript, &U_i1);
132
133        // get KZG evals
134        let kzg_evaluations = W_i1
135            .get_openings()
136            .iter()
137            .zip(&kzg_challenges)
138            .map(|((v, _), &c)| EvalGadget::evaluate_native(v, c))
139            .collect::<Result<Vec<_>, _>>()?;
140
141        Ok(Self {
142            _gc2: PhantomData,
143            _avar: PhantomData,
144            arith: nova.r1cs,
145            cf_arith: nova.cf_r1cs,
146            cf_pedersen_params: nova.cf_cs_pp,
147            poseidon_config: nova.poseidon_config,
148            pp_hash: nova.pp_hash,
149            i: nova.i,
150            z_0: nova.z_0,
151            z_i: nova.z_i,
152            U_i: nova.U_i,
153            W_i: nova.W_i,
154            u_i: nova.u_i,
155            w_i: nova.w_i,
156            U_i1,
157            W_i1,
158            proof: cmT,
159            randomness: r_Fr,
160            cf_U_i: nova.cf_U_i,
161            cf_W_i: nova.cf_W_i,
162            kzg_challenges,
163            kzg_evaluations,
164        })
165    }
166}
167
168pub struct DeciderNovaGadget;
169
170impl<C: CurveGroup>
171    DeciderEnabledNIFS<C, CommittedInstance<C>, CommittedInstance<C>, Witness<C>, R1CS<CF1<C>>>
172    for DeciderNovaGadget
173where
174    CF1<C>: Absorb,
175{
176    type ProofDummyCfg = ();
177    type Proof = C;
178    type RandomnessDummyCfg = ();
179    type Randomness = CF1<C>;
180
181    fn fold_field_elements_gadget(
182        _arith: &R1CS<CF1<C>>,
183        transcript: &mut PoseidonSpongeVar<CF1<C>>,
184        pp_hash: FpVar<CF1<C>>,
185        U: CommittedInstanceVar<C>,
186        U_vec: Vec<FpVar<CF1<C>>>,
187        u: CommittedInstanceVar<C>,
188        proof: C,
189        _randomness: CF1<C>,
190    ) -> Result<CommittedInstanceVar<C>, SynthesisError> {
191        let cs = transcript.cs();
192        let cmT = NonNativeAffineVar::new_input(cs.clone(), || Ok(proof))?;
193        let (new_U, _) = NIFSGadget::verify(transcript, pp_hash, U, U_vec, u, Some(cmT))?;
194        Ok(new_U)
195    }
196
197    fn fold_group_elements_native(
198        U_commitments: &[C],
199        u_commitments: &[C],
200        cmT: Option<Self::Proof>,
201        r: Self::Randomness,
202    ) -> Result<Vec<C>, Error> {
203        let cmT = cmT.ok_or(Error::Empty)?;
204        let U_cmW = U_commitments[0];
205        let U_cmE = U_commitments[1];
206        let u_cmW = u_commitments[0];
207        let u_cmE = u_commitments[1];
208        if !u_cmE.is_zero() {
209            return Err(Error::NotIncomingCommittedInstance);
210        }
211        let cmW = U_cmW + u_cmW.mul(r);
212        let cmE = U_cmE + cmT.mul(r);
213        Ok(vec![cmW, cmE])
214    }
215}
216
217#[cfg(test)]
218pub mod tests {
219    use ark_pallas::{constraints::GVar, Fr, Projective};
220    use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystem};
221    use ark_vesta::{constraints::GVar as GVar2, Projective as Projective2};
222
223    use super::*;
224    use crate::commitment::pedersen::Pedersen;
225    use crate::folding::nova::PreprocessorParam;
226    use crate::frontend::utils::CubicFCircuit;
227    use crate::transcript::poseidon::poseidon_canonical_config;
228    use crate::FoldingScheme;
229
230    #[test]
231    fn test_decider_circuit() {
232        let mut rng = ark_std::test_rng();
233        let poseidon_config = poseidon_canonical_config::<Fr>();
234
235        let F_circuit = CubicFCircuit::<Fr>::new(()).unwrap();
236        let z_0 = vec![Fr::from(3_u32)];
237
238        type N = Nova<
239            Projective,
240            GVar,
241            Projective2,
242            GVar2,
243            CubicFCircuit<Fr>,
244            Pedersen<Projective>,
245            Pedersen<Projective2>,
246            false,
247        >;
248
249        let prep_param = PreprocessorParam::<
250            Projective,
251            Projective2,
252            CubicFCircuit<Fr>,
253            Pedersen<Projective>,
254            Pedersen<Projective2>,
255            false,
256        >::new(poseidon_config, F_circuit);
257        let nova_params = N::preprocess(&mut rng, &prep_param).unwrap();
258
259        // generate a Nova instance and do a step of it
260        let mut nova = N::init(&nova_params, F_circuit, z_0.clone()).unwrap();
261        nova.prove_step(&mut rng, vec![], None).unwrap();
262        let ivc_proof = nova.ivc_proof();
263        N::verify(nova_params.1, ivc_proof).unwrap();
264
265        // load the DeciderEthCircuit from the generated Nova instance
266        let decider_circuit =
267            DeciderEthCircuit::<Projective, Projective2, GVar2>::try_from(nova).unwrap();
268
269        let cs = ConstraintSystem::<Fr>::new_ref();
270
271        // generate the constraints and check that are satisfied by the inputs
272        decider_circuit.generate_constraints(cs.clone()).unwrap();
273        assert!(cs.is_satisfied().unwrap());
274    }
275}