sonobe/folding-schemes/src/folding/nova/circuits.rs
folding-schemes/src/folding/nova/circuits.rsRaw
1/// contains [Nova](https://eprint.iacr.org/2021/370.pdf) related circuits
2use ark_crypto_primitives::sponge::{
3 constraints::{AbsorbGadget, CryptographicSpongeVar},
4 poseidon::{constraints::PoseidonSpongeVar, PoseidonConfig},
5 Absorb, CryptographicSponge,
6};
7use ark_ec::{CurveGroup, Group};
8use ark_ff::PrimeField;
9use ark_r1cs_std::{
10 alloc::{AllocVar, AllocationMode},
11 boolean::Boolean,
12 eq::EqGadget,
13 fields::{fp::FpVar, FieldVar},
14 groups::GroupOpsBounds,
15 prelude::CurveVar,
16 uint8::UInt8,
17 R1CSVar, ToConstraintFieldGadget,
18};
19use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystemRef, Namespace, SynthesisError};
20use ark_std::{fmt::Debug, One, Zero};
21use core::{borrow::Borrow, marker::PhantomData};
22
23use super::{CommittedInstance, NovaCycleFoldConfig};
24use crate::folding::circuits::{
25 cyclefold::{
26 CycleFoldChallengeGadget, CycleFoldCommittedInstance, CycleFoldCommittedInstanceVar,
27 CycleFoldConfig, NIFSFullGadget,
28 },
29 nonnative::{affine::NonNativeAffineVar, uint::NonNativeUintVar},
30 CF1, CF2,
31};
32use crate::frontend::FCircuit;
33use crate::transcript::{AbsorbNonNativeGadget, Transcript, TranscriptVar};
34use crate::{
35 constants::NOVA_N_BITS_RO,
36 folding::traits::{CommittedInstanceVarOps, Dummy},
37};
38
39/// CommittedInstanceVar contains the u, x, cmE and cmW values which are folded on the main Nova
40/// constraints field (E1::Fr, where E1 is the main curve). The peculiarity is that cmE and cmW are
41/// represented non-natively over the constraint field.
42#[derive(Debug, Clone)]
43pub struct CommittedInstanceVar<C: CurveGroup> {
44 pub u: FpVar<C::ScalarField>,
45 pub x: Vec<FpVar<C::ScalarField>>,
46 pub cmE: NonNativeAffineVar<C>,
47 pub cmW: NonNativeAffineVar<C>,
48}
49
50impl<C> AllocVar<CommittedInstance<C>, CF1<C>> for CommittedInstanceVar<C>
51where
52 C: CurveGroup,
53{
54 fn new_variable<T: Borrow<CommittedInstance<C>>>(
55 cs: impl Into<Namespace<CF1<C>>>,
56 f: impl FnOnce() -> Result<T, SynthesisError>,
57 mode: AllocationMode,
58 ) -> Result<Self, SynthesisError> {
59 f().and_then(|val| {
60 let cs = cs.into();
61
62 let u = FpVar::<C::ScalarField>::new_variable(cs.clone(), || Ok(val.borrow().u), mode)?;
63 let x: Vec<FpVar<C::ScalarField>> =
64 Vec::new_variable(cs.clone(), || Ok(val.borrow().x.clone()), mode)?;
65
66 let cmE =
67 NonNativeAffineVar::<C>::new_variable(cs.clone(), || Ok(val.borrow().cmE), mode)?;
68 let cmW =
69 NonNativeAffineVar::<C>::new_variable(cs.clone(), || Ok(val.borrow().cmW), mode)?;
70
71 Ok(Self { u, x, cmE, cmW })
72 })
73 }
74}
75
76impl<C> AbsorbGadget<C::ScalarField> for CommittedInstanceVar<C>
77where
78 C: CurveGroup,
79 <C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField,
80{
81 fn to_sponge_bytes(&self) -> Result<Vec<UInt8<C::ScalarField>>, SynthesisError> {
82 FpVar::batch_to_sponge_bytes(&self.to_sponge_field_elements()?)
83 }
84
85 fn to_sponge_field_elements(&self) -> Result<Vec<FpVar<C::ScalarField>>, SynthesisError> {
86 Ok([
87 vec![self.u.clone()],
88 self.x.clone(),
89 self.cmE.to_constraint_field()?,
90 self.cmW.to_constraint_field()?,
91 ]
92 .concat())
93 }
94}
95
96impl<C: CurveGroup> CommittedInstanceVarOps<C> for CommittedInstanceVar<C> {
97 type PointVar = NonNativeAffineVar<C>;
98
99 fn get_commitments(&self) -> Vec<Self::PointVar> {
100 vec![self.cmW.clone(), self.cmE.clone()]
101 }
102
103 fn get_public_inputs(&self) -> &[FpVar<CF1<C>>] {
104 &self.x
105 }
106
107 fn enforce_incoming(&self) -> Result<(), SynthesisError> {
108 let zero = NonNativeUintVar::new_constant(ConstraintSystemRef::None, CF2::<C>::zero())?;
109 self.cmE.x.enforce_equal_unaligned(&zero)?;
110 self.cmE.y.enforce_equal_unaligned(&zero)?;
111 self.u.enforce_equal(&FpVar::one())
112 }
113
114 fn enforce_partial_equal(&self, other: &Self) -> Result<(), SynthesisError> {
115 self.u.enforce_equal(&other.u)?;
116 self.x.enforce_equal(&other.x)
117 }
118}
119
120/// Implements the circuit that does the checks of the Non-Interactive Folding Scheme Verifier
121/// described in section 4 of [Nova](https://eprint.iacr.org/2021/370.pdf), where the cmE & cmW checks are
122/// delegated to the NIFSCycleFoldGadget.
123pub struct NIFSGadget<C: CurveGroup> {
124 _c: PhantomData<C>,
125}
126
127impl<C: CurveGroup> NIFSGadget<C>
128where
129 C: CurveGroup,
130 <C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField,
131{
132 pub fn fold_committed_instance(
133 r: FpVar<CF1<C>>,
134 ci1: CommittedInstanceVar<C>, // U_i
135 ci2: CommittedInstanceVar<C>, // u_i
136 ) -> Result<CommittedInstanceVar<C>, SynthesisError> {
137 Ok(CommittedInstanceVar {
138 cmE: NonNativeAffineVar::new_constant(ConstraintSystemRef::None, C::zero())?,
139 cmW: NonNativeAffineVar::new_constant(ConstraintSystemRef::None, C::zero())?,
140 // ci3.u = ci1.u + r * ci2.u
141 u: ci1.u + &r * ci2.u,
142 // ci3.x = ci1.x + r * ci2.x
143 x: ci1
144 .x
145 .iter()
146 .zip(ci2.x)
147 .map(|(a, b)| a + &r * &b)
148 .collect::<Vec<FpVar<CF1<C>>>>(),
149 })
150 }
151
152 /// Implements the constraints for NIFS.V for u and x, since cm(E) and cm(W) are delegated to
153 /// the CycleFold circuit.
154 pub fn verify(
155 r: FpVar<CF1<C>>,
156 ci1: CommittedInstanceVar<C>, // U_i
157 ci2: CommittedInstanceVar<C>, // u_i
158 ci3: CommittedInstanceVar<C>, // U_{i+1}
159 ) -> Result<(), SynthesisError> {
160 let ci = Self::fold_committed_instance(r, ci1, ci2)?;
161
162 ci.u.enforce_equal(&ci3.u)?;
163 ci.x.enforce_equal(&ci3.x)?;
164
165 Ok(())
166 }
167}
168
169/// ChallengeGadget computes the RO challenge used for the Nova instances NIFS, it contains a
170/// rust-native and a in-circuit compatible versions.
171pub struct ChallengeGadget<C: CurveGroup> {
172 _c: PhantomData<C>,
173}
174impl<C: CurveGroup> ChallengeGadget<C>
175where
176 C: CurveGroup,
177 <C as CurveGroup>::BaseField: PrimeField,
178 <C as Group>::ScalarField: Absorb,
179{
180 pub fn get_challenge_native<T: Transcript<C::ScalarField>>(
181 transcript: &mut T,
182 pp_hash: C::ScalarField, // public params hash
183 U_i: CommittedInstance<C>,
184 u_i: CommittedInstance<C>,
185 cmT: C,
186 ) -> Vec<bool> {
187 transcript.absorb(&pp_hash);
188 transcript.absorb(&U_i);
189 transcript.absorb(&u_i);
190 transcript.absorb_nonnative(&cmT);
191 transcript.squeeze_bits(NOVA_N_BITS_RO)
192 }
193
194 // compatible with the native get_challenge_native
195 pub fn get_challenge_gadget<S: CryptographicSponge, T: TranscriptVar<CF1<C>, S>>(
196 transcript: &mut T,
197 pp_hash: FpVar<CF1<C>>, // public params hash
198 U_i_vec: Vec<FpVar<CF1<C>>>, // apready processed input, so we don't have to recompute these values
199 u_i: CommittedInstanceVar<C>,
200 cmT: NonNativeAffineVar<C>,
201 ) -> Result<Vec<Boolean<C::ScalarField>>, SynthesisError> {
202 transcript.absorb(&pp_hash)?;
203 transcript.absorb(&U_i_vec)?;
204 transcript.absorb(&u_i)?;
205 transcript.absorb_nonnative(&cmT)?;
206 transcript.squeeze_bits(NOVA_N_BITS_RO)
207 }
208}
209
210/// `AugmentedFCircuit` enhances the original step function `F`, so that it can
211/// be used in recursive arguments such as IVC.
212///
213/// The method for converting `F` to `AugmentedFCircuit` (`F'`) is defined in
214/// [Nova](https://eprint.iacr.org/2021/370.pdf), where `AugmentedFCircuit` not
215/// only invokes `F`, but also adds additional constraints for verifying the
216/// correct folding of primary instances (i.e., Nova's `CommittedInstance`s over
217/// `C1`).
218///
219/// Furthermore, to reduce circuit size over `C2`, we implement the constraints
220/// defined in [CycleFold](https://eprint.iacr.org/2023/1192.pdf). These extra
221/// constraints verify the correct folding of CycleFold instances.
222#[derive(Debug, Clone)]
223pub struct AugmentedFCircuit<
224 C1: CurveGroup,
225 C2: CurveGroup,
226 GC2: CurveVar<C2, CF2<C2>>,
227 FC: FCircuit<CF1<C1>>,
228> where
229 for<'a> &'a GC2: GroupOpsBounds<'a, C2, GC2>,
230{
231 pub(super) _gc2: PhantomData<GC2>,
232 pub(super) poseidon_config: PoseidonConfig<CF1<C1>>,
233 pub(super) pp_hash: Option<CF1<C1>>,
234 pub(super) i: Option<CF1<C1>>,
235 pub(super) i_usize: Option<usize>,
236 pub(super) z_0: Option<Vec<C1::ScalarField>>,
237 pub(super) z_i: Option<Vec<C1::ScalarField>>,
238 pub(super) external_inputs: Option<Vec<C1::ScalarField>>,
239 pub(super) u_i_cmW: Option<C1>,
240 pub(super) U_i: Option<CommittedInstance<C1>>,
241 pub(super) U_i1_cmE: Option<C1>,
242 pub(super) U_i1_cmW: Option<C1>,
243 pub(super) cmT: Option<C1>,
244 pub(super) F: FC, // F circuit
245 pub(super) x: Option<CF1<C1>>, // public input (u_{i+1}.x[0])
246
247 // cyclefold verifier on C1
248 // Here 'cf1, cf2' are for each of the CycleFold circuits, corresponding to the fold of cmW and
249 // cmE respectively
250 pub(super) cf1_u_i_cmW: Option<C2>, // input
251 pub(super) cf2_u_i_cmW: Option<C2>, // input
252 pub(super) cf_U_i: Option<CycleFoldCommittedInstance<C2>>, // input
253 pub(super) cf1_cmT: Option<C2>,
254 pub(super) cf2_cmT: Option<C2>,
255 pub(super) cf_x: Option<CF1<C1>>, // public input (u_{i+1}.x[1])
256}
257
258impl<C1: CurveGroup, C2: CurveGroup, GC2: CurveVar<C2, CF2<C2>>, FC: FCircuit<CF1<C1>>>
259 AugmentedFCircuit<C1, C2, GC2, FC>
260where
261 for<'a> &'a GC2: GroupOpsBounds<'a, C2, GC2>,
262{
263 pub fn empty(poseidon_config: &PoseidonConfig<CF1<C1>>, F_circuit: FC) -> Self {
264 Self {
265 _gc2: PhantomData,
266 poseidon_config: poseidon_config.clone(),
267 pp_hash: None,
268 i: None,
269 i_usize: None,
270 z_0: None,
271 z_i: None,
272 external_inputs: None,
273 u_i_cmW: None,
274 U_i: None,
275 U_i1_cmE: None,
276 U_i1_cmW: None,
277 cmT: None,
278 F: F_circuit,
279 x: None,
280 // cyclefold values
281 cf1_u_i_cmW: None,
282 cf2_u_i_cmW: None,
283 cf_U_i: None,
284 cf1_cmT: None,
285 cf2_cmT: None,
286 cf_x: None,
287 }
288 }
289}
290
291impl<C1, C2, GC2, FC> ConstraintSynthesizer<CF1<C1>> for AugmentedFCircuit<C1, C2, GC2, FC>
292where
293 C1: CurveGroup,
294 C2: CurveGroup,
295 GC2: CurveVar<C2, CF2<C2>> + ToConstraintFieldGadget<CF2<C2>>,
296 FC: FCircuit<CF1<C1>>,
297 <C1 as CurveGroup>::BaseField: PrimeField,
298 <C2 as CurveGroup>::BaseField: PrimeField,
299 <C1 as Group>::ScalarField: Absorb,
300 <C2 as Group>::ScalarField: Absorb,
301 C1: CurveGroup<BaseField = C2::ScalarField, ScalarField = C2::BaseField>,
302 for<'a> &'a GC2: GroupOpsBounds<'a, C2, GC2>,
303{
304 fn generate_constraints(self, cs: ConstraintSystemRef<CF1<C1>>) -> Result<(), SynthesisError> {
305 let pp_hash = FpVar::<CF1<C1>>::new_witness(cs.clone(), || {
306 Ok(self.pp_hash.unwrap_or_else(CF1::<C1>::zero))
307 })?;
308 let i = FpVar::<CF1<C1>>::new_witness(cs.clone(), || {
309 Ok(self.i.unwrap_or_else(CF1::<C1>::zero))
310 })?;
311 let z_0 = Vec::<FpVar<CF1<C1>>>::new_witness(cs.clone(), || {
312 Ok(self
313 .z_0
314 .unwrap_or(vec![CF1::<C1>::zero(); self.F.state_len()]))
315 })?;
316 let z_i = Vec::<FpVar<CF1<C1>>>::new_witness(cs.clone(), || {
317 Ok(self
318 .z_i
319 .unwrap_or(vec![CF1::<C1>::zero(); self.F.state_len()]))
320 })?;
321 let external_inputs = Vec::<FpVar<CF1<C1>>>::new_witness(cs.clone(), || {
322 Ok(self
323 .external_inputs
324 .unwrap_or(vec![CF1::<C1>::zero(); self.F.external_inputs_len()]))
325 })?;
326
327 let u_dummy = CommittedInstance::dummy(2);
328 let U_i = CommittedInstanceVar::<C1>::new_witness(cs.clone(), || {
329 Ok(self.U_i.unwrap_or(u_dummy.clone()))
330 })?;
331 let U_i1_cmE = NonNativeAffineVar::new_witness(cs.clone(), || {
332 Ok(self.U_i1_cmE.unwrap_or_else(C1::zero))
333 })?;
334 let U_i1_cmW = NonNativeAffineVar::new_witness(cs.clone(), || {
335 Ok(self.U_i1_cmW.unwrap_or_else(C1::zero))
336 })?;
337
338 let cmT =
339 NonNativeAffineVar::new_witness(cs.clone(), || Ok(self.cmT.unwrap_or_else(C1::zero)))?;
340
341 let cf_u_dummy = CycleFoldCommittedInstance::dummy(NovaCycleFoldConfig::<C1>::IO_LEN);
342 let cf_U_i = CycleFoldCommittedInstanceVar::<C2, GC2>::new_witness(cs.clone(), || {
343 Ok(self.cf_U_i.unwrap_or(cf_u_dummy.clone()))
344 })?;
345 let cf1_cmT = GC2::new_witness(cs.clone(), || Ok(self.cf1_cmT.unwrap_or_else(C2::zero)))?;
346 let cf2_cmT = GC2::new_witness(cs.clone(), || Ok(self.cf2_cmT.unwrap_or_else(C2::zero)))?;
347
348 // `sponge` is for digest computation.
349 let sponge = PoseidonSpongeVar::<C1::ScalarField>::new(cs.clone(), &self.poseidon_config);
350 // `transcript` is for challenge generation.
351 let mut transcript = sponge.clone();
352
353 let is_basecase = i.is_zero()?;
354
355 // Primary Part
356 // P.1. Compute u_i.x
357 // u_i.x[0] = H(i, z_0, z_i, U_i)
358 let (u_i_x, U_i_vec) = U_i.clone().hash(&sponge, &pp_hash, &i, &z_0, &z_i)?;
359 // u_i.x[1] = H(cf_U_i)
360 let (cf_u_i_x, cf_U_i_vec) = cf_U_i.clone().hash(&sponge, pp_hash.clone())?;
361
362 // P.2. Construct u_i
363 let u_i = CommittedInstanceVar {
364 // u_i.cmE = cm(0)
365 cmE: NonNativeAffineVar::new_constant(cs.clone(), C1::zero())?,
366 // u_i.u = 1
367 u: FpVar::one(),
368 // u_i.cmW is provided by the prover as witness
369 cmW: NonNativeAffineVar::new_witness(cs.clone(), || {
370 Ok(self.u_i_cmW.unwrap_or(C1::zero()))
371 })?,
372 // u_i.x is computed in step 1
373 x: vec![u_i_x, cf_u_i_x],
374 };
375
376 // P.3. nifs.verify, obtains U_{i+1} by folding u_i & U_i .
377
378 // compute r = H(u_i, U_i, cmT)
379 let r_bits = ChallengeGadget::<C1>::get_challenge_gadget(
380 &mut transcript,
381 pp_hash.clone(),
382 U_i_vec,
383 u_i.clone(),
384 cmT.clone(),
385 )?;
386 let r = Boolean::le_bits_to_fp_var(&r_bits)?;
387 // Also convert r_bits to a `NonNativeFieldVar`
388 let r_nonnat = {
389 let mut bits = r_bits;
390 bits.resize(C1::BaseField::MODULUS_BIT_SIZE as usize, Boolean::FALSE);
391 NonNativeUintVar::from(&bits)
392 };
393
394 // Notice that NIFSGadget::fold_committed_instance does not fold cmE & cmW.
395 // We set `U_i1.cmE` and `U_i1.cmW` to unconstrained witnesses `U_i1_cmE` and `U_i1_cmW`
396 // respectively.
397 // The correctness of them will be checked on the other curve.
398 let mut U_i1 = NIFSGadget::<C1>::fold_committed_instance(r, U_i.clone(), u_i.clone())?;
399 U_i1.cmE = U_i1_cmE;
400 U_i1.cmW = U_i1_cmW;
401
402 // P.4.a compute and check the first output of F'
403
404 // get z_{i+1} from the F circuit
405 let i_usize = self.i_usize.unwrap_or(0);
406 let z_i1 = self
407 .F
408 .generate_step_constraints(cs.clone(), i_usize, z_i, external_inputs)?;
409
410 // Base case: u_{i+1}.x[0] == H((i+1, z_0, z_{i+1}, U_{\bot})
411 // Non-base case: u_{i+1}.x[0] == H((i+1, z_0, z_{i+1}, U_{i+1})
412 let (u_i1_x, _) = U_i1.clone().hash(
413 &sponge,
414 &pp_hash,
415 &(i + FpVar::<CF1<C1>>::one()),
416 &z_0,
417 &z_i1,
418 )?;
419 let (u_i1_x_base, _) = CommittedInstanceVar::new_constant(cs.clone(), u_dummy)?.hash(
420 &sponge,
421 &pp_hash,
422 &FpVar::<CF1<C1>>::one(),
423 &z_0,
424 &z_i1,
425 )?;
426 let x = FpVar::new_input(cs.clone(), || Ok(self.x.unwrap_or(u_i1_x_base.value()?)))?;
427 x.enforce_equal(&is_basecase.select(&u_i1_x_base, &u_i1_x)?)?;
428
429 // CycleFold part
430 // C.1. Compute cf1_u_i.x and cf2_u_i.x
431 let cfW_x = vec![
432 r_nonnat.clone(),
433 U_i.cmW.x,
434 U_i.cmW.y,
435 u_i.cmW.x,
436 u_i.cmW.y,
437 U_i1.cmW.x,
438 U_i1.cmW.y,
439 ];
440 let cfE_x = vec![
441 r_nonnat, U_i.cmE.x, U_i.cmE.y, cmT.x, cmT.y, U_i1.cmE.x, U_i1.cmE.y,
442 ];
443
444 // ensure that cf1_u & cf2_u have as public inputs the cmW & cmE from main instances U_i,
445 // u_i, U_i+1 coordinates of the commitments
446 // C.2. Construct `cf1_u_i` and `cf2_u_i`
447 let cf1_u_i = CycleFoldCommittedInstanceVar {
448 // cf1_u_i.cmE = 0
449 cmE: GC2::zero(),
450 // cf1_u_i.u = 1
451 u: NonNativeUintVar::new_constant(cs.clone(), C1::BaseField::one())?,
452 // cf1_u_i.cmW is provided by the prover as witness
453 cmW: GC2::new_witness(cs.clone(), || Ok(self.cf1_u_i_cmW.unwrap_or(C2::zero())))?,
454 // cf1_u_i.x is computed in step 1
455 x: cfW_x,
456 };
457 let cf2_u_i = CycleFoldCommittedInstanceVar {
458 // cf2_u_i.cmE = 0
459 cmE: GC2::zero(),
460 // cf2_u_i.u = 1
461 u: NonNativeUintVar::new_constant(cs.clone(), C1::BaseField::one())?,
462 // cf2_u_i.cmW is provided by the prover as witness
463 cmW: GC2::new_witness(cs.clone(), || Ok(self.cf2_u_i_cmW.unwrap_or(C2::zero())))?,
464 // cf2_u_i.x is computed in step 1
465 x: cfE_x,
466 };
467
468 // C.3. nifs.verify, obtains cf1_U_{i+1} by folding cf1_u_i & cf_U_i, and then cf_U_{i+1}
469 // by folding cf2_u_i & cf1_U_{i+1}.
470
471 // compute cf1_r = H(cf1_u_i, cf_U_i, cf1_cmT)
472 // cf_r_bits is denoted by rho* in the paper.
473 let cf1_r_bits = CycleFoldChallengeGadget::<C2, GC2>::get_challenge_gadget(
474 &mut transcript,
475 pp_hash.clone(),
476 cf_U_i_vec,
477 cf1_u_i.clone(),
478 cf1_cmT.clone(),
479 )?;
480 // Fold cf1_u_i & cf_U_i into cf1_U_{i+1}
481 let cf1_U_i1 = NIFSFullGadget::<C2, GC2>::fold_committed_instance(
482 cf1_r_bits, cf1_cmT, cf_U_i, cf1_u_i,
483 )?;
484
485 // same for cf2_r:
486 let cf2_r_bits = CycleFoldChallengeGadget::<C2, GC2>::get_challenge_gadget(
487 &mut transcript,
488 pp_hash.clone(),
489 cf1_U_i1.to_native_sponge_field_elements()?,
490 cf2_u_i.clone(),
491 cf2_cmT.clone(),
492 )?;
493 let cf_U_i1 = NIFSFullGadget::<C2, GC2>::fold_committed_instance(
494 cf2_r_bits, cf2_cmT, cf1_U_i1, // the output from NIFS.V(cf1_r, cf_U, cfE_u)
495 cf2_u_i,
496 )?;
497
498 // Back to Primary Part
499 // P.4.b compute and check the second output of F'
500 // Base case: u_{i+1}.x[1] == H(cf_U_{\bot})
501 // Non-base case: u_{i+1}.x[1] == H(cf_U_{i+1})
502 let (cf_u_i1_x, _) = cf_U_i1.clone().hash(&sponge, pp_hash.clone())?;
503 let (cf_u_i1_x_base, _) =
504 CycleFoldCommittedInstanceVar::new_constant(cs.clone(), cf_u_dummy)?
505 .hash(&sponge, pp_hash)?;
506 let cf_x = FpVar::new_input(cs.clone(), || {
507 Ok(self.cf_x.unwrap_or(cf_u_i1_x_base.value()?))
508 })?;
509 cf_x.enforce_equal(&is_basecase.select(&cf_u_i1_x_base, &cf_u_i1_x)?)?;
510
511 Ok(())
512 }
513}
514
515#[cfg(test)]
516pub mod tests {
517 use super::*;
518 use ark_bn254::{Fr, G1Projective as Projective};
519 use ark_crypto_primitives::sponge::poseidon::PoseidonSponge;
520 use ark_ff::BigInteger;
521 use ark_relations::r1cs::ConstraintSystem;
522 use ark_std::UniformRand;
523
524 use crate::commitment::pedersen::Pedersen;
525 use crate::folding::nova::nifs::tests::prepare_simple_fold_inputs;
526 use crate::folding::nova::nifs::NIFS;
527 use crate::folding::traits::CommittedInstanceOps;
528 use crate::transcript::poseidon::poseidon_canonical_config;
529
530 #[test]
531 fn test_committed_instance_var() {
532 let mut rng = ark_std::test_rng();
533
534 let ci = CommittedInstance::<Projective> {
535 cmE: Projective::rand(&mut rng),
536 u: Fr::rand(&mut rng),
537 cmW: Projective::rand(&mut rng),
538 x: vec![Fr::rand(&mut rng); 1],
539 };
540
541 let cs = ConstraintSystem::<Fr>::new_ref();
542 let ciVar =
543 CommittedInstanceVar::<Projective>::new_witness(cs.clone(), || Ok(ci.clone())).unwrap();
544 assert_eq!(ciVar.u.value().unwrap(), ci.u);
545 assert_eq!(ciVar.x.value().unwrap(), ci.x);
546 // the values cmE and cmW are checked in the CycleFold's circuit
547 // CommittedInstanceInCycleFoldVar in
548 // cyclefold::tests::test_committed_instance_cyclefold_var
549 }
550
551 #[test]
552 fn test_nifs_gadget() {
553 let (_, _, _, _, ci1, _, ci2, _, ci3, _, cmT, _, r_Fr) = prepare_simple_fold_inputs();
554
555 let ci3_verifier = NIFS::<Projective, Pedersen<Projective>>::verify(r_Fr, &ci1, &ci2, &cmT);
556 assert_eq!(ci3_verifier, ci3);
557
558 let cs = ConstraintSystem::<Fr>::new_ref();
559
560 let rVar = FpVar::<Fr>::new_witness(cs.clone(), || Ok(r_Fr)).unwrap();
561 let ci1Var =
562 CommittedInstanceVar::<Projective>::new_witness(cs.clone(), || Ok(ci1.clone()))
563 .unwrap();
564 let ci2Var =
565 CommittedInstanceVar::<Projective>::new_witness(cs.clone(), || Ok(ci2.clone()))
566 .unwrap();
567 let ci3Var =
568 CommittedInstanceVar::<Projective>::new_witness(cs.clone(), || Ok(ci3.clone()))
569 .unwrap();
570
571 NIFSGadget::<Projective>::verify(
572 rVar.clone(),
573 ci1Var.clone(),
574 ci2Var.clone(),
575 ci3Var.clone(),
576 )
577 .unwrap();
578 assert!(cs.is_satisfied().unwrap());
579 }
580
581 /// test that checks the native CommittedInstance.to_sponge_{bytes,field_elements}
582 /// vs the R1CS constraints version
583 #[test]
584 pub fn test_committed_instance_to_sponge_preimage() {
585 let mut rng = ark_std::test_rng();
586
587 let ci = CommittedInstance::<Projective> {
588 cmE: Projective::rand(&mut rng),
589 u: Fr::rand(&mut rng),
590 cmW: Projective::rand(&mut rng),
591 x: vec![Fr::rand(&mut rng); 1],
592 };
593
594 let bytes = ci.to_sponge_bytes_as_vec();
595 let field_elements = ci.to_sponge_field_elements_as_vec();
596
597 let cs = ConstraintSystem::<Fr>::new_ref();
598
599 let ciVar =
600 CommittedInstanceVar::<Projective>::new_witness(cs.clone(), || Ok(ci.clone())).unwrap();
601 let bytes_var = ciVar.to_sponge_bytes().unwrap();
602 let field_elements_var = ciVar.to_sponge_field_elements().unwrap();
603
604 assert!(cs.is_satisfied().unwrap());
605
606 // check that the natively computed and in-circuit computed hashes match
607 assert_eq!(bytes_var.value().unwrap(), bytes);
608 assert_eq!(field_elements_var.value().unwrap(), field_elements);
609 }
610
611 #[test]
612 fn test_committed_instance_hash() {
613 let mut rng = ark_std::test_rng();
614 let poseidon_config = poseidon_canonical_config::<Fr>();
615 let sponge = PoseidonSponge::<Fr>::new(&poseidon_config);
616 let pp_hash = Fr::from(42u32); // only for test
617
618 let i = Fr::from(3_u32);
619 let z_0 = vec![Fr::from(3_u32)];
620 let z_i = vec![Fr::from(3_u32)];
621 let ci = CommittedInstance::<Projective> {
622 cmE: Projective::rand(&mut rng),
623 u: Fr::rand(&mut rng),
624 cmW: Projective::rand(&mut rng),
625 x: vec![Fr::rand(&mut rng); 1],
626 };
627
628 // compute the CommittedInstance hash natively
629 let h = ci.hash(&sponge, pp_hash, i, &z_0, &z_i);
630
631 let cs = ConstraintSystem::<Fr>::new_ref();
632
633 let pp_hashVar = FpVar::<Fr>::new_witness(cs.clone(), || Ok(pp_hash)).unwrap();
634 let iVar = FpVar::<Fr>::new_witness(cs.clone(), || Ok(i)).unwrap();
635 let z_0Var = Vec::<FpVar<Fr>>::new_witness(cs.clone(), || Ok(z_0.clone())).unwrap();
636 let z_iVar = Vec::<FpVar<Fr>>::new_witness(cs.clone(), || Ok(z_i.clone())).unwrap();
637 let ciVar =
638 CommittedInstanceVar::<Projective>::new_witness(cs.clone(), || Ok(ci.clone())).unwrap();
639
640 let sponge = PoseidonSpongeVar::<Fr>::new(cs.clone(), &poseidon_config);
641
642 // compute the CommittedInstance hash in-circuit
643 let (hVar, _) = ciVar
644 .hash(&sponge, &pp_hashVar, &iVar, &z_0Var, &z_iVar)
645 .unwrap();
646 assert!(cs.is_satisfied().unwrap());
647
648 // check that the natively computed and in-circuit computed hashes match
649 assert_eq!(hVar.value().unwrap(), h);
650 }
651
652 // checks that the gadget and native implementations of the challenge computation match
653 #[test]
654 fn test_challenge_gadget() {
655 let mut rng = ark_std::test_rng();
656 let poseidon_config = poseidon_canonical_config::<Fr>();
657 let mut transcript = PoseidonSponge::<Fr>::new(&poseidon_config);
658
659 let u_i = CommittedInstance::<Projective> {
660 cmE: Projective::rand(&mut rng),
661 u: Fr::rand(&mut rng),
662 cmW: Projective::rand(&mut rng),
663 x: vec![Fr::rand(&mut rng); 1],
664 };
665 let U_i = CommittedInstance::<Projective> {
666 cmE: Projective::rand(&mut rng),
667 u: Fr::rand(&mut rng),
668 cmW: Projective::rand(&mut rng),
669 x: vec![Fr::rand(&mut rng); 1],
670 };
671 let cmT = Projective::rand(&mut rng);
672
673 let pp_hash = Fr::from(42u32); // only for testing
674
675 // compute the challenge natively
676 let r_bits = ChallengeGadget::<Projective>::get_challenge_native(
677 &mut transcript,
678 pp_hash,
679 U_i.clone(),
680 u_i.clone(),
681 cmT,
682 );
683 let r = Fr::from_bigint(BigInteger::from_bits_le(&r_bits)).unwrap();
684
685 let cs = ConstraintSystem::<Fr>::new_ref();
686 let pp_hashVar = FpVar::<Fr>::new_witness(cs.clone(), || Ok(pp_hash)).unwrap();
687 let u_iVar =
688 CommittedInstanceVar::<Projective>::new_witness(cs.clone(), || Ok(u_i.clone()))
689 .unwrap();
690 let U_iVar =
691 CommittedInstanceVar::<Projective>::new_witness(cs.clone(), || Ok(U_i.clone()))
692 .unwrap();
693 let cmTVar = NonNativeAffineVar::<Projective>::new_witness(cs.clone(), || Ok(cmT)).unwrap();
694 let mut transcriptVar = PoseidonSpongeVar::<Fr>::new(cs.clone(), &poseidon_config);
695
696 // compute the challenge in-circuit
697 let U_iVar_vec = [
698 vec![U_iVar.u.clone()],
699 U_iVar.x.clone(),
700 U_iVar.cmE.to_constraint_field().unwrap(),
701 U_iVar.cmW.to_constraint_field().unwrap(),
702 ]
703 .concat();
704 let r_bitsVar = ChallengeGadget::<Projective>::get_challenge_gadget(
705 &mut transcriptVar,
706 pp_hashVar,
707 U_iVar_vec,
708 u_iVar,
709 cmTVar,
710 )
711 .unwrap();
712 assert!(cs.is_satisfied().unwrap());
713
714 // check that the natively computed and in-circuit computed hashes match
715 let rVar = Boolean::le_bits_to_fp_var(&r_bitsVar).unwrap();
716 assert_eq!(rVar.value().unwrap(), r);
717 assert_eq!(r_bitsVar.value().unwrap(), r_bits);
718 }
719}