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, false>: From<FS>,
85 crate::folding::nova::ProverParams<C1, C2, CS1, CS2, false>:
86 From<<FS as FoldingScheme<C1, C2, FC>>::ProverParam>,
87 crate::folding::nova::VerifierParams<C1, C2, CS1, CS2, false>:
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: <Nova<C1, GC1, C2, GC2, FC, CS1, CS2, false> as FoldingScheme<
112 C1,
113 C2,
114 FC,
115 >>::ProverParam = prep_param.0.clone().into();
116 #[allow(clippy::type_complexity)]
117 let nova_vp: <Nova<C1, GC1, C2, GC2, FC, CS1, CS2, false> as FoldingScheme<
118 C1,
119 C2,
120 FC,
121 >>::VerifierParam = prep_param.1.clone().into();
122 let pp_hash = nova_vp.pp_hash()?;
123
124 let pp = (g16_pk, nova_pp.cs_pp);
125 let vp = (pp_hash, g16_vk, nova_vp.cs_vp);
126 Ok((pp, vp))
127 }
128
129 fn prove(
130 mut rng: impl RngCore + CryptoRng,
131 pp: Self::ProverParam,
132 folding_scheme: FS,
133 ) -> Result<Self::Proof, Error> {
134 let (snark_pk, cs_pk): (S::ProvingKey, CS1::ProverParams) = pp;
135
136 let circuit = DeciderEthCircuit::<C1, GC1, C2, GC2, CS1, CS2>::from_nova::<FC>(
137 folding_scheme.into(),
138 )?;
139
140 let snark_proof = S::prove(&snark_pk, circuit.clone(), &mut rng)
141 .map_err(|e| Error::Other(e.to_string()))?;
142
143 let cmT = circuit.cmT.unwrap();
144 let r_Fr = circuit.r.unwrap();
145 let W_i1 = circuit.W_i1.unwrap();
146
147 // get the challenges that have been already computed when preparing the circuit inputs in
148 // the above `from_nova` call
149 let challenge_W = circuit
150 .kzg_c_W
151 .ok_or(Error::MissingValue("kzg_c_W".to_string()))?;
152 let challenge_E = circuit
153 .kzg_c_E
154 .ok_or(Error::MissingValue("kzg_c_E".to_string()))?;
155
156 // generate KZG proofs
157 let U_cmW_proof = CS1::prove_with_challenge(
158 &cs_pk,
159 challenge_W,
160 &W_i1.W,
161 &C1::ScalarField::zero(),
162 None,
163 )?;
164 let U_cmE_proof = CS1::prove_with_challenge(
165 &cs_pk,
166 challenge_E,
167 &W_i1.E,
168 &C1::ScalarField::zero(),
169 None,
170 )?;
171
172 Ok(Self::Proof {
173 snark_proof,
174 kzg_proofs: [U_cmW_proof, U_cmE_proof],
175 cmT,
176 r: r_Fr,
177 kzg_challenges: [challenge_W, challenge_E],
178 })
179 }
180
181 fn verify(
182 vp: Self::VerifierParam,
183 i: C1::ScalarField,
184 z_0: Vec<C1::ScalarField>,
185 z_i: Vec<C1::ScalarField>,
186 running_instance: &Self::CommittedInstance,
187 incoming_instance: &Self::CommittedInstance,
188 proof: &Self::Proof,
189 ) -> Result<bool, Error> {
190 if i <= C1::ScalarField::one() {
191 return Err(Error::NotEnoughSteps);
192 }
193
194 let (pp_hash, snark_vk, cs_vk): (C1::ScalarField, S::VerifyingKey, CS1::VerifierParams) =
195 vp;
196
197 // compute U = U_{d+1}= NIFS.V(U_d, u_d, cmT)
198 let U = NIFS::<C1, CS1>::verify(proof.r, running_instance, incoming_instance, &proof.cmT);
199
200 let (cmE_x, cmE_y) = NonNativeAffineVar::inputize(U.cmE)?;
201 let (cmW_x, cmW_y) = NonNativeAffineVar::inputize(U.cmW)?;
202 let (cmT_x, cmT_y) = NonNativeAffineVar::inputize(proof.cmT)?;
203
204 let public_input: Vec<C1::ScalarField> = vec![
205 vec![pp_hash, i],
206 z_0,
207 z_i,
208 vec![U.u],
209 U.x.clone(),
210 cmE_x,
211 cmE_y,
212 cmW_x,
213 cmW_y,
214 proof.kzg_challenges.to_vec(),
215 vec![
216 proof.kzg_proofs[0].eval, // eval_W
217 proof.kzg_proofs[1].eval, // eval_E
218 ],
219 cmT_x,
220 cmT_y,
221 vec![proof.r],
222 ]
223 .concat();
224
225 let snark_v = S::verify(&snark_vk, &public_input, &proof.snark_proof)
226 .map_err(|e| Error::Other(e.to_string()))?;
227 if !snark_v {
228 return Err(Error::SNARKVerificationFail);
229 }
230
231 // we're at the Ethereum EVM case, so the CS1 is KZG commitments
232 CS1::verify_with_challenge(
233 &cs_vk,
234 proof.kzg_challenges[0],
235 &U.cmW,
236 &proof.kzg_proofs[0],
237 )?;
238 CS1::verify_with_challenge(
239 &cs_vk,
240 proof.kzg_challenges[1],
241 &U.cmE,
242 &proof.kzg_proofs[1],
243 )?;
244
245 Ok(true)
246 }
247}
248
249/// Prepares solidity calldata for calling the NovaDecider contract
250pub fn prepare_calldata(
251 function_signature_check: [u8; 4],
252 i: ark_bn254::Fr,
253 z_0: Vec<ark_bn254::Fr>,
254 z_i: Vec<ark_bn254::Fr>,
255 running_instance: &CommittedInstance<ark_bn254::G1Projective>,
256 incoming_instance: &CommittedInstance<ark_bn254::G1Projective>,
257 proof: Proof<ark_bn254::G1Projective, KZG<'static, Bn254>, Groth16<Bn254>>,
258) -> Result<Vec<u8>, Error> {
259 Ok(vec![
260 function_signature_check.to_vec(),
261 i.into_bigint().to_bytes_be(), // i
262 z_0.iter()
263 .flat_map(|v| v.into_bigint().to_bytes_be())
264 .collect::<Vec<u8>>(), // z_0
265 z_i.iter()
266 .flat_map(|v| v.into_bigint().to_bytes_be())
267 .collect::<Vec<u8>>(), // z_i
268 point_to_eth_format(running_instance.cmW.into_affine())?, // U_i_cmW
269 point_to_eth_format(running_instance.cmE.into_affine())?, // U_i_cmE
270 running_instance.u.into_bigint().to_bytes_be(), // U_i_u
271 incoming_instance.u.into_bigint().to_bytes_be(), // u_i_u
272 proof.r.into_bigint().to_bytes_be(), // r
273 running_instance
274 .x
275 .iter()
276 .flat_map(|v| v.into_bigint().to_bytes_be())
277 .collect::<Vec<u8>>(), // U_i_x
278 point_to_eth_format(incoming_instance.cmW.into_affine())?, // u_i_cmW
279 incoming_instance
280 .x
281 .iter()
282 .flat_map(|v| v.into_bigint().to_bytes_be())
283 .collect::<Vec<u8>>(), // u_i_x
284 point_to_eth_format(proof.cmT.into_affine())?, // cmT
285 point_to_eth_format(proof.snark_proof.a)?, // pA
286 point2_to_eth_format(proof.snark_proof.b)?, // pB
287 point_to_eth_format(proof.snark_proof.c)?, // pC
288 proof.kzg_challenges[0].into_bigint().to_bytes_be(), // challenge_W
289 proof.kzg_challenges[1].into_bigint().to_bytes_be(), // challenge_E
290 proof.kzg_proofs[0].eval.into_bigint().to_bytes_be(), // eval W
291 proof.kzg_proofs[1].eval.into_bigint().to_bytes_be(), // eval E
292 point_to_eth_format(proof.kzg_proofs[0].proof.into_affine())?, // W kzg_proof
293 point_to_eth_format(proof.kzg_proofs[1].proof.into_affine())?, // E kzg_proof
294 ]
295 .concat())
296}
297
298fn point_to_eth_format<C: AffineRepr>(p: C) -> Result<Vec<u8>, Error>
299where
300 C::BaseField: PrimeField,
301{
302 // the encoding of the additive identity is [0, 0] on the EVM
303 let zero_point = (&C::BaseField::zero(), &C::BaseField::zero());
304 let (x, y) = p.xy().unwrap_or(zero_point);
305
306 Ok([x.into_bigint().to_bytes_be(), y.into_bigint().to_bytes_be()].concat())
307}
308fn point2_to_eth_format(p: ark_bn254::G2Affine) -> Result<Vec<u8>, Error> {
309 let zero_point = (&ark_bn254::Fq2::zero(), &ark_bn254::Fq2::zero());
310 let (x, y) = p.xy().unwrap_or(zero_point);
311
312 Ok([
313 x.c1.into_bigint().to_bytes_be(),
314 x.c0.into_bigint().to_bytes_be(),
315 y.c1.into_bigint().to_bytes_be(),
316 y.c0.into_bigint().to_bytes_be(),
317 ]
318 .concat())
319}
320
321#[cfg(test)]
322pub mod tests {
323 use ark_bn254::{constraints::GVar, Fr, G1Projective as Projective};
324 use ark_grumpkin::{constraints::GVar as GVar2, Projective as Projective2};
325 use std::time::Instant;
326
327 use super::*;
328 use crate::commitment::pedersen::Pedersen;
329 use crate::folding::nova::PreprocessorParam;
330 use crate::frontend::tests::CubicFCircuit;
331 use crate::transcript::poseidon::poseidon_canonical_config;
332
333 #[test]
334 fn test_decider() {
335 // use Nova as FoldingScheme
336 type N = Nova<
337 Projective,
338 GVar,
339 Projective2,
340 GVar2,
341 CubicFCircuit<Fr>,
342 KZG<'static, Bn254>,
343 Pedersen<Projective2>,
344 false,
345 >;
346 type D = Decider<
347 Projective,
348 GVar,
349 Projective2,
350 GVar2,
351 CubicFCircuit<Fr>,
352 KZG<'static, Bn254>,
353 Pedersen<Projective2>,
354 Groth16<Bn254>, // here we define the Snark to use in the decider
355 N, // here we define the FoldingScheme to use
356 >;
357
358 let mut rng = ark_std::test_rng();
359 let poseidon_config = poseidon_canonical_config::<Fr>();
360
361 let F_circuit = CubicFCircuit::<Fr>::new(()).unwrap();
362 let z_0 = vec![Fr::from(3_u32)];
363
364 let prep_param = PreprocessorParam::new(poseidon_config, F_circuit);
365 let nova_params = N::preprocess(&mut rng, &prep_param).unwrap();
366
367 let start = Instant::now();
368 let mut nova = N::init(&nova_params, F_circuit, z_0.clone()).unwrap();
369 println!("Nova initialized, {:?}", start.elapsed());
370 let start = Instant::now();
371 nova.prove_step(&mut rng, vec![], None).unwrap();
372 println!("prove_step, {:?}", start.elapsed());
373 nova.prove_step(&mut rng, vec![], None).unwrap(); // do a 2nd step
374
375 let mut rng = rand::rngs::OsRng;
376
377 // prepare the Decider prover & verifier params
378 let (decider_pp, decider_vp) = D::preprocess(&mut rng, &nova_params, nova.clone()).unwrap();
379
380 // decider proof generation
381 let start = Instant::now();
382 let proof = D::prove(rng, decider_pp, nova.clone()).unwrap();
383 println!("Decider prove, {:?}", start.elapsed());
384
385 // decider proof verification
386 let start = Instant::now();
387 let verified = D::verify(
388 decider_vp, nova.i, nova.z_0, nova.z_i, &nova.U_i, &nova.u_i, &proof,
389 )
390 .unwrap();
391 assert!(verified);
392 println!("Decider verify, {:?}", start.elapsed());
393 }
394}