arnaucube
arnaucube/sonobePublic Self-hosted repository
sonobe/folding-schemes/src/folding/nova/decider_circuits.rs
folding-schemes/src/folding/nova/decider_circuits.rsRaw
  1/// This file implements the offchain decider circuit. For ethereum use cases, use the
  2/// DeciderEthCircuit.
  3/// More details can be found at the documentation page:
  4/// https://privacy-scaling-explorations.github.io/sonobe-docs/design/nova-decider-offchain.html
  5use ark_crypto_primitives::sponge::{poseidon::PoseidonSponge, Absorb, CryptographicSponge};
  6use ark_ec::CurveGroup;
  7use ark_ff::{BigInteger, PrimeField};
  8use ark_r1cs_std::{fields::fp::FpVar, prelude::CurveVar, ToConstraintFieldGadget};
  9use core::marker::PhantomData;
 10
 11use super::{
 12    decider_eth_circuit::DeciderNovaGadget,
 13    nifs::{nova::NIFS, NIFSTrait},
 14    CommittedInstance, Nova, Witness,
 15};
 16use crate::folding::{
 17    circuits::{CF1, CF2},
 18    traits::WitnessOps,
 19};
 20use crate::frontend::FCircuit;
 21use crate::Error;
 22use crate::{
 23    arith::r1cs::{circuits::R1CSMatricesVar, R1CS},
 24    folding::circuits::decider::{
 25        off_chain::{GenericOffchainDeciderCircuit1, GenericOffchainDeciderCircuit2},
 26        EvalGadget, KZGChallengesGadget,
 27    },
 28};
 29use crate::{commitment::CommitmentScheme, transcript::poseidon::poseidon_canonical_config};
 30
 31/// Circuit that implements part of the in-circuit checks needed for the offchain verification over
 32/// the Curve2's BaseField (=Curve1's ScalarField).
 33
 34pub type DeciderCircuit1<C1, C2, GC2> = GenericOffchainDeciderCircuit1<
 35    C1,
 36    C2,
 37    GC2,
 38    CommittedInstance<C1>,
 39    CommittedInstance<C1>,
 40    Witness<C1>,
 41    R1CS<CF1<C1>>,
 42    R1CSMatricesVar<CF1<C1>, FpVar<CF1<C1>>>,
 43    DeciderNovaGadget,
 44>;
 45
 46impl<
 47        C1: CurveGroup,
 48        GC1: CurveVar<C1, CF2<C1>> + ToConstraintFieldGadget<CF2<C1>>,
 49        C2: CurveGroup,
 50        GC2: CurveVar<C2, CF2<C2>> + ToConstraintFieldGadget<CF2<C2>>,
 51        FC: FCircuit<C1::ScalarField>,
 52        CS1: CommitmentScheme<C1, H>,
 53        CS2: CommitmentScheme<C2, H>,
 54        const H: bool,
 55    > TryFrom<Nova<C1, GC1, C2, GC2, FC, CS1, CS2, H>> for DeciderCircuit1<C1, C2, GC2>
 56where
 57    CF1<C1>: Absorb,
 58    <C1 as CurveGroup>::BaseField: PrimeField,
 59{
 60    type Error = Error;
 61
 62    fn try_from(nova: Nova<C1, GC1, C2, GC2, FC, CS1, CS2, H>) -> Result<Self, Error> {
 63        let mut transcript = PoseidonSponge::<C1::ScalarField>::new(&nova.poseidon_config);
 64        // pp_hash is absorbed to transcript at the NIFS::prove call
 65
 66        // compute the U_{i+1}, W_{i+1}
 67        let (W_i1, U_i1, cmT, r_bits) = NIFS::<C1, CS1, PoseidonSponge<C1::ScalarField>, H>::prove(
 68            &nova.cs_pp,
 69            &nova.r1cs.clone(),
 70            &mut transcript,
 71            nova.pp_hash,
 72            &nova.W_i,
 73            &nova.U_i,
 74            &nova.w_i,
 75            &nova.u_i,
 76        )?;
 77        let r_Fr = C1::ScalarField::from_bigint(BigInteger::from_bits_le(&r_bits))
 78            .ok_or(Error::OutOfBounds)?;
 79
 80        // compute the KZG challenges used as inputs in the circuit
 81        let kzg_challenges = KZGChallengesGadget::get_challenges_native(&mut transcript, &U_i1);
 82
 83        // get KZG evals
 84        let kzg_evaluations = W_i1
 85            .get_openings()
 86            .iter()
 87            .zip(&kzg_challenges)
 88            .map(|((v, _), &c)| EvalGadget::evaluate_native(v, c))
 89            .collect::<Result<Vec<_>, _>>()?;
 90
 91        Ok(Self {
 92            _gc2: PhantomData,
 93            _avar: PhantomData,
 94            arith: nova.r1cs,
 95            poseidon_config: nova.poseidon_config,
 96            pp_hash: nova.pp_hash,
 97            i: nova.i,
 98            z_0: nova.z_0,
 99            z_i: nova.z_i,
100            U_i: nova.U_i,
101            W_i: nova.W_i,
102            u_i: nova.u_i,
103            w_i: nova.w_i,
104            U_i1,
105            W_i1,
106            proof: cmT,
107            randomness: r_Fr,
108            cf_U_i: nova.cf_U_i,
109            kzg_challenges,
110            kzg_evaluations,
111        })
112    }
113}
114
115/// Circuit that implements part of the in-circuit checks needed for the offchain verification over
116/// the Curve1's BaseField (=Curve2's ScalarField).
117pub type DeciderCircuit2<C2> = GenericOffchainDeciderCircuit2<C2>;
118
119impl<
120        C1: CurveGroup,
121        GC1: CurveVar<C1, CF2<C1>> + ToConstraintFieldGadget<CF2<C1>>,
122        C2: CurveGroup,
123        GC2: CurveVar<C2, CF2<C2>> + ToConstraintFieldGadget<CF2<C2>>,
124        FC: FCircuit<C1::ScalarField>,
125        CS1: CommitmentScheme<C1, H>,
126        CS2: CommitmentScheme<C2, H>,
127        const H: bool,
128    > TryFrom<Nova<C1, GC1, C2, GC2, FC, CS1, CS2, H>> for DeciderCircuit2<C2>
129where
130    CF1<C2>: Absorb,
131{
132    type Error = Error;
133
134    fn try_from(nova: Nova<C1, GC1, C2, GC2, FC, CS1, CS2, H>) -> Result<Self, Error> {
135        // compute the Commitment Scheme challenges of the CycleFold instance commitments, used as
136        // inputs in the circuit
137        let poseidon_config = poseidon_canonical_config::<C2::ScalarField>();
138        let mut transcript = PoseidonSponge::<C2::ScalarField>::new(&poseidon_config);
139        let pp_hash_Fq =
140            C2::ScalarField::from_le_bytes_mod_order(&nova.pp_hash.into_bigint().to_bytes_le());
141        transcript.absorb(&pp_hash_Fq);
142
143        // compute the KZG challenges used as inputs in the circuit
144        let kzg_challenges =
145            KZGChallengesGadget::get_challenges_native(&mut transcript, &nova.cf_U_i);
146
147        // get KZG evals
148        let kzg_evaluations = nova
149            .cf_W_i
150            .get_openings()
151            .iter()
152            .zip(&kzg_challenges)
153            .map(|((v, _), &c)| EvalGadget::evaluate_native(v, c))
154            .collect::<Result<Vec<_>, _>>()?;
155
156        Ok(Self {
157            cf_arith: nova.cf_r1cs,
158            poseidon_config,
159            pp_hash: pp_hash_Fq,
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
168#[cfg(test)]
169pub mod tests {
170    use ark_pallas::{constraints::GVar, Fq, Fr, Projective};
171    use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystem};
172    use ark_vesta::{constraints::GVar as GVar2, Projective as Projective2};
173
174    use super::*;
175    use crate::commitment::pedersen::Pedersen;
176    use crate::folding::nova::PreprocessorParam;
177    use crate::frontend::utils::CubicFCircuit;
178    use crate::transcript::poseidon::poseidon_canonical_config;
179    use crate::FoldingScheme;
180
181    #[test]
182    fn test_decider_circuits() {
183        let mut rng = ark_std::test_rng();
184        let poseidon_config = poseidon_canonical_config::<Fr>();
185
186        let F_circuit = CubicFCircuit::<Fr>::new(()).unwrap();
187        let z_0 = vec![Fr::from(3_u32)];
188
189        type N = Nova<
190            Projective,
191            GVar,
192            Projective2,
193            GVar2,
194            CubicFCircuit<Fr>,
195            Pedersen<Projective>,
196            Pedersen<Projective2>,
197            false,
198        >;
199
200        let prep_param = PreprocessorParam::<
201            Projective,
202            Projective2,
203            CubicFCircuit<Fr>,
204            Pedersen<Projective>,
205            Pedersen<Projective2>,
206            false,
207        >::new(poseidon_config, F_circuit);
208        let nova_params = N::preprocess(&mut rng, &prep_param).unwrap();
209
210        // generate a Nova instance and do a step of it
211        let mut nova = N::init(&nova_params, F_circuit, z_0.clone()).unwrap();
212        nova.prove_step(&mut rng, vec![], None).unwrap();
213        // verify the IVC
214        let ivc_proof = nova.ivc_proof();
215        N::verify(nova_params.1, ivc_proof).unwrap();
216
217        // load the DeciderCircuit 1 & 2 from the Nova instance
218        let decider_circuit1 =
219            DeciderCircuit1::<Projective, Projective2, GVar2>::try_from(nova.clone()).unwrap();
220        let decider_circuit2 = DeciderCircuit2::<Projective2>::try_from(nova).unwrap();
221
222        // generate the constraints of both circuits and check that are satisfied by the inputs
223        let cs1 = ConstraintSystem::<Fr>::new_ref();
224        decider_circuit1.generate_constraints(cs1.clone()).unwrap();
225        assert!(cs1.is_satisfied().unwrap());
226        let cs2 = ConstraintSystem::<Fq>::new_ref();
227        decider_circuit2.generate_constraints(cs2.clone()).unwrap();
228        assert!(cs2.is_satisfied().unwrap());
229    }
230}