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}