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{
59 type Error = Error;
60
61 fn try_from(nova: Nova<C1, GC1, C2, GC2, FC, CS1, CS2, H>) -> Result<Self, Error> {
62 let mut transcript = PoseidonSponge::<C1::ScalarField>::new(&nova.poseidon_config);
63 // pp_hash is absorbed to transcript at the NIFS::prove call
64
65 // compute the U_{i+1}, W_{i+1}
66 let (W_i1, U_i1, cmT, r_bits) = NIFS::<C1, CS1, PoseidonSponge<C1::ScalarField>, H>::prove(
67 &nova.cs_pp,
68 &nova.r1cs.clone(),
69 &mut transcript,
70 nova.pp_hash,
71 &nova.W_i,
72 &nova.U_i,
73 &nova.w_i,
74 &nova.u_i,
75 )?;
76 let r_Fr = C1::ScalarField::from_bigint(BigInteger::from_bits_le(&r_bits))
77 .ok_or(Error::OutOfBounds)?;
78
79 // compute the KZG challenges used as inputs in the circuit
80 let kzg_challenges = KZGChallengesGadget::get_challenges_native(&mut transcript, &U_i1);
81
82 // get KZG evals
83 let kzg_evaluations = W_i1
84 .get_openings()
85 .iter()
86 .zip(&kzg_challenges)
87 .map(|((v, _), &c)| EvalGadget::evaluate_native(v, c))
88 .collect::<Result<Vec<_>, _>>()?;
89
90 Ok(Self {
91 _gc2: PhantomData,
92 _avar: PhantomData,
93 arith: nova.r1cs,
94 poseidon_config: nova.poseidon_config,
95 pp_hash: nova.pp_hash,
96 i: nova.i,
97 z_0: nova.z_0,
98 z_i: nova.z_i,
99 U_i: nova.U_i,
100 W_i: nova.W_i,
101 u_i: nova.u_i,
102 w_i: nova.w_i,
103 U_i1,
104 W_i1,
105 proof: cmT,
106 randomness: r_Fr,
107 cf_U_i: nova.cf_U_i,
108 kzg_challenges,
109 kzg_evaluations,
110 })
111 }
112}
113
114/// Circuit that implements part of the in-circuit checks needed for the offchain verification over
115/// the Curve1's BaseField (=Curve2's ScalarField).
116pub type DeciderCircuit2<C2> = GenericOffchainDeciderCircuit2<C2>;
117
118impl<
119 C1: CurveGroup,
120 GC1: CurveVar<C1, CF2<C1>> + ToConstraintFieldGadget<CF2<C1>>,
121 C2: CurveGroup,
122 GC2: CurveVar<C2, CF2<C2>> + ToConstraintFieldGadget<CF2<C2>>,
123 FC: FCircuit<C1::ScalarField>,
124 CS1: CommitmentScheme<C1, H>,
125 CS2: CommitmentScheme<C2, H>,
126 const H: bool,
127 > TryFrom<Nova<C1, GC1, C2, GC2, FC, CS1, CS2, H>> for DeciderCircuit2<C2>
128where
129 CF1<C2>: Absorb,
130{
131 type Error = Error;
132
133 fn try_from(nova: Nova<C1, GC1, C2, GC2, FC, CS1, CS2, H>) -> Result<Self, Error> {
134 // compute the Commitment Scheme challenges of the CycleFold instance commitments, used as
135 // inputs in the circuit
136 let poseidon_config = poseidon_canonical_config::<C2::ScalarField>();
137 let mut transcript = PoseidonSponge::<C2::ScalarField>::new(&poseidon_config);
138 let pp_hash_Fq =
139 C2::ScalarField::from_le_bytes_mod_order(&nova.pp_hash.into_bigint().to_bytes_le());
140 transcript.absorb(&pp_hash_Fq);
141
142 // compute the KZG challenges used as inputs in the circuit
143 let kzg_challenges =
144 KZGChallengesGadget::get_challenges_native(&mut transcript, &nova.cf_U_i);
145
146 // get KZG evals
147 let kzg_evaluations = nova
148 .cf_W_i
149 .get_openings()
150 .iter()
151 .zip(&kzg_challenges)
152 .map(|((v, _), &c)| EvalGadget::evaluate_native(v, c))
153 .collect::<Result<Vec<_>, _>>()?;
154
155 Ok(Self {
156 cf_arith: nova.cf_r1cs,
157 poseidon_config,
158 pp_hash: pp_hash_Fq,
159 cf_U_i: nova.cf_U_i,
160 cf_W_i: nova.cf_W_i,
161 kzg_challenges,
162 kzg_evaluations,
163 })
164 }
165}
166
167#[cfg(test)]
168pub mod tests {
169 use ark_pallas::{constraints::GVar, Fq, Fr, Projective};
170 use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystem};
171 use ark_vesta::{constraints::GVar as GVar2, Projective as Projective2};
172
173 use super::*;
174 use crate::commitment::pedersen::Pedersen;
175 use crate::folding::nova::PreprocessorParam;
176 use crate::frontend::utils::CubicFCircuit;
177 use crate::transcript::poseidon::poseidon_canonical_config;
178 use crate::FoldingScheme;
179
180 #[test]
181 fn test_decider_circuits() {
182 let mut rng = ark_std::test_rng();
183 let poseidon_config = poseidon_canonical_config::<Fr>();
184
185 let F_circuit = CubicFCircuit::<Fr>::new(()).unwrap();
186 let z_0 = vec![Fr::from(3_u32)];
187
188 type N = Nova<
189 Projective,
190 GVar,
191 Projective2,
192 GVar2,
193 CubicFCircuit<Fr>,
194 Pedersen<Projective>,
195 Pedersen<Projective2>,
196 false,
197 >;
198
199 let prep_param = PreprocessorParam::<
200 Projective,
201 Projective2,
202 CubicFCircuit<Fr>,
203 Pedersen<Projective>,
204 Pedersen<Projective2>,
205 false,
206 >::new(poseidon_config, F_circuit);
207 let nova_params = N::preprocess(&mut rng, &prep_param).unwrap();
208
209 // generate a Nova instance and do a step of it
210 let mut nova = N::init(&nova_params, F_circuit, z_0.clone()).unwrap();
211 nova.prove_step(&mut rng, vec![], None).unwrap();
212 // verify the IVC
213 let ivc_proof = nova.ivc_proof();
214 N::verify(nova_params.1, ivc_proof).unwrap();
215
216 // load the DeciderCircuit 1 & 2 from the Nova instance
217 let decider_circuit1 =
218 DeciderCircuit1::<Projective, Projective2, GVar2>::try_from(nova.clone()).unwrap();
219 let decider_circuit2 = DeciderCircuit2::<Projective2>::try_from(nova).unwrap();
220
221 // generate the constraints of both circuits and check that are satisfied by the inputs
222 let cs1 = ConstraintSystem::<Fr>::new_ref();
223 decider_circuit1.generate_constraints(cs1.clone()).unwrap();
224 assert!(cs1.is_satisfied().unwrap());
225 let cs2 = ConstraintSystem::<Fq>::new_ref();
226 decider_circuit2.generate_constraints(cs2.clone()).unwrap();
227 assert!(cs2.is_satisfied().unwrap());
228 }
229}