Skip to main content

nova_snark/nova/
mod.rs

1//! This module implements Nova's IVC scheme including its folding scheme.
2
3use crate::{
4  constants::NUM_HASH_BITS,
5  digest::{DigestComputer, SimpleDigestible},
6  errors::NovaError,
7  frontend::{
8    r1cs::{NovaShape, NovaWitness},
9    shape_cs::ShapeCS,
10    solver::SatisfyingAssignment,
11    ConstraintSystem, SynthesisError,
12  },
13  gadgets::utils::{base_as_scalar, scalar_as_base},
14  r1cs::{
15    CommitmentKeyHint, R1CSInstance, R1CSShape, R1CSWitness, RelaxedR1CSInstance,
16    RelaxedR1CSWitness,
17  },
18  traits::{
19    circuit::{StepCircuit, TrivialCircuit},
20    commitment::CommitmentEngineTrait,
21    snark::RelaxedR1CSSNARKTrait,
22    AbsorbInROTrait, Engine, ROConstants, ROConstantsCircuit, ROTrait,
23  },
24  CommitmentKey, DerandKey,
25};
26use core::marker::PhantomData;
27use ff::Field;
28use once_cell::sync::OnceCell;
29use rand_core::OsRng;
30use serde::{Deserialize, Serialize};
31
32mod circuit;
33pub mod nifs;
34
35use circuit::{NovaAugmentedCircuit, NovaAugmentedCircuitInputs};
36use nifs::{NIFSRelaxed, NIFS};
37
38/// A type that holds public parameters of Nova
39#[derive(Serialize, Deserialize)]
40#[serde(bound = "")]
41pub struct PublicParams<E1, E2, C>
42where
43  E1: Engine<Base = <E2 as Engine>::Scalar>,
44  E2: Engine<Base = <E1 as Engine>::Scalar>,
45  C: StepCircuit<E1::Scalar>,
46{
47  F_arity: usize,
48
49  ro_consts_primary: ROConstants<E1>,
50  ro_consts_circuit_primary: ROConstantsCircuit<E2>,
51
52  ro_consts_secondary: ROConstants<E2>,
53  ro_consts_circuit_secondary: ROConstantsCircuit<E1>,
54
55  ck_primary: CommitmentKey<E1>,
56  r1cs_shape_primary: R1CSShape<E1>,
57
58  ck_secondary: CommitmentKey<E2>,
59  r1cs_shape_secondary: R1CSShape<E2>,
60
61  #[serde(skip, default = "OnceCell::new")]
62  digest: OnceCell<E1::Scalar>,
63  _p: PhantomData<C>,
64}
65
66impl<E1, E2, C> SimpleDigestible for PublicParams<E1, E2, C>
67where
68  E1: Engine<Base = <E2 as Engine>::Scalar>,
69  E2: Engine<Base = <E1 as Engine>::Scalar>,
70  C: StepCircuit<E1::Scalar>,
71{
72}
73
74impl<E1, E2, C> PublicParams<E1, E2, C>
75where
76  E1: Engine<Base = <E2 as Engine>::Scalar>,
77  E2: Engine<Base = <E1 as Engine>::Scalar>,
78  C: StepCircuit<E1::Scalar>,
79{
80  /// Creates a new `PublicParams` for a circuit `C`.
81  ///
82  /// # Note
83  ///
84  /// Public parameters set up a number of bases for the homomorphic commitment scheme of Nova.
85  ///
86  /// Some final compressing SNARKs, like variants of Spartan, use computation commitments that require
87  /// larger sizes for these parameters. These SNARKs provide a hint for these values by
88  /// implementing `RelaxedR1CSSNARKTrait::ck_floor()`, which can be passed to this function.
89  ///
90  /// If you're not using such a SNARK, pass `nova_snark::traits::snark::default_ck_hint()` instead.
91  ///
92  /// # Arguments
93  ///
94  /// * `c`: The primary circuit of type `C`.
95  /// * `ck_hint1`: A `CommitmentKeyHint` for `S1`, which is a function that provides a hint
96  ///   for the number of generators required in the commitment scheme for the primary circuit.
97  /// * `ck_hint2`: A `CommitmentKeyHint` for `S2`, similar to `ck_hint1`, but for the secondary circuit.
98  ///
99  /// # Example
100  ///
101  /// ```rust
102  /// # use nova_snark::spartan::ppsnark::RelaxedR1CSSNARK;
103  /// # use nova_snark::provider::ipa_pc::EvaluationEngine;
104  /// # use nova_snark::provider::{PallasEngine, VestaEngine};
105  /// # use nova_snark::traits::{circuit::TrivialCircuit, Engine, snark::RelaxedR1CSSNARKTrait};
106  /// # use nova_snark::nova::PublicParams;
107  /// # use nova_snark::errors::NovaError;
108  /// # fn main() -> Result<(), NovaError> {
109  ///
110  /// type E1 = PallasEngine;
111  /// type E2 = VestaEngine;
112  /// type EE<E> = EvaluationEngine<E>;
113  /// type SPrime<E> = RelaxedR1CSSNARK<E, EE<E>>;
114  ///
115  /// let circuit = TrivialCircuit::<<E1 as Engine>::Scalar>::default();
116  /// // Only relevant for a SNARK using computational commitments, pass &(|_| 0)
117  /// // or &*nova_snark::traits::snark::default_ck_hint() otherwise.
118  /// let ck_hint1 = &*SPrime::<E1>::ck_floor();
119  /// let ck_hint2 = &*SPrime::<E2>::ck_floor();
120  ///
121  /// let pp = PublicParams::setup(&circuit, ck_hint1, ck_hint2)?;
122  /// Ok(())
123  /// # }
124  /// ```
125  pub fn setup(
126    c: &C,
127    ck_hint1: &CommitmentKeyHint<E1>,
128    ck_hint2: &CommitmentKeyHint<E2>,
129  ) -> Result<Self, NovaError> {
130    let ro_consts_primary: ROConstants<E1> = ROConstants::<E1>::default();
131    let ro_consts_secondary: ROConstants<E2> = ROConstants::<E2>::default();
132
133    let F_arity = c.arity();
134
135    // ro_consts_circuit_primary are parameterized by E2 because the type alias uses E2::Base = E1::Scalar
136    let ro_consts_circuit_primary: ROConstantsCircuit<E2> = ROConstantsCircuit::<E2>::default();
137    let ro_consts_circuit_secondary: ROConstantsCircuit<E1> = ROConstantsCircuit::<E1>::default();
138
139    // Initialize ck for the primary
140    let circuit_primary: NovaAugmentedCircuit<'_, E2, C> =
141      NovaAugmentedCircuit::new(true, None, c, ro_consts_circuit_primary.clone());
142    let mut cs: ShapeCS<E1> = ShapeCS::new();
143    let _ = circuit_primary.synthesize(&mut cs)?;
144    let r1cs_shape_primary = cs.r1cs_shape()?;
145    let ck_primary = R1CSShape::commitment_key(&[&r1cs_shape_primary], &[ck_hint1])?;
146
147    // Initialize ck for the secondary
148    let tc = TrivialCircuit::<E2::Scalar>::default();
149    let circuit_secondary: NovaAugmentedCircuit<'_, E1, _> =
150      NovaAugmentedCircuit::new(false, None, &tc, ro_consts_circuit_secondary.clone());
151    let mut cs: ShapeCS<E2> = ShapeCS::new();
152    let _ = circuit_secondary.synthesize(&mut cs)?;
153    let r1cs_shape_secondary = cs.r1cs_shape()?;
154    let ck_secondary = R1CSShape::commitment_key(&[&r1cs_shape_secondary], &[ck_hint2])?;
155
156    // Nova's augmented circuits always have exactly 2 IO elements:
157    // the hashes of the two running instances (one on primary curve, one on secondary curve)
158    if r1cs_shape_primary.num_io != 2 || r1cs_shape_secondary.num_io != 2 {
159      return Err(NovaError::InvalidStepCircuitIO);
160    }
161
162    let pp = PublicParams {
163      F_arity,
164
165      ro_consts_primary,
166      ro_consts_circuit_primary,
167
168      ro_consts_secondary,
169      ro_consts_circuit_secondary,
170
171      ck_primary,
172      r1cs_shape_primary,
173
174      ck_secondary,
175      r1cs_shape_secondary,
176
177      digest: OnceCell::new(),
178      _p: Default::default(),
179    };
180
181    // call pp.digest() so the digest is computed here rather than in RecursiveSNARK methods
182    let _ = pp.digest();
183
184    Ok(pp)
185  }
186
187  /// Creates a new `PublicParams` for a circuit `C` using commitment keys loaded from a ptau directory.
188  ///
189  /// This is designed for use with HyperKZG or Mercury on the primary curve (e.g., BN256).
190  /// The commitment key for the primary circuit is loaded from a Powers of Tau ceremony file,
191  /// while the secondary circuit (which uses a non-pairing-friendly curve like Grumpkin) uses
192  /// standard key generation.
193  ///
194  /// **Note:** This method requires `E1::GE` to implement `PairingGroup`. It is only available
195  /// for pairing-friendly primary curves (BN256, BLS12-381, etc.) and will not compile for
196  /// non-pairing curves.
197  ///
198  /// The function automatically selects the appropriate ptau file from the directory based on
199  /// the circuit size. Files should be named `ppot_pruned_{power}.ptau` (e.g., `ppot_pruned_20.ptau`).
200  ///
201  /// # Arguments
202  ///
203  /// * `c`: The primary circuit of type `C`.
204  /// * `ck_hint1`: A `CommitmentKeyHint` for the primary circuit.
205  /// * `ck_hint2`: A `CommitmentKeyHint` for the secondary circuit.
206  /// * `ptau_dir`: Path to the directory containing pruned ptau files.
207  ///
208  /// # Example
209  ///
210  /// ```ignore
211  /// use std::path::Path;
212  /// use nova_snark::nova::PublicParams;
213  ///
214  /// let pp = PublicParams::setup_with_ptau_dir(
215  ///     &circuit,
216  ///     &*S1::ck_floor(),
217  ///     &*S2::ck_floor(),
218  ///     Path::new("path/to/pruned_ptau_files"),
219  /// )?;
220  /// ```
221  #[cfg(feature = "io")]
222  pub fn setup_with_ptau_dir(
223    c: &C,
224    ck_hint1: &CommitmentKeyHint<E1>,
225    ck_hint2: &CommitmentKeyHint<E2>,
226    ptau_dir: &std::path::Path,
227  ) -> Result<Self, NovaError>
228  where
229    E1::GE: crate::provider::traits::PairingGroup,
230  {
231    let ro_consts_primary: ROConstants<E1> = ROConstants::<E1>::default();
232    let ro_consts_secondary: ROConstants<E2> = ROConstants::<E2>::default();
233
234    let F_arity = c.arity();
235
236    let ro_consts_circuit_primary: ROConstantsCircuit<E2> = ROConstantsCircuit::<E2>::default();
237    let ro_consts_circuit_secondary: ROConstantsCircuit<E1> = ROConstantsCircuit::<E1>::default();
238
239    // Initialize shape for the primary
240    let circuit_primary: NovaAugmentedCircuit<'_, E2, C> =
241      NovaAugmentedCircuit::new(true, None, c, ro_consts_circuit_primary.clone());
242    let mut cs: ShapeCS<E1> = ShapeCS::new();
243    let _ = circuit_primary.synthesize(&mut cs)?;
244    let r1cs_shape_primary = cs.r1cs_shape()?;
245
246    // Load ck for the primary from ptau directory (auto-selects appropriate file)
247    let ck_primary =
248      R1CSShape::commitment_key_from_ptau_dir(&[&r1cs_shape_primary], &[ck_hint1], ptau_dir)?;
249
250    // Initialize shape for the secondary
251    let tc = TrivialCircuit::<E2::Scalar>::default();
252    let circuit_secondary: NovaAugmentedCircuit<'_, E1, _> =
253      NovaAugmentedCircuit::new(false, None, &tc, ro_consts_circuit_secondary.clone());
254    let mut cs: ShapeCS<E2> = ShapeCS::new();
255    let _ = circuit_secondary.synthesize(&mut cs)?;
256    let r1cs_shape_secondary = cs.r1cs_shape()?;
257
258    // Generate ck for the secondary using standard method (non-pairing curve)
259    let ck_secondary = R1CSShape::commitment_key(&[&r1cs_shape_secondary], &[ck_hint2])?;
260
261    // Nova's augmented circuits always have exactly 2 IO elements:
262    // the hashes of the two running instances (one on primary curve, one on secondary curve)
263    if r1cs_shape_primary.num_io != 2 || r1cs_shape_secondary.num_io != 2 {
264      return Err(NovaError::InvalidStepCircuitIO);
265    }
266
267    let pp = PublicParams {
268      F_arity,
269
270      ro_consts_primary,
271      ro_consts_circuit_primary,
272
273      ro_consts_secondary,
274      ro_consts_circuit_secondary,
275
276      ck_primary,
277      r1cs_shape_primary,
278
279      ck_secondary,
280      r1cs_shape_secondary,
281
282      digest: OnceCell::new(),
283      _p: Default::default(),
284    };
285
286    // call pp.digest() so the digest is computed here rather than in RecursiveSNARK methods
287    let _ = pp.digest();
288
289    Ok(pp)
290  }
291
292  /// Retrieve the digest of the public parameters.
293  pub fn digest(&self) -> E1::Scalar {
294    self
295      .digest
296      .get_or_try_init(|| DigestComputer::new(self).digest())
297      .cloned()
298      .expect("Failure in retrieving digest")
299  }
300
301  /// Returns the number of constraints in the primary and secondary circuits
302  pub const fn num_constraints(&self) -> (usize, usize) {
303    (
304      self.r1cs_shape_primary.num_cons,
305      self.r1cs_shape_secondary.num_cons,
306    )
307  }
308
309  /// Returns the number of variables in the primary and secondary circuits
310  pub const fn num_variables(&self) -> (usize, usize) {
311    (
312      self.r1cs_shape_primary.num_vars,
313      self.r1cs_shape_secondary.num_vars,
314    )
315  }
316}
317
318/// A SNARK that proves the correct execution of an incremental computation
319#[derive(Clone, Debug, Serialize, Deserialize)]
320#[serde(bound = "")]
321pub struct RecursiveSNARK<E1, E2, C>
322where
323  E1: Engine<Base = <E2 as Engine>::Scalar>,
324  E2: Engine<Base = <E1 as Engine>::Scalar>,
325  C: StepCircuit<E1::Scalar>,
326{
327  z0: Vec<E1::Scalar>,
328
329  r_W_primary: RelaxedR1CSWitness<E1>,
330  r_U_primary: RelaxedR1CSInstance<E1>,
331  ri_primary: E1::Scalar,
332
333  r_W_secondary: RelaxedR1CSWitness<E2>,
334  r_U_secondary: RelaxedR1CSInstance<E2>,
335  ri_secondary: E2::Scalar,
336
337  l_w_secondary: R1CSWitness<E2>,
338  l_u_secondary: R1CSInstance<E2>,
339
340  i: usize,
341
342  zi: Vec<E1::Scalar>,
343
344  _p: PhantomData<C>,
345}
346
347impl<E1, E2, C> RecursiveSNARK<E1, E2, C>
348where
349  E1: Engine<Base = <E2 as Engine>::Scalar>,
350  E2: Engine<Base = <E1 as Engine>::Scalar>,
351  C: StepCircuit<E1::Scalar>,
352{
353  /// Create new instance of recursive SNARK
354  pub fn new(pp: &PublicParams<E1, E2, C>, c: &C, z0: &[E1::Scalar]) -> Result<Self, NovaError> {
355    if z0.len() != pp.F_arity {
356      return Err(NovaError::InvalidInitialInputLength);
357    }
358
359    let ri_primary = E1::Scalar::random(&mut OsRng);
360    let ri_secondary = E2::Scalar::random(&mut OsRng);
361
362    // base case for the primary
363    let mut cs_primary = SatisfyingAssignment::<E1>::new();
364    let inputs_primary: NovaAugmentedCircuitInputs<E2> = NovaAugmentedCircuitInputs::new(
365      scalar_as_base::<E1>(pp.digest()),
366      E1::Scalar::ZERO,
367      z0.to_vec(),
368      None,
369      None,
370      None,
371      ri_primary, // "r next"
372      None,
373      None,
374    );
375
376    let circuit_primary: NovaAugmentedCircuit<'_, E2, C> = NovaAugmentedCircuit::new(
377      true,
378      Some(inputs_primary),
379      c,
380      pp.ro_consts_circuit_primary.clone(),
381    );
382    let zi_primary = circuit_primary.synthesize(&mut cs_primary)?;
383    let (u_primary, w_primary) =
384      cs_primary.r1cs_instance_and_witness(&pp.r1cs_shape_primary, &pp.ck_primary)?;
385
386    // base case for the secondary
387    let mut cs_secondary = SatisfyingAssignment::<E2>::new();
388    let inputs_secondary: NovaAugmentedCircuitInputs<E1> = NovaAugmentedCircuitInputs::new(
389      pp.digest(),
390      E2::Scalar::ZERO,
391      vec![E2::Scalar::ZERO],
392      None,
393      None,
394      None,
395      ri_secondary, // "r next"
396      Some(u_primary.clone()),
397      None,
398    );
399    let tc = TrivialCircuit::<E2::Scalar>::default();
400    let circuit_secondary: NovaAugmentedCircuit<'_, E1, _> = NovaAugmentedCircuit::new(
401      false,
402      Some(inputs_secondary),
403      &tc,
404      pp.ro_consts_circuit_secondary.clone(),
405    );
406    let _ = circuit_secondary.synthesize(&mut cs_secondary)?;
407    let (u_secondary, w_secondary) =
408      cs_secondary.r1cs_instance_and_witness(&pp.r1cs_shape_secondary, &pp.ck_secondary)?;
409
410    // IVC proof for the primary circuit
411    let l_w_primary = w_primary;
412    let l_u_primary = u_primary;
413    let r_W_primary = RelaxedR1CSWitness::from_r1cs_witness(&pp.r1cs_shape_primary, &l_w_primary);
414    let r_U_primary =
415      RelaxedR1CSInstance::from_r1cs_instance(&pp.ck_primary, &pp.r1cs_shape_primary, &l_u_primary);
416
417    // IVC proof for the secondary circuit
418    let l_w_secondary = w_secondary;
419    let l_u_secondary = u_secondary;
420    let r_W_secondary = RelaxedR1CSWitness::<E2>::default(&pp.r1cs_shape_secondary);
421    let r_U_secondary =
422      RelaxedR1CSInstance::<E2>::default(&pp.ck_secondary, &pp.r1cs_shape_secondary);
423
424    if zi_primary.len() != pp.F_arity {
425      return Err(NovaError::InvalidStepOutputLength);
426    }
427
428    let zi_primary = zi_primary
429      .iter()
430      .map(|v| v.get_value().ok_or(SynthesisError::AssignmentMissing))
431      .collect::<Result<Vec<<E1 as Engine>::Scalar>, _>>()?;
432
433    Ok(Self {
434      z0: z0.to_vec(),
435
436      r_W_primary,
437      r_U_primary,
438      ri_primary,
439
440      r_W_secondary,
441      r_U_secondary,
442      ri_secondary,
443
444      l_w_secondary,
445      l_u_secondary,
446
447      i: 0,
448
449      zi: zi_primary,
450
451      _p: Default::default(),
452    })
453  }
454
455  /// Updates the provided `RecursiveSNARK` by executing a step of the incremental computation
456  pub fn prove_step(&mut self, pp: &PublicParams<E1, E2, C>, c: &C) -> Result<(), NovaError> {
457    // first step was already done in the constructor
458    if self.i == 0 {
459      self.i = 1;
460      return Ok(());
461    }
462
463    // fold the secondary circuit's instance
464    let (nifs_secondary, (r_U_secondary, r_W_secondary)) = NIFS::prove(
465      &pp.ck_secondary,
466      &pp.ro_consts_secondary,
467      &scalar_as_base::<E1>(pp.digest()),
468      &pp.r1cs_shape_secondary,
469      &self.r_U_secondary,
470      &self.r_W_secondary,
471      &self.l_u_secondary,
472      &self.l_w_secondary,
473    )?;
474
475    let r_next_primary = E1::Scalar::random(&mut OsRng);
476
477    let mut cs_primary = SatisfyingAssignment::<E1>::new();
478    let inputs_primary: NovaAugmentedCircuitInputs<E2> = NovaAugmentedCircuitInputs::new(
479      scalar_as_base::<E1>(pp.digest()),
480      E1::Scalar::from(self.i as u64),
481      self.z0.to_vec(),
482      Some(self.zi.clone()),
483      Some(self.r_U_secondary.clone()),
484      Some(self.ri_primary),
485      r_next_primary,
486      Some(self.l_u_secondary.clone()),
487      Some(nifs_secondary.comm_T),
488    );
489
490    let circuit_primary: NovaAugmentedCircuit<'_, E2, C> = NovaAugmentedCircuit::new(
491      true,
492      Some(inputs_primary),
493      c,
494      pp.ro_consts_circuit_primary.clone(),
495    );
496    let zi_primary = circuit_primary.synthesize(&mut cs_primary)?;
497
498    let (l_u_primary, l_w_primary) =
499      cs_primary.r1cs_instance_and_witness(&pp.r1cs_shape_primary, &pp.ck_primary)?;
500
501    // fold the primary circuit's instance
502    let (nifs_primary, (r_U_primary, r_W_primary)) = NIFS::prove(
503      &pp.ck_primary,
504      &pp.ro_consts_primary,
505      &pp.digest(),
506      &pp.r1cs_shape_primary,
507      &self.r_U_primary,
508      &self.r_W_primary,
509      &l_u_primary,
510      &l_w_primary,
511    )?;
512
513    let r_next_secondary = E2::Scalar::random(&mut OsRng);
514
515    let mut cs_secondary = SatisfyingAssignment::<E2>::new();
516    let inputs_secondary: NovaAugmentedCircuitInputs<E1> = NovaAugmentedCircuitInputs::new(
517      pp.digest(),
518      E2::Scalar::from(self.i as u64),
519      vec![E2::Scalar::ZERO],
520      Some(vec![E2::Scalar::ZERO]),
521      Some(self.r_U_primary.clone()),
522      Some(self.ri_secondary),
523      r_next_secondary,
524      Some(l_u_primary),
525      Some(nifs_primary.comm_T),
526    );
527
528    let tc = TrivialCircuit::<E2::Scalar>::default();
529    let circuit_secondary: NovaAugmentedCircuit<'_, E1, _> = NovaAugmentedCircuit::new(
530      false,
531      Some(inputs_secondary),
532      &tc,
533      pp.ro_consts_circuit_secondary.clone(),
534    );
535    let _ = circuit_secondary.synthesize(&mut cs_secondary)?;
536
537    let (l_u_secondary, l_w_secondary) = cs_secondary
538      .r1cs_instance_and_witness(&pp.r1cs_shape_secondary, &pp.ck_secondary)
539      .map_err(|_e| NovaError::UnSat {
540        reason: "Unable to generate a satisfying witness on the secondary curve".to_string(),
541      })?;
542
543    // update the running instances and witnesses
544    self.zi = zi_primary
545      .iter()
546      .map(|v| v.get_value().ok_or(SynthesisError::AssignmentMissing))
547      .collect::<Result<Vec<<E1 as Engine>::Scalar>, _>>()?;
548
549    self.l_u_secondary = l_u_secondary;
550    self.l_w_secondary = l_w_secondary;
551
552    self.r_U_primary = r_U_primary;
553    self.r_W_primary = r_W_primary;
554
555    self.i += 1;
556
557    self.r_U_secondary = r_U_secondary;
558    self.r_W_secondary = r_W_secondary;
559
560    self.ri_primary = r_next_primary;
561    self.ri_secondary = r_next_secondary;
562
563    Ok(())
564  }
565
566  /// Verify the correctness of the `RecursiveSNARK`
567  pub fn verify(
568    &self,
569    pp: &PublicParams<E1, E2, C>,
570    num_steps: usize,
571    z0: &[E1::Scalar],
572  ) -> Result<Vec<E1::Scalar>, NovaError> {
573    // number of steps cannot be zero
574    let is_num_steps_zero = num_steps == 0;
575
576    // check if the provided proof has executed num_steps
577    let is_num_steps_not_match = self.i != num_steps;
578
579    // check if the initial inputs match
580    let is_inputs_not_match = self.z0 != z0;
581
582    // check if the (relaxed) R1CS instances have two public outputs
583    let is_instance_has_two_outputs = self.l_u_secondary.X.len() != 2
584      || self.r_U_primary.X.len() != 2
585      || self.r_U_secondary.X.len() != 2;
586
587    if is_num_steps_zero
588      || is_num_steps_not_match
589      || is_inputs_not_match
590      || is_instance_has_two_outputs
591    {
592      return Err(NovaError::ProofVerifyError {
593        reason: "Invalid number of steps or inputs".to_string(),
594      });
595    }
596
597    // check if the output hashes in R1CS instances point to the right running instances
598    let (hash_primary, hash_secondary) = {
599      let mut hasher = <E2 as Engine>::RO::new(pp.ro_consts_secondary.clone());
600      hasher.absorb(pp.digest());
601      hasher.absorb(E1::Scalar::from(num_steps as u64));
602      for e in z0 {
603        hasher.absorb(*e);
604      }
605      for e in &self.zi {
606        hasher.absorb(*e);
607      }
608      self.r_U_secondary.absorb_in_ro(&mut hasher);
609      hasher.absorb(self.ri_primary);
610
611      let mut hasher2 = <E1 as Engine>::RO::new(pp.ro_consts_primary.clone());
612      hasher2.absorb(scalar_as_base::<E1>(pp.digest()));
613      hasher2.absorb(E2::Scalar::from(num_steps as u64));
614      hasher2.absorb(E2::Scalar::ZERO);
615      hasher2.absorb(E2::Scalar::ZERO);
616      self.r_U_primary.absorb_in_ro(&mut hasher2);
617      hasher2.absorb(self.ri_secondary);
618
619      (
620        hasher.squeeze(NUM_HASH_BITS, false),
621        hasher2.squeeze(NUM_HASH_BITS, false),
622      )
623    };
624
625    if hash_primary != scalar_as_base::<E2>(self.l_u_secondary.X[0])
626      || hash_secondary != self.l_u_secondary.X[1]
627    {
628      return Err(NovaError::ProofVerifyError {
629        reason: "Invalid output hash in R1CS instances".to_string(),
630      });
631    }
632
633    // check the satisfiability of the provided instances
634    let (res_r_primary, (res_r_secondary, res_l_secondary)) = rayon::join(
635      || {
636        pp.r1cs_shape_primary
637          .is_sat_relaxed(&pp.ck_primary, &self.r_U_primary, &self.r_W_primary)
638      },
639      || {
640        rayon::join(
641          || {
642            pp.r1cs_shape_secondary.is_sat_relaxed(
643              &pp.ck_secondary,
644              &self.r_U_secondary,
645              &self.r_W_secondary,
646            )
647          },
648          || {
649            pp.r1cs_shape_secondary.is_sat(
650              &pp.ck_secondary,
651              &self.l_u_secondary,
652              &self.l_w_secondary,
653            )
654          },
655        )
656      },
657    );
658
659    // check the returned res objects
660    res_r_primary?;
661    res_r_secondary?;
662    res_l_secondary?;
663
664    Ok(self.zi.clone())
665  }
666
667  /// Get the outputs after the last step of computation.
668  pub fn outputs(&self) -> &[E1::Scalar] {
669    &self.zi
670  }
671
672  /// The number of steps which have been executed thus far.
673  pub fn num_steps(&self) -> usize {
674    self.i
675  }
676}
677
678/// A type that holds the prover key for `CompressedSNARK`
679#[derive(Clone, Debug, Serialize, Deserialize)]
680#[serde(bound = "")]
681pub struct ProverKey<E1, E2, C, S1, S2>
682where
683  E1: Engine<Base = <E2 as Engine>::Scalar>,
684  E2: Engine<Base = <E1 as Engine>::Scalar>,
685  C: StepCircuit<E1::Scalar>,
686  S1: RelaxedR1CSSNARKTrait<E1>,
687  S2: RelaxedR1CSSNARKTrait<E2>,
688{
689  pk_primary: S1::ProverKey,
690  pk_secondary: S2::ProverKey,
691  _p: PhantomData<C>,
692}
693
694/// A type that holds the verifier key for `CompressedSNARK`
695#[derive(Clone, Serialize, Deserialize)]
696#[serde(bound = "")]
697pub struct VerifierKey<E1, E2, C, S1, S2>
698where
699  E1: Engine<Base = <E2 as Engine>::Scalar>,
700  E2: Engine<Base = <E1 as Engine>::Scalar>,
701  C: StepCircuit<E1::Scalar>,
702  S1: RelaxedR1CSSNARKTrait<E1>,
703  S2: RelaxedR1CSSNARKTrait<E2>,
704{
705  F_arity: usize,
706  ro_consts_primary: ROConstants<E1>,
707  ro_consts_secondary: ROConstants<E2>,
708  pp_digest: E1::Scalar,
709  vk_primary: S1::VerifierKey,
710  vk_secondary: S2::VerifierKey,
711  dk_primary: DerandKey<E1>,
712  dk_secondary: DerandKey<E2>,
713  _p: PhantomData<C>,
714}
715
716/// A SNARK that proves the knowledge of a valid `RecursiveSNARK`
717#[derive(Clone, Serialize, Deserialize)]
718#[serde(bound = "")]
719pub struct CompressedSNARK<E1, E2, C, S1, S2>
720where
721  E1: Engine<Base = <E2 as Engine>::Scalar>,
722  E2: Engine<Base = <E1 as Engine>::Scalar>,
723  C: StepCircuit<E1::Scalar>,
724  S1: RelaxedR1CSSNARKTrait<E1>,
725  S2: RelaxedR1CSSNARKTrait<E2>,
726{
727  r_U_secondary: RelaxedR1CSInstance<E2>,
728  ri_secondary: E2::Scalar,
729  l_u_secondary: R1CSInstance<E2>,
730  nifs_Uf_secondary: NIFS<E2>,
731
732  l_ur_secondary: RelaxedR1CSInstance<E2>,
733  nifs_Un_secondary: NIFSRelaxed<E2>,
734
735  r_U_primary: RelaxedR1CSInstance<E1>,
736  ri_primary: E1::Scalar,
737  l_ur_primary: RelaxedR1CSInstance<E1>,
738  nifs_Un_primary: NIFSRelaxed<E1>,
739
740  wit_blind_r_Wn_primary: E1::Scalar,
741  err_blind_r_Wn_primary: E1::Scalar,
742  wit_blind_r_Wn_secondary: E2::Scalar,
743  err_blind_r_Wn_secondary: E2::Scalar,
744
745  snark_primary: S1,
746  snark_secondary: S2,
747
748  zn: Vec<E1::Scalar>,
749
750  _p: PhantomData<C>,
751}
752
753impl<E1, E2, C, S1, S2> CompressedSNARK<E1, E2, C, S1, S2>
754where
755  E1: Engine<Base = <E2 as Engine>::Scalar>,
756  E2: Engine<Base = <E1 as Engine>::Scalar>,
757  C: StepCircuit<E1::Scalar>,
758  S1: RelaxedR1CSSNARKTrait<E1>,
759  S2: RelaxedR1CSSNARKTrait<E2>,
760{
761  /// Creates prover and verifier keys for `CompressedSNARK`
762  pub fn setup(
763    pp: &PublicParams<E1, E2, C>,
764  ) -> Result<(ProverKey<E1, E2, C, S1, S2>, VerifierKey<E1, E2, C, S1, S2>), NovaError> {
765    let (pk_primary, vk_primary) = S1::setup(&pp.ck_primary, &pp.r1cs_shape_primary)?;
766    let (pk_secondary, vk_secondary) = S2::setup(&pp.ck_secondary, &pp.r1cs_shape_secondary)?;
767
768    let pk = ProverKey {
769      pk_primary,
770      pk_secondary,
771      _p: Default::default(),
772    };
773
774    let vk = VerifierKey {
775      F_arity: pp.F_arity,
776      ro_consts_primary: pp.ro_consts_primary.clone(),
777      ro_consts_secondary: pp.ro_consts_secondary.clone(),
778      pp_digest: pp.digest(),
779      vk_primary,
780      vk_secondary,
781      dk_primary: E1::CE::derand_key(&pp.ck_primary),
782      dk_secondary: E2::CE::derand_key(&pp.ck_secondary),
783      _p: Default::default(),
784    };
785
786    Ok((pk, vk))
787  }
788
789  /// Create a new `CompressedSNARK` (provides zero-knowledge)
790  pub fn prove(
791    pp: &PublicParams<E1, E2, C>,
792    pk: &ProverKey<E1, E2, C, S1, S2>,
793    recursive_snark: &RecursiveSNARK<E1, E2, C>,
794  ) -> Result<Self, NovaError> {
795    // prove three foldings
796
797    // fold secondary U/W with secondary u/w to get Uf/Wf
798    let (nifs_Uf_secondary, (r_Uf_secondary, r_Wf_secondary)) = NIFS::prove(
799      &pp.ck_secondary,
800      &pp.ro_consts_secondary,
801      &scalar_as_base::<E1>(pp.digest()),
802      &pp.r1cs_shape_secondary,
803      &recursive_snark.r_U_secondary,
804      &recursive_snark.r_W_secondary,
805      &recursive_snark.l_u_secondary,
806      &recursive_snark.l_w_secondary,
807    )?;
808
809    // fold Uf/Wf with random inst/wit to get U1/W1
810    let (l_ur_secondary, l_wr_secondary) = pp
811      .r1cs_shape_secondary
812      .sample_random_instance_witness(&pp.ck_secondary)?;
813
814    let (nifs_Un_secondary, (r_Un_secondary, r_Wn_secondary)) = NIFSRelaxed::prove(
815      &pp.ck_secondary,
816      &pp.ro_consts_secondary,
817      &scalar_as_base::<E1>(pp.digest()),
818      &pp.r1cs_shape_secondary,
819      &r_Uf_secondary,
820      &r_Wf_secondary,
821      &l_ur_secondary,
822      &l_wr_secondary,
823    )?;
824
825    // fold primary U/W with random inst/wit to get U2/W2
826    let (l_ur_primary, l_wr_primary) = pp
827      .r1cs_shape_primary
828      .sample_random_instance_witness(&pp.ck_primary)?;
829
830    let (nifs_Un_primary, (r_Un_primary, r_Wn_primary)) = NIFSRelaxed::prove(
831      &pp.ck_primary,
832      &pp.ro_consts_primary,
833      &pp.digest(),
834      &pp.r1cs_shape_primary,
835      &recursive_snark.r_U_primary,
836      &recursive_snark.r_W_primary,
837      &l_ur_primary,
838      &l_wr_primary,
839    )?;
840
841    // derandomize/unblind commitments
842    let (derandom_r_Wn_primary, wit_blind_r_Wn_primary, err_blind_r_Wn_primary) =
843      r_Wn_primary.derandomize();
844    let derandom_r_Un_primary = r_Un_primary.derandomize(
845      &E1::CE::derand_key(&pp.ck_primary),
846      &wit_blind_r_Wn_primary,
847      &err_blind_r_Wn_primary,
848    );
849
850    let (derandom_r_Wn_secondary, wit_blind_r_Wn_secondary, err_blind_r_Wn_secondary) =
851      r_Wn_secondary.derandomize();
852    let derandom_r_Un_secondary = r_Un_secondary.derandomize(
853      &E2::CE::derand_key(&pp.ck_secondary),
854      &wit_blind_r_Wn_secondary,
855      &err_blind_r_Wn_secondary,
856    );
857
858    // create SNARKs proving the knowledge of Wn primary/secondary
859    let (snark_primary, snark_secondary) = rayon::join(
860      || {
861        S1::prove(
862          &pp.ck_primary,
863          &pk.pk_primary,
864          &pp.r1cs_shape_primary,
865          &derandom_r_Un_primary,
866          &derandom_r_Wn_primary,
867        )
868      },
869      || {
870        S2::prove(
871          &pp.ck_secondary,
872          &pk.pk_secondary,
873          &pp.r1cs_shape_secondary,
874          &derandom_r_Un_secondary,
875          &derandom_r_Wn_secondary,
876        )
877      },
878    );
879
880    Ok(Self {
881      r_U_secondary: recursive_snark.r_U_secondary.clone(),
882      ri_secondary: recursive_snark.ri_secondary,
883      l_u_secondary: recursive_snark.l_u_secondary.clone(),
884      nifs_Uf_secondary: nifs_Uf_secondary.clone(),
885
886      l_ur_secondary: l_ur_secondary.clone(),
887      nifs_Un_secondary: nifs_Un_secondary.clone(),
888
889      r_U_primary: recursive_snark.r_U_primary.clone(),
890      ri_primary: recursive_snark.ri_primary,
891      l_ur_primary: l_ur_primary.clone(),
892      nifs_Un_primary: nifs_Un_primary.clone(),
893
894      wit_blind_r_Wn_primary,
895      err_blind_r_Wn_primary,
896      wit_blind_r_Wn_secondary,
897      err_blind_r_Wn_secondary,
898
899      snark_primary: snark_primary?,
900      snark_secondary: snark_secondary?,
901
902      zn: recursive_snark.zi.clone(),
903
904      _p: Default::default(),
905    })
906  }
907
908  /// Verify the correctness of the `CompressedSNARK` (provides zero-knowledge)
909  pub fn verify(
910    &self,
911    vk: &VerifierKey<E1, E2, C, S1, S2>,
912    num_steps: usize,
913    z0: &[E1::Scalar],
914  ) -> Result<Vec<E1::Scalar>, NovaError> {
915    // the number of steps cannot be zero
916    if num_steps == 0 {
917      return Err(NovaError::ProofVerifyError {
918        reason: "Number of steps cannot be zero".to_string(),
919      });
920    }
921
922    // check if the (relaxed) R1CS instances have two public outputs
923    if self.l_u_secondary.X.len() != 2
924      || self.r_U_primary.X.len() != 2
925      || self.r_U_secondary.X.len() != 2
926      || self.l_ur_primary.X.len() != 2
927      || self.l_ur_secondary.X.len() != 2
928    {
929      return Err(NovaError::ProofVerifyError {
930        reason: "Invalid number of outputs in R1CS instances".to_string(),
931      });
932    }
933
934    // check if the output hashes in R1CS instances point to the right running instances
935    let (hash_primary, hash_secondary) = {
936      let mut hasher = <E2 as Engine>::RO::new(vk.ro_consts_secondary.clone());
937      hasher.absorb(vk.pp_digest);
938      hasher.absorb(E1::Scalar::from(num_steps as u64));
939      for e in z0 {
940        hasher.absorb(*e);
941      }
942      for e in &self.zn {
943        hasher.absorb(*e);
944      }
945      self.r_U_secondary.absorb_in_ro(&mut hasher);
946      hasher.absorb(self.ri_primary);
947
948      let mut hasher2 = <E1 as Engine>::RO::new(vk.ro_consts_primary.clone());
949      hasher2.absorb(scalar_as_base::<E1>(vk.pp_digest));
950      hasher2.absorb(E2::Scalar::from(num_steps as u64));
951      hasher2.absorb(E2::Scalar::ZERO);
952      hasher2.absorb(E2::Scalar::ZERO);
953      self.r_U_primary.absorb_in_ro(&mut hasher2);
954      hasher2.absorb(self.ri_secondary);
955
956      (
957        hasher.squeeze(NUM_HASH_BITS, false),
958        hasher2.squeeze(NUM_HASH_BITS, false),
959      )
960    };
961
962    if hash_primary != base_as_scalar::<E1>(self.l_u_secondary.X[0])
963      || hash_secondary != self.l_u_secondary.X[1]
964    {
965      return Err(NovaError::ProofVerifyError {
966        reason: "Invalid output hash in R1CS instances".to_string(),
967      });
968    }
969
970    // fold secondary U/W with secondary u/w to get Uf/Wf
971    let r_Uf_secondary = self.nifs_Uf_secondary.verify(
972      &vk.ro_consts_secondary,
973      &scalar_as_base::<E1>(vk.pp_digest),
974      &self.r_U_secondary,
975      &self.l_u_secondary,
976    )?;
977
978    // fold Uf/Wf with random inst/wit to get U1/W1
979    let r_Un_secondary = self.nifs_Un_secondary.verify(
980      &vk.ro_consts_secondary,
981      &scalar_as_base::<E1>(vk.pp_digest),
982      &r_Uf_secondary,
983      &self.l_ur_secondary,
984    )?;
985
986    // fold primary U/W with random inst/wit to get U2/W2
987    let r_Un_primary = self.nifs_Un_primary.verify(
988      &vk.ro_consts_primary,
989      &vk.pp_digest,
990      &self.r_U_primary,
991      &self.l_ur_primary,
992    )?;
993
994    // derandomize/unblind commitments
995    let derandom_r_Un_primary = r_Un_primary.derandomize(
996      &vk.dk_primary,
997      &self.wit_blind_r_Wn_primary,
998      &self.err_blind_r_Wn_primary,
999    );
1000    let derandom_r_Un_secondary = r_Un_secondary.derandomize(
1001      &vk.dk_secondary,
1002      &self.wit_blind_r_Wn_secondary,
1003      &self.err_blind_r_Wn_secondary,
1004    );
1005
1006    // check the satisfiability of the folded instances using
1007    // SNARKs proving the knowledge of their satisfying witnesses
1008    let (res_primary, res_secondary) = rayon::join(
1009      || {
1010        self
1011          .snark_primary
1012          .verify(&vk.vk_primary, &derandom_r_Un_primary)
1013      },
1014      || {
1015        self
1016          .snark_secondary
1017          .verify(&vk.vk_secondary, &derandom_r_Un_secondary)
1018      },
1019    );
1020
1021    res_primary?;
1022    res_secondary?;
1023
1024    Ok(self.zn.clone())
1025  }
1026}
1027
1028#[cfg(test)]
1029mod tests {
1030  use super::*;
1031  use crate::{
1032    frontend::{num::AllocatedNum, ConstraintSystem, SynthesisError},
1033    provider::{
1034      pedersen::CommitmentKeyExtTrait, traits::DlogGroup, Bn256EngineIPA, Bn256EngineKZG,
1035      GrumpkinEngine, PallasEngine, Secp256k1Engine, Secq256k1Engine, VestaEngine,
1036    },
1037    traits::{circuit::TrivialCircuit, evaluation::EvaluationEngineTrait, snark::default_ck_hint},
1038  };
1039  use core::{fmt::Write, marker::PhantomData};
1040  use expect_test::{expect, Expect};
1041  use ff::PrimeField;
1042
1043  type EE<E> = crate::provider::ipa_pc::EvaluationEngine<E>;
1044  type EEPrime<E> = crate::provider::hyperkzg::EvaluationEngine<E>;
1045  type S<E, EE> = crate::spartan::snark::RelaxedR1CSSNARK<E, EE>;
1046  type SPrime<E, EE> = crate::spartan::ppsnark::RelaxedR1CSSNARK<E, EE>;
1047
1048  #[derive(Clone, Debug, Default)]
1049  struct CubicCircuit<F: PrimeField> {
1050    _p: PhantomData<F>,
1051  }
1052
1053  impl<F: PrimeField> StepCircuit<F> for CubicCircuit<F> {
1054    fn arity(&self) -> usize {
1055      1
1056    }
1057
1058    fn synthesize<CS: ConstraintSystem<F>>(
1059      &self,
1060      cs: &mut CS,
1061      z: &[AllocatedNum<F>],
1062    ) -> Result<Vec<AllocatedNum<F>>, SynthesisError> {
1063      // Consider a cubic equation: `x^3 + x + 5 = y`, where `x` and `y` are respectively the input and output.
1064      let x = &z[0];
1065      let x_sq = x.square(cs.namespace(|| "x_sq"))?;
1066      let x_cu = x_sq.mul(cs.namespace(|| "x_cu"), x)?;
1067      let y = AllocatedNum::alloc(cs.namespace(|| "y"), || {
1068        Ok(x_cu.get_value().unwrap() + x.get_value().unwrap() + F::from(5u64))
1069      })?;
1070
1071      cs.enforce(
1072        || "y = x^3 + x + 5",
1073        |lc| {
1074          lc + x_cu.get_variable()
1075            + x.get_variable()
1076            + CS::one()
1077            + CS::one()
1078            + CS::one()
1079            + CS::one()
1080            + CS::one()
1081        },
1082        |lc| lc + CS::one(),
1083        |lc| lc + y.get_variable(),
1084      );
1085
1086      Ok(vec![y])
1087    }
1088  }
1089
1090  impl<F: PrimeField> CubicCircuit<F> {
1091    fn output(&self, z: &[F]) -> Vec<F> {
1092      vec![z[0] * z[0] * z[0] + z[0] + F::from(5u64)]
1093    }
1094  }
1095
1096  fn test_pp_digest_with<E1, E2, C>(circuit: &C, expected: &Expect)
1097  where
1098    E1: Engine<Base = <E2 as Engine>::Scalar>,
1099    E2: Engine<Base = <E1 as Engine>::Scalar>,
1100    E1::GE: DlogGroup,
1101    E2::GE: DlogGroup,
1102    C: StepCircuit<E1::Scalar>,
1103    // required to use the IPA in the initialization of the commitment key hints below
1104    <E1::CE as CommitmentEngineTrait<E1>>::CommitmentKey: CommitmentKeyExtTrait<E1>,
1105    <E2::CE as CommitmentEngineTrait<E2>>::CommitmentKey: CommitmentKeyExtTrait<E2>,
1106  {
1107    // this tests public parameters with a size specifically intended for a spark-compressed SNARK
1108    let ck_hint1 = &*SPrime::<E1, EE<E1>>::ck_floor();
1109    let ck_hint2 = &*SPrime::<E2, EE<E2>>::ck_floor();
1110    let pp = PublicParams::<E1, E2, C>::setup(circuit, ck_hint1, ck_hint2).unwrap();
1111
1112    let digest_str = pp
1113      .digest()
1114      .to_repr()
1115      .as_ref()
1116      .iter()
1117      .fold(String::new(), |mut output, b| {
1118        let _ = write!(output, "{b:02x}");
1119        output
1120      });
1121    expected.assert_eq(&digest_str);
1122  }
1123
1124  #[test]
1125  fn test_pp_digest() {
1126    test_pp_digest_with::<PallasEngine, VestaEngine, _>(
1127      &TrivialCircuit::<_>::default(),
1128      &expect!["9e459d7e9450cce023a7adad2e4552170170b031e75b65333713ea161a294503"],
1129    );
1130
1131    test_pp_digest_with::<Bn256EngineIPA, GrumpkinEngine, _>(
1132      &TrivialCircuit::<_>::default(),
1133      &expect!["aa8f0e6d8be0c4028ddf7a7a733cf8d1ceeff408d6ec9a8aefa815b2668b2402"],
1134    );
1135
1136    test_pp_digest_with::<Secp256k1Engine, Secq256k1Engine, _>(
1137      &TrivialCircuit::<_>::default(),
1138      &expect!["719f988d15984fe6e838dd6267455265e1081beeb3b7313594a26d702afe5702"],
1139    );
1140  }
1141
1142  fn test_ivc_trivial_with<E1, E2>()
1143  where
1144    E1: Engine<Base = <E2 as Engine>::Scalar>,
1145    E2: Engine<Base = <E1 as Engine>::Scalar>,
1146  {
1147    let test_circuit = TrivialCircuit::<<E1 as Engine>::Scalar>::default();
1148
1149    // produce public parameters
1150    let pp = PublicParams::<E1, E2, TrivialCircuit<<E1 as Engine>::Scalar>>::setup(
1151      &test_circuit,
1152      &*default_ck_hint(),
1153      &*default_ck_hint(),
1154    )
1155    .unwrap();
1156
1157    let num_steps = 1;
1158
1159    // produce a recursive SNARK
1160    let mut recursive_snark =
1161      RecursiveSNARK::new(&pp, &test_circuit, &[<E1 as Engine>::Scalar::ZERO]).unwrap();
1162
1163    let res = recursive_snark.prove_step(&pp, &test_circuit);
1164
1165    assert!(res.is_ok());
1166
1167    // verify the recursive SNARK
1168    let res = recursive_snark.verify(&pp, num_steps, &[<E1 as Engine>::Scalar::ZERO]);
1169    assert!(res.is_ok());
1170  }
1171
1172  #[test]
1173  fn test_ivc_trivial() {
1174    test_ivc_trivial_with::<PallasEngine, VestaEngine>();
1175    test_ivc_trivial_with::<Bn256EngineKZG, GrumpkinEngine>();
1176    test_ivc_trivial_with::<Secp256k1Engine, Secq256k1Engine>();
1177  }
1178
1179  fn test_ivc_nontrivial_with<E1, E2>()
1180  where
1181    E1: Engine<Base = <E2 as Engine>::Scalar>,
1182    E2: Engine<Base = <E1 as Engine>::Scalar>,
1183  {
1184    let circuit = CubicCircuit::default();
1185
1186    // produce public parameters
1187    let pp = PublicParams::<E1, E2, CubicCircuit<E1::Scalar>>::setup(
1188      &circuit,
1189      &*default_ck_hint(),
1190      &*default_ck_hint(),
1191    )
1192    .unwrap();
1193
1194    let num_steps = 3;
1195
1196    // produce a recursive SNARK
1197    let mut recursive_snark = RecursiveSNARK::<E1, E2, CubicCircuit<<E1 as Engine>::Scalar>>::new(
1198      &pp,
1199      &circuit,
1200      &[<E1 as Engine>::Scalar::ZERO],
1201    )
1202    .unwrap();
1203
1204    for i in 0..num_steps {
1205      let res = recursive_snark.prove_step(&pp, &circuit);
1206      assert!(res.is_ok());
1207
1208      // verify the recursive snark at each step of recursion
1209      let res = recursive_snark.verify(&pp, i + 1, &[<E1 as Engine>::Scalar::ZERO]);
1210      assert!(res.is_ok());
1211    }
1212
1213    // verify the recursive SNARK
1214    let res = recursive_snark.verify(&pp, num_steps, &[<E1 as Engine>::Scalar::ZERO]);
1215    assert!(res.is_ok());
1216
1217    let zn = res.unwrap();
1218
1219    // sanity: check the claimed output with a direct computation of the same
1220    let mut zn_direct = vec![<E1 as Engine>::Scalar::ZERO];
1221    for _i in 0..num_steps {
1222      zn_direct = circuit.clone().output(&zn_direct);
1223    }
1224    assert_eq!(zn, zn_direct);
1225    assert_eq!(zn, vec![E1::Scalar::from(2460515u64)]);
1226  }
1227
1228  #[test]
1229  fn test_ivc_nontrivial() {
1230    test_ivc_nontrivial_with::<PallasEngine, VestaEngine>();
1231    test_ivc_nontrivial_with::<Bn256EngineKZG, GrumpkinEngine>();
1232    test_ivc_nontrivial_with::<Secp256k1Engine, Secq256k1Engine>();
1233  }
1234
1235  fn test_ivc_nontrivial_with_compression_with<E1, E2, EE1, EE2>()
1236  where
1237    E1: Engine<Base = <E2 as Engine>::Scalar>,
1238    E2: Engine<Base = <E1 as Engine>::Scalar>,
1239    EE1: EvaluationEngineTrait<E1>,
1240    EE2: EvaluationEngineTrait<E2>,
1241  {
1242    let circuit = CubicCircuit::default();
1243
1244    // produce public parameters
1245    let pp = PublicParams::<E1, E2, CubicCircuit<<E1 as Engine>::Scalar>>::setup(
1246      &circuit,
1247      &*default_ck_hint(),
1248      &*default_ck_hint(),
1249    )
1250    .unwrap();
1251
1252    let num_steps = 3;
1253
1254    // produce a recursive SNARK
1255    let mut recursive_snark = RecursiveSNARK::<E1, E2, CubicCircuit<<E1 as Engine>::Scalar>>::new(
1256      &pp,
1257      &circuit,
1258      &[<E1 as Engine>::Scalar::ZERO],
1259    )
1260    .unwrap();
1261
1262    for _i in 0..num_steps {
1263      let res = recursive_snark.prove_step(&pp, &circuit);
1264      assert!(res.is_ok());
1265    }
1266
1267    // verify the recursive SNARK
1268    let res = recursive_snark.verify(&pp, num_steps, &[<E1 as Engine>::Scalar::ZERO]);
1269    assert!(res.is_ok());
1270
1271    let zn = res.unwrap();
1272
1273    // sanity: check the claimed output with a direct computation of the same
1274    let mut zn_direct = vec![<E1 as Engine>::Scalar::ZERO];
1275    for _i in 0..num_steps {
1276      zn_direct = circuit.clone().output(&zn_direct);
1277    }
1278    assert_eq!(zn, zn_direct);
1279    assert_eq!(zn, vec![<E1 as Engine>::Scalar::from(2460515u64)]);
1280
1281    // produce the prover and verifier keys for compressed snark
1282    let (pk, vk) = CompressedSNARK::<_, _, _, S<E1, EE1>, S<E2, EE2>>::setup(&pp).unwrap();
1283
1284    // produce a compressed SNARK
1285    let res = CompressedSNARK::<_, _, _, S<E1, EE1>, S<E2, EE2>>::prove(&pp, &pk, &recursive_snark);
1286    assert!(res.is_ok());
1287    let compressed_snark = res.unwrap();
1288
1289    // verify the compressed SNARK
1290    let res = compressed_snark.verify(&vk, num_steps, &[<E1 as Engine>::Scalar::ZERO]);
1291    assert!(res.is_ok());
1292  }
1293
1294  #[test]
1295  fn test_ivc_nontrivial_with_compression() {
1296    test_ivc_nontrivial_with_compression_with::<PallasEngine, VestaEngine, EE<_>, EE<_>>();
1297    test_ivc_nontrivial_with_compression_with::<Bn256EngineKZG, GrumpkinEngine, EEPrime<_>, EE<_>>(
1298    );
1299    test_ivc_nontrivial_with_compression_with::<Secp256k1Engine, Secq256k1Engine, EE<_>, EE<_>>();
1300
1301    test_ivc_nontrivial_with_spark_compression_with::<
1302      Bn256EngineKZG,
1303      GrumpkinEngine,
1304      crate::provider::hyperkzg::EvaluationEngine<_>,
1305      EE<_>,
1306    >();
1307  }
1308
1309  fn test_ivc_nontrivial_with_spark_compression_with<E1, E2, EE1, EE2>()
1310  where
1311    E1: Engine<Base = <E2 as Engine>::Scalar>,
1312    E2: Engine<Base = <E1 as Engine>::Scalar>,
1313    EE1: EvaluationEngineTrait<E1>,
1314    EE2: EvaluationEngineTrait<E2>,
1315  {
1316    let circuit = CubicCircuit::default();
1317
1318    // produce public parameters, which we'll use with a spark-compressed SNARK
1319    let pp = PublicParams::<E1, E2, CubicCircuit<<E1 as Engine>::Scalar>>::setup(
1320      &circuit,
1321      &*SPrime::<E1, EE1>::ck_floor(),
1322      &*SPrime::<E2, EE2>::ck_floor(),
1323    )
1324    .unwrap();
1325
1326    let num_steps = 3;
1327
1328    // produce a recursive SNARK
1329    let mut recursive_snark = RecursiveSNARK::<E1, E2, CubicCircuit<<E1 as Engine>::Scalar>>::new(
1330      &pp,
1331      &circuit,
1332      &[<E1 as Engine>::Scalar::ZERO],
1333    )
1334    .unwrap();
1335
1336    for _i in 0..num_steps {
1337      let res = recursive_snark.prove_step(&pp, &circuit);
1338      assert!(res.is_ok());
1339    }
1340
1341    // verify the recursive SNARK
1342    let res = recursive_snark.verify(&pp, num_steps, &[<E1 as Engine>::Scalar::ZERO]);
1343    assert!(res.is_ok());
1344
1345    let zn = res.unwrap();
1346
1347    // sanity: check the claimed output with a direct computation of the same
1348    let mut zn_direct = vec![<E1 as Engine>::Scalar::ZERO];
1349    for _i in 0..num_steps {
1350      zn_direct = CubicCircuit::default().output(&zn_direct);
1351    }
1352    assert_eq!(zn, zn_direct);
1353    assert_eq!(zn, vec![<E1 as Engine>::Scalar::from(2460515u64)]);
1354
1355    // run the compressed snark with Spark compiler
1356    // produce the prover and verifier keys for compressed snark
1357    let (pk, vk) =
1358      CompressedSNARK::<_, _, _, SPrime<E1, EE1>, SPrime<E2, EE2>>::setup(&pp).unwrap();
1359
1360    // produce a compressed SNARK
1361    let res = CompressedSNARK::<_, _, _, SPrime<E1, EE1>, SPrime<E2, EE2>>::prove(
1362      &pp,
1363      &pk,
1364      &recursive_snark,
1365    );
1366    assert!(res.is_ok());
1367    let compressed_snark = res.unwrap();
1368
1369    // verify the compressed SNARK
1370    let res = compressed_snark.verify(&vk, num_steps, &[<E1 as Engine>::Scalar::ZERO]);
1371    assert!(res.is_ok());
1372  }
1373
1374  #[test]
1375  fn test_ivc_nontrivial_with_spark_compression() {
1376    test_ivc_nontrivial_with_spark_compression_with::<PallasEngine, VestaEngine, EE<_>, EE<_>>();
1377    test_ivc_nontrivial_with_spark_compression_with::<
1378      Bn256EngineKZG,
1379      GrumpkinEngine,
1380      EEPrime<_>,
1381      EE<_>,
1382    >();
1383    test_ivc_nontrivial_with_spark_compression_with::<Secp256k1Engine, Secq256k1Engine, EE<_>, EE<_>>(
1384    );
1385  }
1386
1387  fn test_ivc_nondet_with_compression_with<E1, E2, EE1, EE2>()
1388  where
1389    E1: Engine<Base = <E2 as Engine>::Scalar>,
1390    E2: Engine<Base = <E1 as Engine>::Scalar>,
1391    EE1: EvaluationEngineTrait<E1>,
1392    EE2: EvaluationEngineTrait<E2>,
1393  {
1394    // y is a non-deterministic advice representing the fifth root of the input at a step.
1395    #[derive(Clone, Debug)]
1396    struct FifthRootCheckingCircuit<F: PrimeField> {
1397      y: F,
1398    }
1399
1400    impl<F: PrimeField> FifthRootCheckingCircuit<F> {
1401      fn new(num_steps: usize) -> (Vec<F>, Vec<Self>) {
1402        let mut powers = Vec::new();
1403        let rng = &mut rand::rngs::OsRng;
1404        let mut seed = F::random(rng);
1405        for _i in 0..num_steps + 1 {
1406          seed *= seed.clone().square().square();
1407
1408          powers.push(Self { y: seed });
1409        }
1410
1411        // reverse the powers to get roots
1412        let roots = powers.into_iter().rev().collect::<Vec<Self>>();
1413        (vec![roots[0].y], roots[1..].to_vec())
1414      }
1415    }
1416
1417    impl<F> StepCircuit<F> for FifthRootCheckingCircuit<F>
1418    where
1419      F: PrimeField,
1420    {
1421      fn arity(&self) -> usize {
1422        1
1423      }
1424
1425      fn synthesize<CS: ConstraintSystem<F>>(
1426        &self,
1427        cs: &mut CS,
1428        z: &[AllocatedNum<F>],
1429      ) -> Result<Vec<AllocatedNum<F>>, SynthesisError> {
1430        let x = &z[0];
1431
1432        // we allocate a variable and set it to the provided non-deterministic advice.
1433        let y = AllocatedNum::alloc_infallible(cs.namespace(|| "y"), || self.y);
1434
1435        // We now check if y = x^{1/5} by checking if y^5 = x
1436        let y_sq = y.square(cs.namespace(|| "y_sq"))?;
1437        let y_quad = y_sq.square(cs.namespace(|| "y_quad"))?;
1438        let y_pow_5 = y_quad.mul(cs.namespace(|| "y_fifth"), &y)?;
1439
1440        cs.enforce(
1441          || "y^5 = x",
1442          |lc| lc + y_pow_5.get_variable(),
1443          |lc| lc + CS::one(),
1444          |lc| lc + x.get_variable(),
1445        );
1446
1447        Ok(vec![y])
1448      }
1449    }
1450
1451    let circuit = FifthRootCheckingCircuit {
1452      y: <E1 as Engine>::Scalar::ZERO,
1453    };
1454
1455    // produce public parameters
1456    let pp = PublicParams::<E1, E2, FifthRootCheckingCircuit<<E1 as Engine>::Scalar>>::setup(
1457      &circuit,
1458      &*default_ck_hint(),
1459      &*default_ck_hint(),
1460    )
1461    .unwrap();
1462
1463    let num_steps = 3;
1464
1465    // produce non-deterministic advice
1466    let (z0, roots) = FifthRootCheckingCircuit::new(num_steps);
1467
1468    // produce a recursive SNARK
1469    let mut recursive_snark: RecursiveSNARK<
1470      E1,
1471      E2,
1472      FifthRootCheckingCircuit<<E1 as Engine>::Scalar>,
1473    > = RecursiveSNARK::<E1, E2, FifthRootCheckingCircuit<<E1 as Engine>::Scalar>>::new(
1474      &pp, &roots[0], &z0,
1475    )
1476    .unwrap();
1477
1478    for circuit in roots.iter().take(num_steps) {
1479      let res = recursive_snark.prove_step(&pp, circuit);
1480      assert!(res.is_ok());
1481    }
1482
1483    // verify the recursive SNARK
1484    let res = recursive_snark.verify(&pp, num_steps, &z0);
1485    assert!(res.is_ok());
1486
1487    // produce the prover and verifier keys for compressed snark
1488    let (pk, vk) = CompressedSNARK::<_, _, _, S<E1, EE1>, S<E2, EE2>>::setup(&pp).unwrap();
1489
1490    // produce a compressed SNARK
1491    let res = CompressedSNARK::<_, _, _, S<E1, EE1>, S<E2, EE2>>::prove(&pp, &pk, &recursive_snark);
1492    assert!(res.is_ok());
1493    let compressed_snark = res.unwrap();
1494
1495    // verify the compressed SNARK
1496    let res = compressed_snark.verify(&vk, num_steps, &z0);
1497    assert!(res.is_ok());
1498  }
1499
1500  #[test]
1501  fn test_ivc_nondet_with_compression() {
1502    test_ivc_nondet_with_compression_with::<PallasEngine, VestaEngine, EE<_>, EE<_>>();
1503    test_ivc_nondet_with_compression_with::<Bn256EngineKZG, GrumpkinEngine, EEPrime<_>, EE<_>>();
1504    test_ivc_nondet_with_compression_with::<Secp256k1Engine, Secq256k1Engine, EE<_>, EE<_>>();
1505  }
1506
1507  fn test_ivc_base_with<E1, E2>()
1508  where
1509    E1: Engine<Base = <E2 as Engine>::Scalar>,
1510    E2: Engine<Base = <E1 as Engine>::Scalar>,
1511  {
1512    let test_circuit1 = CubicCircuit::<<E1 as Engine>::Scalar>::default();
1513
1514    // produce public parameters
1515    let pp = PublicParams::<E1, E2, CubicCircuit<<E1 as Engine>::Scalar>>::setup(
1516      &test_circuit1,
1517      &*default_ck_hint(),
1518      &*default_ck_hint(),
1519    )
1520    .unwrap();
1521
1522    let num_steps = 1;
1523
1524    // produce a recursive SNARK
1525    let mut recursive_snark = RecursiveSNARK::<E1, E2, CubicCircuit<<E1 as Engine>::Scalar>>::new(
1526      &pp,
1527      &test_circuit1,
1528      &[<E1 as Engine>::Scalar::ZERO],
1529    )
1530    .unwrap();
1531
1532    // produce a recursive SNARK
1533    let res = recursive_snark.prove_step(&pp, &test_circuit1);
1534
1535    assert!(res.is_ok());
1536
1537    // verify the recursive SNARK
1538    let res = recursive_snark.verify(&pp, num_steps, &[<E1 as Engine>::Scalar::ZERO]);
1539    assert!(res.is_ok());
1540
1541    let zn = res.unwrap();
1542
1543    assert_eq!(zn, vec![<E1 as Engine>::Scalar::from(5u64)]);
1544  }
1545
1546  #[test]
1547  fn test_ivc_base() {
1548    test_ivc_base_with::<PallasEngine, VestaEngine>();
1549    test_ivc_base_with::<Bn256EngineKZG, GrumpkinEngine>();
1550    test_ivc_base_with::<Secp256k1Engine, Secq256k1Engine>();
1551  }
1552
1553  fn test_setup_with<E1, E2>()
1554  where
1555    E1: Engine<Base = <E2 as Engine>::Scalar>,
1556    E2: Engine<Base = <E1 as Engine>::Scalar>,
1557  {
1558    #[derive(Clone, Debug, Default)]
1559    struct CircuitWithInputize<F: PrimeField> {
1560      _p: PhantomData<F>,
1561    }
1562
1563    impl<F: PrimeField> StepCircuit<F> for CircuitWithInputize<F> {
1564      fn arity(&self) -> usize {
1565        1
1566      }
1567
1568      fn synthesize<CS: ConstraintSystem<F>>(
1569        &self,
1570        cs: &mut CS,
1571        z: &[AllocatedNum<F>],
1572      ) -> Result<Vec<AllocatedNum<F>>, SynthesisError> {
1573        let x = &z[0];
1574        let y = x.square(cs.namespace(|| "x_sq"))?;
1575        y.inputize(cs.namespace(|| "y"))?; // inputize y
1576        Ok(vec![y])
1577      }
1578    }
1579
1580    // produce public parameters with trivial secondary
1581    let circuit = CircuitWithInputize::<<E1 as Engine>::Scalar>::default();
1582    let pp = PublicParams::<E1, E2, CircuitWithInputize<E1::Scalar>>::setup(
1583      &circuit,
1584      &*default_ck_hint(),
1585      &*default_ck_hint(),
1586    );
1587    assert!(pp.is_err());
1588    assert_eq!(pp.err(), Some(NovaError::InvalidStepCircuitIO));
1589
1590    let circuit = CircuitWithInputize::<E1::Scalar>::default();
1591    let pp = PublicParams::<E1, E2, CircuitWithInputize<E1::Scalar>>::setup(
1592      &circuit,
1593      &*default_ck_hint(),
1594      &*default_ck_hint(),
1595    );
1596    assert!(pp.is_err());
1597    assert_eq!(pp.err(), Some(NovaError::InvalidStepCircuitIO));
1598  }
1599
1600  #[test]
1601  fn test_setup() {
1602    test_setup_with::<Bn256EngineKZG, GrumpkinEngine>();
1603  }
1604
1605  /// Test that proves and verifies a circuit using pruned PPOT files.
1606  ///
1607  /// This test is ignored by default because it requires external PPOT files.
1608  /// To run it, download pruned PPOT files to `/tmp/pruned_ptau/` and run:
1609  ///
1610  /// ```bash
1611  /// cargo test --release --features io test_ivc_with_ppot_files -- --ignored
1612  /// ```
1613  ///
1614  /// You can obtain pruned PPOT files by running:
1615  /// `cargo run --example ppot_prune --features io -- --power 20 --output /tmp/pruned_ptau`
1616  #[test]
1617  #[ignore]
1618  #[cfg(feature = "io")]
1619  fn test_ivc_with_ppot_files() {
1620    use std::path::Path;
1621
1622    type E1 = Bn256EngineKZG;
1623    type E2 = GrumpkinEngine;
1624
1625    let circuit_primary = CubicCircuit::<<E1 as Engine>::Scalar>::default();
1626
1627    // Path to pruned PPOT files - adjust as needed
1628    let ptau_dir = Path::new("/tmp/pruned_ptau");
1629
1630    if !ptau_dir.exists() {
1631      eprintln!(
1632        "PPOT directory not found at {:?}. \n\
1633         To run this test:\n\
1634         1. Create the directory: mkdir -p /tmp/pruned_ptau\n\
1635         2. Download or generate pruned PPOT files:\n\
1636            cargo run --example ppot_prune --features io -- --power 20 --output /tmp/pruned_ptau\n\
1637         3. Run the test: cargo test --release --features io test_ivc_with_ppot_files -- --ignored",
1638        ptau_dir
1639      );
1640      return;
1641    }
1642
1643    // Create public parameters using PPOT files
1644    let pp = PublicParams::<E1, E2, CubicCircuit<<E1 as Engine>::Scalar>>::setup_with_ptau_dir(
1645      &circuit_primary,
1646      &*default_ck_hint(),
1647      &*default_ck_hint(),
1648      ptau_dir,
1649    )
1650    .expect("Failed to setup with PPOT files");
1651
1652    // Verify the digest is computed correctly
1653    let _ = pp.digest();
1654
1655    // Create a recursive SNARK
1656    let z0_primary = vec![<E1 as Engine>::Scalar::from(3u64)];
1657    let mut recursive_snark = RecursiveSNARK::<E1, E2, CubicCircuit<<E1 as Engine>::Scalar>>::new(
1658      &pp,
1659      &circuit_primary,
1660      &z0_primary,
1661    )
1662    .expect("Failed to create recursive SNARK");
1663
1664    // Perform a few iterations
1665    let num_steps = 3;
1666    for _ in 0..num_steps {
1667      recursive_snark
1668        .prove_step(&pp, &circuit_primary)
1669        .expect("Failed to prove step");
1670    }
1671
1672    // Verify the recursive SNARK
1673    let z0_primary = vec![<E1 as Engine>::Scalar::from(3u64)];
1674    recursive_snark
1675      .verify(&pp, num_steps, &z0_primary)
1676      .expect("Failed to verify recursive SNARK");
1677
1678    println!(
1679      "Successfully proved and verified {} steps using PPOT files from {:?}",
1680      num_steps, ptau_dir
1681    );
1682  }
1683}