sonobe/folding-schemes/src/folding/nova/decider.rs
folding-schemes/src/folding/nova/decider.rsRaw
1/// This file implements the offchain decider. For ethereum use cases, use the
2/// DeciderEth from decider_eth.rs file.
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::Absorb;
6use ark_ec::{CurveGroup, Group};
7use ark_ff::{BigInteger, PrimeField};
8use ark_r1cs_std::{groups::GroupOpsBounds, prelude::CurveVar, ToConstraintFieldGadget};
9use ark_serialize::{CanonicalDeserialize, CanonicalSerialize};
10use ark_snark::SNARK;
11use ark_std::rand::{CryptoRng, RngCore};
12use ark_std::{One, Zero};
13use core::marker::PhantomData;
14
15use super::decider_circuits::{DeciderCircuit1, DeciderCircuit2};
16use super::decider_eth_circuit::DeciderNovaGadget;
17use super::Nova;
18use crate::commitment::CommitmentScheme;
19use crate::folding::circuits::decider::DeciderEnabledNIFS;
20use crate::folding::circuits::{
21 cyclefold::{CycleFoldCommittedInstance, CycleFoldCommittedInstanceVar},
22 CF2,
23};
24use crate::folding::traits::{CommittedInstanceOps, Inputize, WitnessOps};
25use crate::frontend::FCircuit;
26use crate::Error;
27use crate::{Decider as DeciderTrait, FoldingScheme};
28
29#[derive(Debug, Clone, Eq, PartialEq)]
30pub struct Proof<C1, C2, CS1, CS2, S1, S2>
31where
32 C1: CurveGroup,
33 C2: CurveGroup,
34 CS1: CommitmentScheme<C1>,
35 CS2: CommitmentScheme<C2>,
36 S1: SNARK<C1::ScalarField>,
37 S2: SNARK<C2::ScalarField>,
38{
39 c1_snark_proof: S1::Proof,
40 c2_snark_proof: S2::Proof,
41 cs1_proofs: [CS1::Proof; 2],
42 cs2_proofs: [CS2::Proof; 2],
43 // cmT and r are values for the last fold, U_{i+1}=NIFS.V(r, U_i, u_i, cmT), and they are
44 // checked in-circuit
45 cmT: C1,
46 r: C1::ScalarField,
47 // cyclefold committed instance
48 cf_U_final: CycleFoldCommittedInstance<C2>,
49 // the CS challenges are provided by the prover, but in-circuit they are checked to match the
50 // in-circuit computed computed ones.
51 cs1_challenges: [C1::ScalarField; 2],
52 cs2_challenges: [C2::ScalarField; 2],
53}
54
55#[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)]
56pub struct ProverParam<CS1_ProvingKey, S1_ProvingKey, CS2_ProvingKey, S2_ProvingKey>
57where
58 CS1_ProvingKey: Clone + CanonicalSerialize + CanonicalDeserialize,
59 S1_ProvingKey: Clone + CanonicalSerialize + CanonicalDeserialize,
60 CS2_ProvingKey: Clone + CanonicalSerialize + CanonicalDeserialize,
61 S2_ProvingKey: Clone + CanonicalSerialize + CanonicalDeserialize,
62{
63 pub c1_snark_pp: S1_ProvingKey,
64 pub c1_cs_pp: CS1_ProvingKey,
65 pub c2_snark_pp: S2_ProvingKey,
66 pub c2_cs_pp: CS2_ProvingKey,
67}
68
69#[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)]
70pub struct VerifierParam<C1, CS1_VerifyingKey, S1_VerifyingKey, CS2_VerifyingKey, S2_VerifyingKey>
71where
72 C1: CurveGroup,
73 CS1_VerifyingKey: Clone + CanonicalSerialize + CanonicalDeserialize,
74 S1_VerifyingKey: Clone + CanonicalSerialize + CanonicalDeserialize,
75 CS2_VerifyingKey: Clone + CanonicalSerialize + CanonicalDeserialize,
76 S2_VerifyingKey: Clone + CanonicalSerialize + CanonicalDeserialize,
77{
78 pub pp_hash: C1::ScalarField,
79 pub c1_snark_vp: S1_VerifyingKey,
80 pub c1_cs_vp: CS1_VerifyingKey,
81 pub c2_snark_vp: S2_VerifyingKey,
82 pub c2_cs_vp: CS2_VerifyingKey,
83}
84
85/// Onchain Decider, for ethereum use cases
86#[derive(Clone, Debug)]
87pub struct Decider<C1, GC1, C2, GC2, FC, CS1, CS2, S1, S2, FS> {
88 _c1: PhantomData<C1>,
89 _gc1: PhantomData<GC1>,
90 _c2: PhantomData<C2>,
91 _gc2: PhantomData<GC2>,
92 _fc: PhantomData<FC>,
93 _cs1: PhantomData<CS1>,
94 _cs2: PhantomData<CS2>,
95 _s1: PhantomData<S1>,
96 _s2: PhantomData<S2>,
97 _fs: PhantomData<FS>,
98}
99
100impl<C1, GC1, C2, GC2, FC, CS1, CS2, S1, S2, FS> DeciderTrait<C1, C2, FC, FS>
101 for Decider<C1, GC1, C2, GC2, FC, CS1, CS2, S1, S2, FS>
102where
103 C1: CurveGroup,
104 C2: CurveGroup,
105 GC1: CurveVar<C1, CF2<C1>> + ToConstraintFieldGadget<CF2<C1>>,
106 GC2: CurveVar<C2, CF2<C2>> + ToConstraintFieldGadget<CF2<C2>>,
107 FC: FCircuit<C1::ScalarField>,
108 CS1: CommitmentScheme<
109 C1,
110 ProverChallenge = C1::ScalarField,
111 Challenge = C1::ScalarField,
112 Proof = crate::commitment::kzg::Proof<C1>,
113 >,
114 CS2: CommitmentScheme<
115 C2,
116 ProverChallenge = C2::ScalarField,
117 Challenge = C2::ScalarField,
118 Proof = crate::commitment::kzg::Proof<C2>,
119 >,
120 S1: SNARK<C1::ScalarField>,
121 S2: SNARK<C2::ScalarField>,
122 FS: FoldingScheme<C1, C2, FC>,
123 <C1 as CurveGroup>::BaseField: PrimeField,
124 <C2 as CurveGroup>::BaseField: PrimeField,
125 <C1 as Group>::ScalarField: Absorb,
126 <C2 as Group>::ScalarField: Absorb,
127 C1: CurveGroup<BaseField = C2::ScalarField, ScalarField = C2::BaseField>,
128 for<'b> &'b GC1: GroupOpsBounds<'b, C1, GC1>,
129 for<'b> &'b GC2: GroupOpsBounds<'b, C2, GC2>,
130 // constrain FS into Nova, since this is a Decider specifically for Nova
131 Nova<C1, GC1, C2, GC2, FC, CS1, CS2, false>: From<FS>,
132 crate::folding::nova::ProverParams<C1, C2, CS1, CS2, false>:
133 From<<FS as FoldingScheme<C1, C2, FC>>::ProverParam>,
134 crate::folding::nova::VerifierParams<C1, C2, CS1, CS2, false>:
135 From<<FS as FoldingScheme<C1, C2, FC>>::VerifierParam>,
136{
137 type PreprocessorParam = (FS::ProverParam, FS::VerifierParam);
138 type ProverParam =
139 ProverParam<CS1::ProverParams, S1::ProvingKey, CS2::ProverParams, S2::ProvingKey>;
140 type Proof = Proof<C1, C2, CS1, CS2, S1, S2>;
141 type VerifierParam = VerifierParam<
142 C1,
143 CS1::VerifierParams,
144 S1::VerifyingKey,
145 CS2::VerifierParams,
146 S2::VerifyingKey,
147 >;
148 type PublicInput = Vec<C1::ScalarField>;
149 type CommittedInstance = Vec<C1>;
150
151 fn preprocess(
152 mut rng: impl RngCore + CryptoRng,
153 prep_param: Self::PreprocessorParam,
154 fs: FS,
155 ) -> Result<(Self::ProverParam, Self::VerifierParam), Error> {
156 let circuit1 = DeciderCircuit1::<C1, C2, GC2>::try_from(Nova::from(fs.clone()))?;
157 let circuit2 = DeciderCircuit2::<C2>::try_from(Nova::from(fs))?;
158
159 // get the Groth16 specific setup for the circuits
160 let (c1_g16_pk, c1_g16_vk) = S1::circuit_specific_setup(circuit1, &mut rng)
161 .map_err(|e| Error::SNARKSetupFail(e.to_string()))?;
162 let (c2_g16_pk, c2_g16_vk) = S2::circuit_specific_setup(circuit2, &mut rng)
163 .map_err(|e| Error::SNARKSetupFail(e.to_string()))?;
164
165 // get the FoldingScheme prover & verifier params from Nova
166 #[allow(clippy::type_complexity)]
167 let nova_pp: <Nova<C1, GC1, C2, GC2, FC, CS1, CS2, false> as FoldingScheme<
168 C1,
169 C2,
170 FC,
171 >>::ProverParam = prep_param.0.clone().into();
172 #[allow(clippy::type_complexity)]
173 let nova_vp: <Nova<C1, GC1, C2, GC2, FC, CS1, CS2, false> as FoldingScheme<
174 C1,
175 C2,
176 FC,
177 >>::VerifierParam = prep_param.1.clone().into();
178
179 let pp_hash = nova_vp.pp_hash()?;
180 let pp = Self::ProverParam {
181 c1_snark_pp: c1_g16_pk,
182 c1_cs_pp: nova_pp.cs_pp,
183 c2_snark_pp: c2_g16_pk,
184 c2_cs_pp: nova_pp.cf_cs_pp,
185 };
186 let vp = Self::VerifierParam {
187 pp_hash,
188 c1_snark_vp: c1_g16_vk,
189 c1_cs_vp: nova_vp.cs_vp,
190 c2_snark_vp: c2_g16_vk,
191 c2_cs_vp: nova_vp.cf_cs_vp,
192 };
193 Ok((pp, vp))
194 }
195
196 fn prove(
197 mut rng: impl RngCore + CryptoRng,
198 pp: Self::ProverParam,
199 fs: FS,
200 ) -> Result<Self::Proof, Error> {
201 let circuit1 = DeciderCircuit1::<C1, C2, GC2>::try_from(Nova::from(fs.clone()))?;
202 let circuit2 = DeciderCircuit2::<C2>::try_from(Nova::from(fs))?;
203
204 let cmT = circuit1.proof;
205 let r = circuit1.randomness;
206 let cf_U_final = circuit1.cf_U_i.clone();
207
208 let c1_kzg_challenges = circuit1.kzg_challenges.clone();
209 let c1_kzg_proofs = circuit1
210 .W_i1
211 .get_openings()
212 .iter()
213 .zip(&c1_kzg_challenges)
214 .map(|((v, _), &c)| {
215 CS1::prove_with_challenge(&pp.c1_cs_pp, c, v, &C1::ScalarField::zero(), None)
216 })
217 .collect::<Result<Vec<_>, _>>()?;
218 let c2_kzg_challenges = circuit2.kzg_challenges.clone();
219 let c2_kzg_proofs = circuit2
220 .cf_W_i
221 .get_openings()
222 .iter()
223 .zip(&c2_kzg_challenges)
224 .map(|((v, _), &c)| {
225 CS2::prove_with_challenge(&pp.c2_cs_pp, c, v, &C2::ScalarField::zero(), None)
226 })
227 .collect::<Result<Vec<_>, _>>()?;
228
229 let c1_snark_proof = S1::prove(&pp.c1_snark_pp, circuit1, &mut rng)
230 .map_err(|e| Error::Other(e.to_string()))?;
231 let c2_snark_proof = S2::prove(&pp.c2_snark_pp, circuit2, &mut rng)
232 .map_err(|e| Error::Other(e.to_string()))?;
233
234 Ok(Self::Proof {
235 c1_snark_proof,
236 c2_snark_proof,
237 cs1_proofs: c1_kzg_proofs
238 .try_into()
239 .map_err(|e: Vec<_>| Error::NotExpectedLength(e.len(), 2))?,
240 cs2_proofs: c2_kzg_proofs
241 .try_into()
242 .map_err(|e: Vec<_>| Error::NotExpectedLength(e.len(), 2))?,
243 cmT,
244 r,
245 cf_U_final,
246 cs1_challenges: c1_kzg_challenges
247 .try_into()
248 .map_err(|e: Vec<_>| Error::NotExpectedLength(e.len(), 2))?,
249 cs2_challenges: c2_kzg_challenges
250 .try_into()
251 .map_err(|e: Vec<_>| Error::NotExpectedLength(e.len(), 2))?,
252 })
253 }
254
255 fn verify(
256 vp: Self::VerifierParam,
257 i: C1::ScalarField,
258 z_0: Vec<C1::ScalarField>,
259 z_i: Vec<C1::ScalarField>,
260 // we don't use the instances at the verifier level, since we check them in-circuit
261 running_commitments: &Self::CommittedInstance,
262 incoming_commitments: &Self::CommittedInstance,
263 proof: &Self::Proof,
264 ) -> Result<bool, Error> {
265 if i <= C1::ScalarField::one() {
266 return Err(Error::NotEnoughSteps);
267 }
268
269 // 6.2. Fold the commitments
270 let U_final_commitments = DeciderNovaGadget::fold_group_elements_native(
271 running_commitments,
272 incoming_commitments,
273 Some(proof.cmT),
274 proof.r,
275 )?;
276 let cf_U = proof.cf_U_final.clone();
277
278 // snark proof 1
279 let c1_public_input = [
280 &[vp.pp_hash, i][..],
281 &z_0,
282 &z_i,
283 &U_final_commitments
284 .iter()
285 .flat_map(|c| c.inputize())
286 .collect::<Vec<_>>(),
287 &Inputize::<CF2<C2>, CycleFoldCommittedInstanceVar<C2, GC2>>::inputize(&cf_U),
288 &proof.cs1_challenges,
289 &proof.cs1_proofs.iter().map(|p| p.eval).collect::<Vec<_>>(),
290 &proof.cmT.inputize(),
291 &[proof.r],
292 ]
293 .concat();
294
295 let c1_snark_v = S1::verify(&vp.c1_snark_vp, &c1_public_input, &proof.c1_snark_proof)
296 .map_err(|e| Error::Other(e.to_string()))?;
297 if !c1_snark_v {
298 return Err(Error::SNARKVerificationFail);
299 }
300
301 // snark proof 2
302 // migrate pp_hash from C1::Fr to C1::Fq
303 let pp_hash_Fq =
304 C2::ScalarField::from_le_bytes_mod_order(&vp.pp_hash.into_bigint().to_bytes_le());
305 let c2_public_input: Vec<C2::ScalarField> = [
306 &[pp_hash_Fq][..],
307 &cf_U.inputize(),
308 &proof.cs2_challenges,
309 &proof.cs2_proofs.iter().map(|p| p.eval).collect::<Vec<_>>(),
310 ]
311 .concat();
312
313 let c2_snark_v = S2::verify(&vp.c2_snark_vp, &c2_public_input, &proof.c2_snark_proof)
314 .map_err(|e| Error::Other(e.to_string()))?;
315 if !c2_snark_v {
316 return Err(Error::SNARKVerificationFail);
317 }
318
319 // 7.3. check C1 commitments (main instance commitments)
320 for ((cm, &c), pi) in U_final_commitments
321 .iter()
322 .zip(&proof.cs1_challenges)
323 .zip(&proof.cs1_proofs)
324 {
325 CS1::verify_with_challenge(&vp.c1_cs_vp, c, cm, pi)?;
326 }
327
328 // 4.3. check C2 commitments (CycleFold instance commitments)
329 for ((cm, &c), pi) in cf_U
330 .get_commitments()
331 .iter()
332 .zip(&proof.cs2_challenges)
333 .zip(&proof.cs2_proofs)
334 {
335 CS2::verify_with_challenge(&vp.c2_cs_vp, c, cm, pi)?;
336 }
337
338 Ok(true)
339 }
340}
341
342#[cfg(test)]
343pub mod tests {
344 use ark_groth16::Groth16;
345
346 // Note: do not use the MNTx_298 curves in practice, these are just for tests. Use the MNTx_753
347 // curves instead.
348 use ark_mnt4_298::{
349 constraints::G1Var as GVar, Fr, G1Projective as Projective, MNT4_298 as MNT4,
350 };
351 use ark_mnt6_298::{
352 constraints::G1Var as GVar2, G1Projective as Projective2, MNT6_298 as MNT6,
353 };
354 use std::time::Instant;
355
356 use super::*;
357 use crate::commitment::kzg::KZG;
358 use crate::folding::nova::PreprocessorParam;
359 use crate::frontend::utils::CubicFCircuit;
360 use crate::transcript::poseidon::poseidon_canonical_config;
361
362 #[test]
363 fn test_decider() {
364 // use Nova as FoldingScheme
365 type N = Nova<
366 Projective,
367 GVar,
368 Projective2,
369 GVar2,
370 CubicFCircuit<Fr>,
371 KZG<'static, MNT4>,
372 KZG<'static, MNT6>,
373 false,
374 >;
375 type D = Decider<
376 Projective,
377 GVar,
378 Projective2,
379 GVar2,
380 CubicFCircuit<Fr>,
381 KZG<'static, MNT4>,
382 KZG<'static, MNT6>,
383 Groth16<MNT4>,
384 Groth16<MNT6>,
385 N, // here we define the FoldingScheme to use
386 >;
387
388 let mut rng = ark_std::test_rng();
389 let poseidon_config = poseidon_canonical_config::<Fr>();
390
391 let F_circuit = CubicFCircuit::<Fr>::new(()).unwrap();
392 let z_0 = vec![Fr::from(3_u32)];
393
394 let start = Instant::now();
395 let prep_param = PreprocessorParam::new(poseidon_config, F_circuit);
396 let nova_params = N::preprocess(&mut rng, &prep_param).unwrap();
397 println!("Nova preprocess, {:?}", start.elapsed());
398
399 let start = Instant::now();
400 let mut nova = N::init(&nova_params, F_circuit, z_0.clone()).unwrap();
401 println!("Nova initialized, {:?}", start.elapsed());
402 let start = Instant::now();
403 nova.prove_step(&mut rng, vec![], None).unwrap();
404 println!("prove_step, {:?}", start.elapsed());
405 nova.prove_step(&mut rng, vec![], None).unwrap(); // do a 2nd step
406
407 let mut rng = rand::rngs::OsRng;
408
409 // prepare the Decider prover & verifier params
410 let start = Instant::now();
411 let (decider_pp, decider_vp) = D::preprocess(&mut rng, nova_params, nova.clone()).unwrap();
412 println!("Decider preprocess, {:?}", start.elapsed());
413
414 // decider proof generation
415 let start = Instant::now();
416 let proof = D::prove(rng, decider_pp, nova.clone()).unwrap();
417 println!("Decider prove, {:?}", start.elapsed());
418
419 // decider proof verification
420 let start = Instant::now();
421 let verified = D::verify(
422 decider_vp,
423 nova.i,
424 nova.z_0,
425 nova.z_i,
426 &nova.U_i.get_commitments(),
427 &nova.u_i.get_commitments(),
428 &proof,
429 )
430 .unwrap();
431 assert!(verified);
432 println!("Decider verify, {:?}", start.elapsed());
433 }
434}