diff --git a/Cargo.toml b/Cargo.toml index b227ff2c..224a1715 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nova-snark" -version = "0.39.0" +version = "0.40.0" authors = ["Srinath Setty "] edition = "2021" description = "High-speed recursive arguments from folding schemes" diff --git a/benches/compressed-snark.rs b/benches/compressed-snark.rs index 078a8d8e..99623597 100644 --- a/benches/compressed-snark.rs +++ b/benches/compressed-snark.rs @@ -5,13 +5,13 @@ use criterion::{measurement::WallTime, *}; use ff::PrimeField; use nova_snark::{ frontend::{num::AllocatedNum, ConstraintSystem, SynthesisError}, + nova::{CompressedSNARK, PublicParams, RecursiveSNARK}, provider::{Bn256EngineKZG, GrumpkinEngine}, traits::{ circuit::{StepCircuit, TrivialCircuit}, snark::RelaxedR1CSSNARKTrait, Engine, }, - CompressedSNARK, PublicParams, RecursiveSNARK, }; use std::time::Duration; diff --git a/benches/compute-digest.rs b/benches/compute-digest.rs index 61e9181b..9d99f3c3 100644 --- a/benches/compute-digest.rs +++ b/benches/compute-digest.rs @@ -4,13 +4,13 @@ use criterion::{black_box, criterion_group, criterion_main, Criterion}; use ff::PrimeField; use nova_snark::{ frontend::{num::AllocatedNum, ConstraintSystem, SynthesisError}, + nova::PublicParams, provider::{Bn256EngineKZG, GrumpkinEngine}, traits::{ circuit::{StepCircuit, TrivialCircuit}, snark::default_ck_hint, Engine, }, - PublicParams, }; type E1 = Bn256EngineKZG; diff --git a/benches/recursive-snark.rs b/benches/recursive-snark.rs index 4e9e88a8..1b311a92 100644 --- a/benches/recursive-snark.rs +++ b/benches/recursive-snark.rs @@ -5,13 +5,13 @@ use criterion::*; use ff::PrimeField; use nova_snark::{ frontend::{num::AllocatedNum, ConstraintSystem, SynthesisError}, + nova::{PublicParams, RecursiveSNARK}, provider::{Bn256EngineKZG, GrumpkinEngine}, traits::{ circuit::{StepCircuit, TrivialCircuit}, snark::default_ck_hint, Engine, }, - PublicParams, RecursiveSNARK, }; use std::time::Duration; diff --git a/benches/sha256.rs b/benches/sha256.rs index bfa17336..227093fb 100644 --- a/benches/sha256.rs +++ b/benches/sha256.rs @@ -11,13 +11,13 @@ use nova_snark::{ num::{AllocatedNum, Num}, sha256, AllocatedBit, Assignment, Boolean, ConstraintSystem, SynthesisError, }, + nova::{PublicParams, RecursiveSNARK}, provider::{Bn256EngineKZG, GrumpkinEngine}, traits::{ circuit::{StepCircuit, TrivialCircuit}, snark::default_ck_hint, Engine, }, - PublicParams, RecursiveSNARK, }; use sha2::{Digest, Sha256}; diff --git a/examples/and.rs b/examples/and.rs index 24790720..35fb965b 100644 --- a/examples/and.rs +++ b/examples/and.rs @@ -8,13 +8,13 @@ use nova_snark::{ frontend::{ num::AllocatedNum, AllocatedBit, ConstraintSystem, LinearCombination, SynthesisError, }, + nova::{CompressedSNARK, PublicParams, RecursiveSNARK}, provider::{Bn256EngineKZG, GrumpkinEngine}, traits::{ circuit::{StepCircuit, TrivialCircuit}, snark::RelaxedR1CSSNARKTrait, Engine, Group, }, - CompressedSNARK, PublicParams, RecursiveSNARK, }; use rand::Rng; use std::time::Instant; diff --git a/examples/hashchain.rs b/examples/hashchain.rs index f3e3ba9d..6a90eef2 100644 --- a/examples/hashchain.rs +++ b/examples/hashchain.rs @@ -11,13 +11,13 @@ use nova_snark::{ num::AllocatedNum, ConstraintSystem, SynthesisError, }, + nova::{CompressedSNARK, PublicParams, RecursiveSNARK}, provider::{Bn256EngineKZG, GrumpkinEngine}, traits::{ circuit::{StepCircuit, TrivialCircuit}, snark::RelaxedR1CSSNARKTrait, Engine, Group, }, - CompressedSNARK, PublicParams, RecursiveSNARK, }; use std::time::Instant; diff --git a/examples/minroot.rs b/examples/minroot.rs index 1bb78ef7..23bdf050 100644 --- a/examples/minroot.rs +++ b/examples/minroot.rs @@ -5,13 +5,13 @@ use ff::Field; use flate2::{write::ZlibEncoder, Compression}; use nova_snark::{ frontend::{num::AllocatedNum, ConstraintSystem, SynthesisError}, + nova::{CompressedSNARK, PublicParams, RecursiveSNARK}, provider::{Bn256EngineKZG, GrumpkinEngine}, traits::{ circuit::{StepCircuit, TrivialCircuit}, snark::RelaxedR1CSSNARKTrait, Engine, Group, }, - CompressedSNARK, PublicParams, RecursiveSNARK, }; use num_bigint::BigUint; use std::time::Instant; diff --git a/src/constants.rs b/src/constants.rs index 6c6180a8..c458a5dd 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -2,6 +2,3 @@ pub(crate) const NUM_CHALLENGE_BITS: usize = 128; pub(crate) const NUM_HASH_BITS: usize = 250; pub(crate) const BN_LIMB_WIDTH: usize = 64; pub(crate) const BN_N_LIMBS: usize = 4; -pub(crate) const NUM_FE_WITHOUT_IO_FOR_CRHF: usize = 18; -pub(crate) const NUM_FE_FOR_RO: usize = 9; -pub(crate) const NUM_FE_FOR_RO_RELAXED: usize = 34; diff --git a/src/gadgets/r1cs.rs b/src/gadgets/r1cs.rs index 25167508..ec7b8f5e 100644 --- a/src/gadgets/r1cs.rs +++ b/src/gadgets/r1cs.rs @@ -4,7 +4,7 @@ use super::nonnative::{ util::{f_to_nat, Num}, }; use crate::{ - constants::{NUM_CHALLENGE_BITS, NUM_FE_FOR_RO}, + constants::NUM_CHALLENGE_BITS, frontend::{num::AllocatedNum, Assignment, Boolean, ConstraintSystem, SynthesisError}, gadgets::{ ecc::AllocatedPoint, @@ -234,7 +234,7 @@ impl AllocatedRelaxedR1CSInstance { n_limbs: usize, ) -> Result, SynthesisError> { // Compute r: - let mut ro = E::ROCircuit::new(ro_consts, NUM_FE_FOR_RO); + let mut ro = E::ROCircuit::new(ro_consts); ro.absorb(params); // running instance `U` does not need to absorbed since u.X[0] = Hash(params, U, i, z0, zi) diff --git a/src/lib.rs b/src/lib.rs index 3bcc8cd9..8d4b059e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,12 +10,8 @@ #![allow(non_snake_case)] #![forbid(unsafe_code)] -// private modules -mod circuit; -mod constants; -mod digest; -mod nifs; -mod r1cs; +// main APIs exposed by this library +pub mod nova; // public modules pub mod errors; @@ -25,1683 +21,15 @@ pub mod provider; pub mod spartan; pub mod traits; -use circuit::{NovaAugmentedCircuit, NovaAugmentedCircuitInputs, NovaAugmentedCircuitParams}; -use constants::{BN_LIMB_WIDTH, BN_N_LIMBS, NUM_FE_WITHOUT_IO_FOR_CRHF, NUM_HASH_BITS}; -use core::marker::PhantomData; -use digest::{DigestComputer, SimpleDigestible}; -use errors::NovaError; -use ff::Field; -use frontend::{ - r1cs::{NovaShape, NovaWitness}, - shape_cs::ShapeCS, - solver::SatisfyingAssignment, - ConstraintSystem, SynthesisError, -}; -use gadgets::utils::scalar_as_base; -use nifs::{NIFSRelaxed, NIFS}; -use once_cell::sync::OnceCell; -use r1cs::{ - CommitmentKeyHint, R1CSInstance, R1CSShape, R1CSWitness, RelaxedR1CSInstance, RelaxedR1CSWitness, -}; -use rand_core::OsRng; -use serde::{Deserialize, Serialize}; -use traits::{ - circuit::StepCircuit, commitment::CommitmentEngineTrait, snark::RelaxedR1CSSNARKTrait, - AbsorbInROTrait, Engine, ROConstants, ROConstantsCircuit, ROTrait, -}; - -/// A type that holds public parameters of Nova -#[derive(Serialize, Deserialize)] -#[serde(bound = "")] -pub struct PublicParams -where - E1: Engine::Scalar>, - E2: Engine::Scalar>, - C1: StepCircuit, - C2: StepCircuit, -{ - F_arity_primary: usize, - F_arity_secondary: usize, - ro_consts_primary: ROConstants, - ro_consts_circuit_primary: ROConstantsCircuit, - ck_primary: CommitmentKey, - r1cs_shape_primary: R1CSShape, - ro_consts_secondary: ROConstants, - ro_consts_circuit_secondary: ROConstantsCircuit, - ck_secondary: CommitmentKey, - r1cs_shape_secondary: R1CSShape, - augmented_circuit_params_primary: NovaAugmentedCircuitParams, - augmented_circuit_params_secondary: NovaAugmentedCircuitParams, - #[serde(skip, default = "OnceCell::new")] - digest: OnceCell, - _p: PhantomData<(C1, C2)>, -} - -impl SimpleDigestible for PublicParams -where - E1: Engine::Scalar>, - E2: Engine::Scalar>, - C1: StepCircuit, - C2: StepCircuit, -{ -} - -impl PublicParams -where - E1: Engine::Scalar>, - E2: Engine::Scalar>, - C1: StepCircuit, - C2: StepCircuit, -{ - /// Creates a new `PublicParams` for a pair of circuits `C1` and `C2`. - /// - /// # Note - /// - /// Public parameters set up a number of bases for the homomorphic commitment scheme of Nova. - /// - /// Some final compressing SNARKs, like variants of Spartan, use computation commitments that require - /// larger sizes for these parameters. These SNARKs provide a hint for these values by - /// implementing `RelaxedR1CSSNARKTrait::ck_floor()`, which can be passed to this function. - /// - /// If you're not using such a SNARK, pass `nova_snark::traits::snark::default_ck_hint()` instead. - /// - /// # Arguments - /// - /// * `c_primary`: The primary circuit of type `C1`. - /// * `c_secondary`: The secondary circuit of type `C2`. - /// * `ck_hint1`: A `CommitmentKeyHint` for `G1`, which is a function that provides a hint - /// for the number of generators required in the commitment scheme for the primary circuit. - /// * `ck_hint2`: A `CommitmentKeyHint` for `G2`, similar to `ck_hint1`, but for the secondary circuit. - /// - /// # Example - /// - /// ```rust - /// # use nova_snark::spartan::ppsnark::RelaxedR1CSSNARK; - /// # use nova_snark::provider::ipa_pc::EvaluationEngine; - /// # use nova_snark::provider::{PallasEngine, VestaEngine}; - /// # use nova_snark::traits::{circuit::TrivialCircuit, Engine, snark::RelaxedR1CSSNARKTrait}; - /// use nova_snark::PublicParams; - /// - /// type E1 = PallasEngine; - /// type E2 = VestaEngine; - /// type EE = EvaluationEngine; - /// type SPrime = RelaxedR1CSSNARK>; - /// - /// let circuit1 = TrivialCircuit::<::Scalar>::default(); - /// let circuit2 = TrivialCircuit::<::Scalar>::default(); - /// // Only relevant for a SNARK using computational commitments, pass &(|_| 0) - /// // or &*nova_snark::traits::snark::default_ck_hint() otherwise. - /// let ck_hint1 = &*SPrime::::ck_floor(); - /// let ck_hint2 = &*SPrime::::ck_floor(); - /// - /// let pp = PublicParams::setup(&circuit1, &circuit2, ck_hint1, ck_hint2); - /// ``` - pub fn setup( - c_primary: &C1, - c_secondary: &C2, - ck_hint1: &CommitmentKeyHint, - ck_hint2: &CommitmentKeyHint, - ) -> Result { - let augmented_circuit_params_primary = - NovaAugmentedCircuitParams::new(BN_LIMB_WIDTH, BN_N_LIMBS, true); - let augmented_circuit_params_secondary = - NovaAugmentedCircuitParams::new(BN_LIMB_WIDTH, BN_N_LIMBS, false); - - let ro_consts_primary: ROConstants = ROConstants::::default(); - let ro_consts_secondary: ROConstants = ROConstants::::default(); - - let F_arity_primary = c_primary.arity(); - let F_arity_secondary = c_secondary.arity(); - - // ro_consts_circuit_primary are parameterized by E2 because the type alias uses E2::Base = E1::Scalar - let ro_consts_circuit_primary: ROConstantsCircuit = ROConstantsCircuit::::default(); - let ro_consts_circuit_secondary: ROConstantsCircuit = ROConstantsCircuit::::default(); - - // Initialize ck for the primary - let circuit_primary: NovaAugmentedCircuit<'_, E2, C1> = NovaAugmentedCircuit::new( - &augmented_circuit_params_primary, - None, - c_primary, - ro_consts_circuit_primary.clone(), - ); - let mut cs: ShapeCS = ShapeCS::new(); - let _ = circuit_primary.synthesize(&mut cs); - let (r1cs_shape_primary, ck_primary) = cs.r1cs_shape(ck_hint1); - - // Initialize ck for the secondary - let circuit_secondary: NovaAugmentedCircuit<'_, E1, C2> = NovaAugmentedCircuit::new( - &augmented_circuit_params_secondary, - None, - c_secondary, - ro_consts_circuit_secondary.clone(), - ); - let mut cs: ShapeCS = ShapeCS::new(); - let _ = circuit_secondary.synthesize(&mut cs); - let (r1cs_shape_secondary, ck_secondary) = cs.r1cs_shape(ck_hint2); - - if r1cs_shape_primary.num_io != 2 || r1cs_shape_secondary.num_io != 2 { - return Err(NovaError::InvalidStepCircuitIO); - } - - let pp = PublicParams { - F_arity_primary, - F_arity_secondary, - ro_consts_primary, - ro_consts_circuit_primary, - ck_primary, - r1cs_shape_primary, - ro_consts_secondary, - ro_consts_circuit_secondary, - ck_secondary, - r1cs_shape_secondary, - augmented_circuit_params_primary, - augmented_circuit_params_secondary, - digest: OnceCell::new(), - _p: Default::default(), - }; - - // call pp.digest() so the digest is computed here rather than in RecursiveSNARK methods - let _ = pp.digest(); - - Ok(pp) - } - - /// Retrieve the digest of the public parameters. - pub fn digest(&self) -> E1::Scalar { - self - .digest - .get_or_try_init(|| DigestComputer::new(self).digest()) - .cloned() - .expect("Failure in retrieving digest") - } - - /// Returns the number of constraints in the primary and secondary circuits - pub const fn num_constraints(&self) -> (usize, usize) { - ( - self.r1cs_shape_primary.num_cons, - self.r1cs_shape_secondary.num_cons, - ) - } - - /// Returns the number of variables in the primary and secondary circuits - pub const fn num_variables(&self) -> (usize, usize) { - ( - self.r1cs_shape_primary.num_vars, - self.r1cs_shape_secondary.num_vars, - ) - } -} - -/// A SNARK that proves the correct execution of an incremental computation -#[derive(Clone, Debug, Serialize, Deserialize)] -#[serde(bound = "")] -pub struct RecursiveSNARK -where - E1: Engine::Scalar>, - E2: Engine::Scalar>, - C1: StepCircuit, - C2: StepCircuit, -{ - z0_primary: Vec, - z0_secondary: Vec, - r_W_primary: RelaxedR1CSWitness, - r_U_primary: RelaxedR1CSInstance, - ri_primary: E1::Scalar, - r_W_secondary: RelaxedR1CSWitness, - r_U_secondary: RelaxedR1CSInstance, - ri_secondary: E2::Scalar, - l_w_secondary: R1CSWitness, - l_u_secondary: R1CSInstance, - i: usize, - zi_primary: Vec, - zi_secondary: Vec, - _p: PhantomData<(C1, C2)>, -} - -impl RecursiveSNARK -where - E1: Engine::Scalar>, - E2: Engine::Scalar>, - C1: StepCircuit, - C2: StepCircuit, -{ - /// Create new instance of recursive SNARK - pub fn new( - pp: &PublicParams, - c_primary: &C1, - c_secondary: &C2, - z0_primary: &[E1::Scalar], - z0_secondary: &[E2::Scalar], - ) -> Result { - if z0_primary.len() != pp.F_arity_primary || z0_secondary.len() != pp.F_arity_secondary { - return Err(NovaError::InvalidInitialInputLength); - } - - let ri_primary = E1::Scalar::random(&mut OsRng); - let ri_secondary = E2::Scalar::random(&mut OsRng); - - // base case for the primary - let mut cs_primary = SatisfyingAssignment::::new(); - let inputs_primary: NovaAugmentedCircuitInputs = NovaAugmentedCircuitInputs::new( - scalar_as_base::(pp.digest()), - E1::Scalar::ZERO, - z0_primary.to_vec(), - None, - None, - None, - ri_primary, // "r next" - None, - None, - ); - - let circuit_primary: NovaAugmentedCircuit<'_, E2, C1> = NovaAugmentedCircuit::new( - &pp.augmented_circuit_params_primary, - Some(inputs_primary), - c_primary, - pp.ro_consts_circuit_primary.clone(), - ); - let zi_primary = circuit_primary.synthesize(&mut cs_primary)?; - let (u_primary, w_primary) = - cs_primary.r1cs_instance_and_witness(&pp.r1cs_shape_primary, &pp.ck_primary)?; - - // base case for the secondary - let mut cs_secondary = SatisfyingAssignment::::new(); - let inputs_secondary: NovaAugmentedCircuitInputs = NovaAugmentedCircuitInputs::new( - pp.digest(), - E2::Scalar::ZERO, - z0_secondary.to_vec(), - None, - None, - None, - ri_secondary, // "r next" - Some(u_primary.clone()), - None, - ); - let circuit_secondary: NovaAugmentedCircuit<'_, E1, C2> = NovaAugmentedCircuit::new( - &pp.augmented_circuit_params_secondary, - Some(inputs_secondary), - c_secondary, - pp.ro_consts_circuit_secondary.clone(), - ); - let zi_secondary = circuit_secondary.synthesize(&mut cs_secondary)?; - let (u_secondary, w_secondary) = - cs_secondary.r1cs_instance_and_witness(&pp.r1cs_shape_secondary, &pp.ck_secondary)?; - - // IVC proof for the primary circuit - let l_w_primary = w_primary; - let l_u_primary = u_primary; - let r_W_primary = RelaxedR1CSWitness::from_r1cs_witness(&pp.r1cs_shape_primary, &l_w_primary); - let r_U_primary = - RelaxedR1CSInstance::from_r1cs_instance(&pp.ck_primary, &pp.r1cs_shape_primary, &l_u_primary); - - // IVC proof for the secondary circuit - let l_w_secondary = w_secondary; - let l_u_secondary = u_secondary; - let r_W_secondary = RelaxedR1CSWitness::::default(&pp.r1cs_shape_secondary); - let r_U_secondary = - RelaxedR1CSInstance::::default(&pp.ck_secondary, &pp.r1cs_shape_secondary); - - assert!( - !(zi_primary.len() != pp.F_arity_primary || zi_secondary.len() != pp.F_arity_secondary), - "Invalid step length" - ); - - let zi_primary = zi_primary - .iter() - .map(|v| v.get_value().ok_or(SynthesisError::AssignmentMissing)) - .collect::::Scalar>, _>>()?; - - let zi_secondary = zi_secondary - .iter() - .map(|v| v.get_value().ok_or(SynthesisError::AssignmentMissing)) - .collect::::Scalar>, _>>()?; - - Ok(Self { - z0_primary: z0_primary.to_vec(), - z0_secondary: z0_secondary.to_vec(), - r_W_primary, - r_U_primary, - ri_primary, - r_W_secondary, - r_U_secondary, - ri_secondary, - l_w_secondary, - l_u_secondary, - i: 0, - zi_primary, - zi_secondary, - _p: Default::default(), - }) - } - - /// Create a new `RecursiveSNARK` (or updates the provided `RecursiveSNARK`) - /// by executing a step of the incremental computation - pub fn prove_step( - &mut self, - pp: &PublicParams, - c_primary: &C1, - c_secondary: &C2, - ) -> Result<(), NovaError> { - // first step was already done in the constructor - if self.i == 0 { - self.i = 1; - return Ok(()); - } - - // fold the secondary circuit's instance - let (nifs_secondary, (r_U_secondary, r_W_secondary)) = NIFS::prove( - &pp.ck_secondary, - &pp.ro_consts_secondary, - &scalar_as_base::(pp.digest()), - &pp.r1cs_shape_secondary, - &self.r_U_secondary, - &self.r_W_secondary, - &self.l_u_secondary, - &self.l_w_secondary, - )?; - - let r_next_primary = E1::Scalar::random(&mut OsRng); - - let mut cs_primary = SatisfyingAssignment::::new(); - let inputs_primary: NovaAugmentedCircuitInputs = NovaAugmentedCircuitInputs::new( - scalar_as_base::(pp.digest()), - E1::Scalar::from(self.i as u64), - self.z0_primary.to_vec(), - Some(self.zi_primary.clone()), - Some(self.r_U_secondary.clone()), - Some(self.ri_primary), - r_next_primary, - Some(self.l_u_secondary.clone()), - Some(nifs_secondary.comm_T), - ); - - let circuit_primary: NovaAugmentedCircuit<'_, E2, C1> = NovaAugmentedCircuit::new( - &pp.augmented_circuit_params_primary, - Some(inputs_primary), - c_primary, - pp.ro_consts_circuit_primary.clone(), - ); - let zi_primary = circuit_primary.synthesize(&mut cs_primary)?; - - let (l_u_primary, l_w_primary) = - cs_primary.r1cs_instance_and_witness(&pp.r1cs_shape_primary, &pp.ck_primary)?; - - // fold the primary circuit's instance - let (nifs_primary, (r_U_primary, r_W_primary)) = NIFS::prove( - &pp.ck_primary, - &pp.ro_consts_primary, - &pp.digest(), - &pp.r1cs_shape_primary, - &self.r_U_primary, - &self.r_W_primary, - &l_u_primary, - &l_w_primary, - )?; - - let r_next_secondary = E2::Scalar::random(&mut OsRng); - - let mut cs_secondary = SatisfyingAssignment::::new(); - let inputs_secondary: NovaAugmentedCircuitInputs = NovaAugmentedCircuitInputs::new( - pp.digest(), - E2::Scalar::from(self.i as u64), - self.z0_secondary.to_vec(), - Some(self.zi_secondary.clone()), - Some(self.r_U_primary.clone()), - Some(self.ri_secondary), - r_next_secondary, - Some(l_u_primary), - Some(nifs_primary.comm_T), - ); - - let circuit_secondary: NovaAugmentedCircuit<'_, E1, C2> = NovaAugmentedCircuit::new( - &pp.augmented_circuit_params_secondary, - Some(inputs_secondary), - c_secondary, - pp.ro_consts_circuit_secondary.clone(), - ); - let zi_secondary = circuit_secondary.synthesize(&mut cs_secondary)?; - - let (l_u_secondary, l_w_secondary) = cs_secondary - .r1cs_instance_and_witness(&pp.r1cs_shape_secondary, &pp.ck_secondary) - .map_err(|_e| NovaError::UnSat)?; - - // update the running instances and witnesses - self.zi_primary = zi_primary - .iter() - .map(|v| v.get_value().ok_or(SynthesisError::AssignmentMissing)) - .collect::::Scalar>, _>>()?; - self.zi_secondary = zi_secondary - .iter() - .map(|v| v.get_value().ok_or(SynthesisError::AssignmentMissing)) - .collect::::Scalar>, _>>()?; - - self.l_u_secondary = l_u_secondary; - self.l_w_secondary = l_w_secondary; - - self.r_U_primary = r_U_primary; - self.r_W_primary = r_W_primary; - - self.i += 1; - - self.r_U_secondary = r_U_secondary; - self.r_W_secondary = r_W_secondary; - - self.ri_primary = r_next_primary; - self.ri_secondary = r_next_secondary; - - Ok(()) - } - - /// Verify the correctness of the `RecursiveSNARK` - pub fn verify( - &self, - pp: &PublicParams, - num_steps: usize, - z0_primary: &[E1::Scalar], - z0_secondary: &[E2::Scalar], - ) -> Result<(Vec, Vec), NovaError> { - // number of steps cannot be zero - let is_num_steps_zero = num_steps == 0; - - // check if the provided proof has executed num_steps - let is_num_steps_not_match = self.i != num_steps; - - // check if the initial inputs match - let is_inputs_not_match = self.z0_primary != z0_primary || self.z0_secondary != z0_secondary; - - // check if the (relaxed) R1CS instances have two public outputs - let is_instance_has_two_outputs = self.l_u_secondary.X.len() != 2 - || self.r_U_primary.X.len() != 2 - || self.r_U_secondary.X.len() != 2; - - if is_num_steps_zero - || is_num_steps_not_match - || is_inputs_not_match - || is_instance_has_two_outputs - { - return Err(NovaError::ProofVerifyError); - } - - // check if the output hashes in R1CS instances point to the right running instances - let (hash_primary, hash_secondary) = { - let mut hasher = ::RO::new( - pp.ro_consts_secondary.clone(), - NUM_FE_WITHOUT_IO_FOR_CRHF + 2 * pp.F_arity_primary, - ); - hasher.absorb(pp.digest()); - hasher.absorb(E1::Scalar::from(num_steps as u64)); - for e in z0_primary { - hasher.absorb(*e); - } - for e in &self.zi_primary { - hasher.absorb(*e); - } - self.r_U_secondary.absorb_in_ro(&mut hasher); - hasher.absorb(self.ri_primary); - - let mut hasher2 = ::RO::new( - pp.ro_consts_primary.clone(), - NUM_FE_WITHOUT_IO_FOR_CRHF + 2 * pp.F_arity_secondary, - ); - hasher2.absorb(scalar_as_base::(pp.digest())); - hasher2.absorb(E2::Scalar::from(num_steps as u64)); - for e in z0_secondary { - hasher2.absorb(*e); - } - for e in &self.zi_secondary { - hasher2.absorb(*e); - } - self.r_U_primary.absorb_in_ro(&mut hasher2); - hasher2.absorb(self.ri_secondary); - - ( - hasher.squeeze(NUM_HASH_BITS), - hasher2.squeeze(NUM_HASH_BITS), - ) - }; - - if hash_primary != self.l_u_secondary.X[0] - || hash_secondary != scalar_as_base::(self.l_u_secondary.X[1]) - { - return Err(NovaError::ProofVerifyError); - } - - // check the satisfiability of the provided instances - let (res_r_primary, (res_r_secondary, res_l_secondary)) = rayon::join( - || { - pp.r1cs_shape_primary - .is_sat_relaxed(&pp.ck_primary, &self.r_U_primary, &self.r_W_primary) - }, - || { - rayon::join( - || { - pp.r1cs_shape_secondary.is_sat_relaxed( - &pp.ck_secondary, - &self.r_U_secondary, - &self.r_W_secondary, - ) - }, - || { - pp.r1cs_shape_secondary.is_sat( - &pp.ck_secondary, - &self.l_u_secondary, - &self.l_w_secondary, - ) - }, - ) - }, - ); - - // check the returned res objects - res_r_primary?; - res_r_secondary?; - res_l_secondary?; - - Ok((self.zi_primary.clone(), self.zi_secondary.clone())) - } - - /// Get the outputs after the last step of computation. - pub fn outputs(&self) -> (&[E1::Scalar], &[E2::Scalar]) { - (&self.zi_primary, &self.zi_secondary) - } - - /// The number of steps which have been executed thus far. - pub fn num_steps(&self) -> usize { - self.i - } -} - -/// A type that holds the prover key for `CompressedSNARK` -#[derive(Clone, Debug, Serialize, Deserialize)] -#[serde(bound = "")] -pub struct ProverKey -where - E1: Engine::Scalar>, - E2: Engine::Scalar>, - C1: StepCircuit, - C2: StepCircuit, - S1: RelaxedR1CSSNARKTrait, - S2: RelaxedR1CSSNARKTrait, -{ - pk_primary: S1::ProverKey, - pk_secondary: S2::ProverKey, - _p: PhantomData<(C1, C2)>, -} - -/// A type that holds the verifier key for `CompressedSNARK` -#[derive(Clone, Serialize, Deserialize)] -#[serde(bound = "")] -pub struct VerifierKey -where - E1: Engine::Scalar>, - E2: Engine::Scalar>, - C1: StepCircuit, - C2: StepCircuit, - S1: RelaxedR1CSSNARKTrait, - S2: RelaxedR1CSSNARKTrait, -{ - F_arity_primary: usize, - F_arity_secondary: usize, - ro_consts_primary: ROConstants, - ro_consts_secondary: ROConstants, - pp_digest: E1::Scalar, - vk_primary: S1::VerifierKey, - vk_secondary: S2::VerifierKey, - dk_primary: DerandKey, - dk_secondary: DerandKey, - _p: PhantomData<(C1, C2)>, -} - -/// A SNARK that proves the knowledge of a valid `RecursiveSNARK` -#[derive(Clone, Serialize, Deserialize)] -#[serde(bound = "")] -pub struct CompressedSNARK -where - E1: Engine::Scalar>, - E2: Engine::Scalar>, - C1: StepCircuit, - C2: StepCircuit, - S1: RelaxedR1CSSNARKTrait, - S2: RelaxedR1CSSNARKTrait, -{ - r_U_secondary: RelaxedR1CSInstance, - ri_secondary: E2::Scalar, - l_u_secondary: R1CSInstance, - nifs_Uf_secondary: NIFS, - - l_ur_secondary: RelaxedR1CSInstance, - nifs_Un_secondary: NIFSRelaxed, - - r_U_primary: RelaxedR1CSInstance, - ri_primary: E1::Scalar, - l_ur_primary: RelaxedR1CSInstance, - nifs_Un_primary: NIFSRelaxed, - - wit_blind_r_Wn_primary: E1::Scalar, - err_blind_r_Wn_primary: E1::Scalar, - wit_blind_r_Wn_secondary: E2::Scalar, - err_blind_r_Wn_secondary: E2::Scalar, - - snark_primary: S1, - snark_secondary: S2, - - zn_primary: Vec, - zn_secondary: Vec, - - _p: PhantomData<(C1, C2)>, -} - -impl CompressedSNARK -where - E1: Engine::Scalar>, - E2: Engine::Scalar>, - C1: StepCircuit, - C2: StepCircuit, - S1: RelaxedR1CSSNARKTrait, - S2: RelaxedR1CSSNARKTrait, -{ - /// Creates prover and verifier keys for `CompressedSNARK` - pub fn setup( - pp: &PublicParams, - ) -> Result< - ( - ProverKey, - VerifierKey, - ), - NovaError, - > { - let (pk_primary, vk_primary) = S1::setup(&pp.ck_primary, &pp.r1cs_shape_primary)?; - let (pk_secondary, vk_secondary) = S2::setup(&pp.ck_secondary, &pp.r1cs_shape_secondary)?; - - let pk = ProverKey { - pk_primary, - pk_secondary, - _p: Default::default(), - }; - - let vk = VerifierKey { - F_arity_primary: pp.F_arity_primary, - F_arity_secondary: pp.F_arity_secondary, - ro_consts_primary: pp.ro_consts_primary.clone(), - ro_consts_secondary: pp.ro_consts_secondary.clone(), - pp_digest: pp.digest(), - vk_primary, - vk_secondary, - dk_primary: E1::CE::derand_key(&pp.ck_primary), - dk_secondary: E2::CE::derand_key(&pp.ck_secondary), - _p: Default::default(), - }; - - Ok((pk, vk)) - } - - /// Create a new `CompressedSNARK` (provides zero-knowledge) - pub fn prove( - pp: &PublicParams, - pk: &ProverKey, - recursive_snark: &RecursiveSNARK, - ) -> Result { - // prove three foldings - - // fold secondary U/W with secondary u/w to get Uf/Wf - let (nifs_Uf_secondary, (r_Uf_secondary, r_Wf_secondary)) = NIFS::prove( - &pp.ck_secondary, - &pp.ro_consts_secondary, - &scalar_as_base::(pp.digest()), - &pp.r1cs_shape_secondary, - &recursive_snark.r_U_secondary, - &recursive_snark.r_W_secondary, - &recursive_snark.l_u_secondary, - &recursive_snark.l_w_secondary, - )?; - - // fold Uf/Wf with random inst/wit to get U1/W1 - let (l_ur_secondary, l_wr_secondary) = pp - .r1cs_shape_secondary - .sample_random_instance_witness(&pp.ck_secondary)?; - - let (nifs_Un_secondary, (r_Un_secondary, r_Wn_secondary)) = NIFSRelaxed::prove( - &pp.ck_secondary, - &pp.ro_consts_secondary, - &scalar_as_base::(pp.digest()), - &pp.r1cs_shape_secondary, - &r_Uf_secondary, - &r_Wf_secondary, - &l_ur_secondary, - &l_wr_secondary, - )?; - - // fold primary U/W with random inst/wit to get U2/W2 - let (l_ur_primary, l_wr_primary) = pp - .r1cs_shape_primary - .sample_random_instance_witness(&pp.ck_primary)?; - - let (nifs_Un_primary, (r_Un_primary, r_Wn_primary)) = NIFSRelaxed::prove( - &pp.ck_primary, - &pp.ro_consts_primary, - &pp.digest(), - &pp.r1cs_shape_primary, - &recursive_snark.r_U_primary, - &recursive_snark.r_W_primary, - &l_ur_primary, - &l_wr_primary, - )?; - - // derandomize/unblind commitments - let (derandom_r_Wn_primary, wit_blind_r_Wn_primary, err_blind_r_Wn_primary) = - r_Wn_primary.derandomize(); - let derandom_r_Un_primary = r_Un_primary.derandomize( - &E1::CE::derand_key(&pp.ck_primary), - &wit_blind_r_Wn_primary, - &err_blind_r_Wn_primary, - ); - - let (derandom_r_Wn_secondary, wit_blind_r_Wn_secondary, err_blind_r_Wn_secondary) = - r_Wn_secondary.derandomize(); - let derandom_r_Un_secondary = r_Un_secondary.derandomize( - &E2::CE::derand_key(&pp.ck_secondary), - &wit_blind_r_Wn_secondary, - &err_blind_r_Wn_secondary, - ); - - // create SNARKs proving the knowledge of Wn primary/secondary - let (snark_primary, snark_secondary) = rayon::join( - || { - S1::prove( - &pp.ck_primary, - &pk.pk_primary, - &pp.r1cs_shape_primary, - &derandom_r_Un_primary, - &derandom_r_Wn_primary, - ) - }, - || { - S2::prove( - &pp.ck_secondary, - &pk.pk_secondary, - &pp.r1cs_shape_secondary, - &derandom_r_Un_secondary, - &derandom_r_Wn_secondary, - ) - }, - ); - - Ok(Self { - r_U_secondary: recursive_snark.r_U_secondary.clone(), - ri_secondary: recursive_snark.ri_secondary, - l_u_secondary: recursive_snark.l_u_secondary.clone(), - nifs_Uf_secondary: nifs_Uf_secondary.clone(), - - l_ur_secondary: l_ur_secondary.clone(), - nifs_Un_secondary: nifs_Un_secondary.clone(), - - r_U_primary: recursive_snark.r_U_primary.clone(), - ri_primary: recursive_snark.ri_primary, - l_ur_primary: l_ur_primary.clone(), - nifs_Un_primary: nifs_Un_primary.clone(), - - wit_blind_r_Wn_primary, - err_blind_r_Wn_primary, - wit_blind_r_Wn_secondary, - err_blind_r_Wn_secondary, - - snark_primary: snark_primary?, - snark_secondary: snark_secondary?, - - zn_primary: recursive_snark.zi_primary.clone(), - zn_secondary: recursive_snark.zi_secondary.clone(), - - _p: Default::default(), - }) - } - - /// Verify the correctness of the `CompressedSNARK` (provides zero-knowledge) - pub fn verify( - &self, - vk: &VerifierKey, - num_steps: usize, - z0_primary: &[E1::Scalar], - z0_secondary: &[E2::Scalar], - ) -> Result<(Vec, Vec), NovaError> { - // the number of steps cannot be zero - if num_steps == 0 { - return Err(NovaError::ProofVerifyError); - } - - // check if the (relaxed) R1CS instances have two public outputs - if self.l_u_secondary.X.len() != 2 - || self.r_U_primary.X.len() != 2 - || self.r_U_secondary.X.len() != 2 - || self.l_ur_primary.X.len() != 2 - || self.l_ur_secondary.X.len() != 2 - { - return Err(NovaError::ProofVerifyError); - } - - // check if the output hashes in R1CS instances point to the right running instances - let (hash_primary, hash_secondary) = { - let mut hasher = ::RO::new( - vk.ro_consts_secondary.clone(), - NUM_FE_WITHOUT_IO_FOR_CRHF + 2 * vk.F_arity_primary, - ); - hasher.absorb(vk.pp_digest); - hasher.absorb(E1::Scalar::from(num_steps as u64)); - for e in z0_primary { - hasher.absorb(*e); - } - for e in &self.zn_primary { - hasher.absorb(*e); - } - self.r_U_secondary.absorb_in_ro(&mut hasher); - hasher.absorb(self.ri_primary); - - let mut hasher2 = ::RO::new( - vk.ro_consts_primary.clone(), - NUM_FE_WITHOUT_IO_FOR_CRHF + 2 * vk.F_arity_secondary, - ); - hasher2.absorb(scalar_as_base::(vk.pp_digest)); - hasher2.absorb(E2::Scalar::from(num_steps as u64)); - for e in z0_secondary { - hasher2.absorb(*e); - } - for e in &self.zn_secondary { - hasher2.absorb(*e); - } - self.r_U_primary.absorb_in_ro(&mut hasher2); - hasher2.absorb(self.ri_secondary); - - ( - hasher.squeeze(NUM_HASH_BITS), - hasher2.squeeze(NUM_HASH_BITS), - ) - }; - - if hash_primary != self.l_u_secondary.X[0] - || hash_secondary != scalar_as_base::(self.l_u_secondary.X[1]) - { - return Err(NovaError::ProofVerifyError); - } - - // fold secondary U/W with secondary u/w to get Uf/Wf - let r_Uf_secondary = self.nifs_Uf_secondary.verify( - &vk.ro_consts_secondary, - &scalar_as_base::(vk.pp_digest), - &self.r_U_secondary, - &self.l_u_secondary, - )?; - - // fold Uf/Wf with random inst/wit to get U1/W1 - let r_Un_secondary = self.nifs_Un_secondary.verify( - &vk.ro_consts_secondary, - &scalar_as_base::(vk.pp_digest), - &r_Uf_secondary, - &self.l_ur_secondary, - )?; - - // fold primary U/W with random inst/wit to get U2/W2 - let r_Un_primary = self.nifs_Un_primary.verify( - &vk.ro_consts_primary, - &vk.pp_digest, - &self.r_U_primary, - &self.l_ur_primary, - )?; - - // derandomize/unblind commitments - let derandom_r_Un_primary = r_Un_primary.derandomize( - &vk.dk_primary, - &self.wit_blind_r_Wn_primary, - &self.err_blind_r_Wn_primary, - ); - let derandom_r_Un_secondary = r_Un_secondary.derandomize( - &vk.dk_secondary, - &self.wit_blind_r_Wn_secondary, - &self.err_blind_r_Wn_secondary, - ); - - // check the satisfiability of the folded instances using - // SNARKs proving the knowledge of their satisfying witnesses - let (res_primary, res_secondary) = rayon::join( - || { - self - .snark_primary - .verify(&vk.vk_primary, &derandom_r_Un_primary) - }, - || { - self - .snark_secondary - .verify(&vk.vk_secondary, &derandom_r_Un_secondary) - }, - ); - - res_primary?; - res_secondary?; +// private modules +mod constants; +mod digest; +mod r1cs; - Ok((self.zn_primary.clone(), self.zn_secondary.clone())) - } -} +use traits::{commitment::CommitmentEngineTrait, Engine}; +// some type aliases type CommitmentKey = <::CE as CommitmentEngineTrait>::CommitmentKey; type DerandKey = <::CE as CommitmentEngineTrait>::DerandKey; type Commitment = <::CE as CommitmentEngineTrait>::Commitment; type CE = ::CE; - -#[cfg(test)] -mod tests { - use super::*; - use crate::{ - provider::{ - pedersen::CommitmentKeyExtTrait, traits::DlogGroup, Bn256EngineIPA, Bn256EngineKZG, - GrumpkinEngine, PallasEngine, Secp256k1Engine, Secq256k1Engine, VestaEngine, - }, - traits::{circuit::TrivialCircuit, evaluation::EvaluationEngineTrait, snark::default_ck_hint}, - }; - use core::{fmt::Write, marker::PhantomData}; - use expect_test::{expect, Expect}; - use ff::PrimeField; - use frontend::{num::AllocatedNum, ConstraintSystem, SynthesisError}; - - type EE = provider::ipa_pc::EvaluationEngine; - type EEPrime = provider::hyperkzg::EvaluationEngine; - type S = spartan::snark::RelaxedR1CSSNARK; - type SPrime = spartan::ppsnark::RelaxedR1CSSNARK; - - #[derive(Clone, Debug, Default)] - struct CubicCircuit { - _p: PhantomData, - } - - impl StepCircuit for CubicCircuit { - fn arity(&self) -> usize { - 1 - } - - fn synthesize>( - &self, - cs: &mut CS, - z: &[AllocatedNum], - ) -> Result>, SynthesisError> { - // Consider a cubic equation: `x^3 + x + 5 = y`, where `x` and `y` are respectively the input and output. - let x = &z[0]; - let x_sq = x.square(cs.namespace(|| "x_sq"))?; - let x_cu = x_sq.mul(cs.namespace(|| "x_cu"), x)?; - let y = AllocatedNum::alloc(cs.namespace(|| "y"), || { - Ok(x_cu.get_value().unwrap() + x.get_value().unwrap() + F::from(5u64)) - })?; - - cs.enforce( - || "y = x^3 + x + 5", - |lc| { - lc + x_cu.get_variable() - + x.get_variable() - + CS::one() - + CS::one() - + CS::one() - + CS::one() - + CS::one() - }, - |lc| lc + CS::one(), - |lc| lc + y.get_variable(), - ); - - Ok(vec![y]) - } - } - - impl CubicCircuit { - fn output(&self, z: &[F]) -> Vec { - vec![z[0] * z[0] * z[0] + z[0] + F::from(5u64)] - } - } - - fn test_pp_digest_with(circuit1: &T1, circuit2: &T2, expected: &Expect) - where - E1: Engine::Scalar>, - E2: Engine::Scalar>, - E1::GE: DlogGroup, - E2::GE: DlogGroup, - T1: StepCircuit, - T2: StepCircuit, - // required to use the IPA in the initialization of the commitment key hints below - >::CommitmentKey: CommitmentKeyExtTrait, - >::CommitmentKey: CommitmentKeyExtTrait, - { - // this tests public parameters with a size specifically intended for a spark-compressed SNARK - let ck_hint1 = &*SPrime::>::ck_floor(); - let ck_hint2 = &*SPrime::>::ck_floor(); - let pp = PublicParams::::setup(circuit1, circuit2, ck_hint1, ck_hint2).unwrap(); - - let digest_str = pp - .digest() - .to_repr() - .as_ref() - .iter() - .fold(String::new(), |mut output, b| { - let _ = write!(output, "{b:02x}"); - output - }); - expected.assert_eq(&digest_str); - } - - #[test] - fn test_pp_digest() { - test_pp_digest_with::( - &TrivialCircuit::<_>::default(), - &TrivialCircuit::<_>::default(), - &expect!["b3da591d9a3c7dc2632e550e009f2b745d60cf919956cf02e9ca68e8e5e17603"], - ); - - test_pp_digest_with::( - &TrivialCircuit::<_>::default(), - &TrivialCircuit::<_>::default(), - &expect!["aaf1f0b723e281603838004327e73a02f3a2b5e2f2087e34b6f4f2c8f34e8401"], - ); - - test_pp_digest_with::( - &TrivialCircuit::<_>::default(), - &TrivialCircuit::<_>::default(), - &expect!["890b992d9c431625610659fe62b5c00859188e60802a5852cf5db0d10ca59403"], - ); - } - - fn test_ivc_trivial_with() - where - E1: Engine::Scalar>, - E2: Engine::Scalar>, - { - let test_circuit1 = TrivialCircuit::<::Scalar>::default(); - let test_circuit2 = TrivialCircuit::<::Scalar>::default(); - - // produce public parameters - let pp = PublicParams::< - E1, - E2, - TrivialCircuit<::Scalar>, - TrivialCircuit<::Scalar>, - >::setup( - &test_circuit1, - &test_circuit2, - &*default_ck_hint(), - &*default_ck_hint(), - ) - .unwrap(); - - let num_steps = 1; - - // produce a recursive SNARK - let mut recursive_snark = RecursiveSNARK::new( - &pp, - &test_circuit1, - &test_circuit2, - &[::Scalar::ZERO], - &[::Scalar::ZERO], - ) - .unwrap(); - - let res = recursive_snark.prove_step(&pp, &test_circuit1, &test_circuit2); - - assert!(res.is_ok()); - - // verify the recursive SNARK - let res = recursive_snark.verify( - &pp, - num_steps, - &[::Scalar::ZERO], - &[::Scalar::ZERO], - ); - assert!(res.is_ok()); - } - - #[test] - fn test_ivc_trivial() { - test_ivc_trivial_with::(); - test_ivc_trivial_with::(); - test_ivc_trivial_with::(); - } - - fn test_ivc_nontrivial_with() - where - E1: Engine::Scalar>, - E2: Engine::Scalar>, - { - let circuit_primary = TrivialCircuit::default(); - let circuit_secondary = CubicCircuit::default(); - - // produce public parameters - let pp = PublicParams::< - E1, - E2, - TrivialCircuit<::Scalar>, - CubicCircuit<::Scalar>, - >::setup( - &circuit_primary, - &circuit_secondary, - &*default_ck_hint(), - &*default_ck_hint(), - ) - .unwrap(); - - let num_steps = 3; - - // produce a recursive SNARK - let mut recursive_snark = RecursiveSNARK::< - E1, - E2, - TrivialCircuit<::Scalar>, - CubicCircuit<::Scalar>, - >::new( - &pp, - &circuit_primary, - &circuit_secondary, - &[::Scalar::ONE], - &[::Scalar::ZERO], - ) - .unwrap(); - - for i in 0..num_steps { - let res = recursive_snark.prove_step(&pp, &circuit_primary, &circuit_secondary); - assert!(res.is_ok()); - - // verify the recursive snark at each step of recursion - let res = recursive_snark.verify( - &pp, - i + 1, - &[::Scalar::ONE], - &[::Scalar::ZERO], - ); - assert!(res.is_ok()); - } - - // verify the recursive SNARK - let res = recursive_snark.verify( - &pp, - num_steps, - &[::Scalar::ONE], - &[::Scalar::ZERO], - ); - assert!(res.is_ok()); - - let (zn_primary, zn_secondary) = res.unwrap(); - - // sanity: check the claimed output with a direct computation of the same - assert_eq!(zn_primary, vec![::Scalar::ONE]); - let mut zn_secondary_direct = vec![::Scalar::ZERO]; - for _i in 0..num_steps { - zn_secondary_direct = circuit_secondary.clone().output(&zn_secondary_direct); - } - assert_eq!(zn_secondary, zn_secondary_direct); - assert_eq!(zn_secondary, vec![::Scalar::from(2460515u64)]); - } - - #[test] - fn test_ivc_nontrivial() { - test_ivc_nontrivial_with::(); - test_ivc_nontrivial_with::(); - test_ivc_nontrivial_with::(); - } - - fn test_ivc_nontrivial_with_compression_with() - where - E1: Engine::Scalar>, - E2: Engine::Scalar>, - EE1: EvaluationEngineTrait, - EE2: EvaluationEngineTrait, - { - let circuit_primary = TrivialCircuit::default(); - let circuit_secondary = CubicCircuit::default(); - - // produce public parameters - let pp = PublicParams::< - E1, - E2, - TrivialCircuit<::Scalar>, - CubicCircuit<::Scalar>, - >::setup( - &circuit_primary, - &circuit_secondary, - &*default_ck_hint(), - &*default_ck_hint(), - ) - .unwrap(); - - let num_steps = 3; - - // produce a recursive SNARK - let mut recursive_snark = RecursiveSNARK::< - E1, - E2, - TrivialCircuit<::Scalar>, - CubicCircuit<::Scalar>, - >::new( - &pp, - &circuit_primary, - &circuit_secondary, - &[::Scalar::ONE], - &[::Scalar::ZERO], - ) - .unwrap(); - - for _i in 0..num_steps { - let res = recursive_snark.prove_step(&pp, &circuit_primary, &circuit_secondary); - assert!(res.is_ok()); - } - - // verify the recursive SNARK - let res = recursive_snark.verify( - &pp, - num_steps, - &[::Scalar::ONE], - &[::Scalar::ZERO], - ); - assert!(res.is_ok()); - - let (zn_primary, zn_secondary) = res.unwrap(); - - // sanity: check the claimed output with a direct computation of the same - assert_eq!(zn_primary, vec![::Scalar::ONE]); - let mut zn_secondary_direct = vec![::Scalar::ZERO]; - for _i in 0..num_steps { - zn_secondary_direct = circuit_secondary.clone().output(&zn_secondary_direct); - } - assert_eq!(zn_secondary, zn_secondary_direct); - assert_eq!(zn_secondary, vec![::Scalar::from(2460515u64)]); - - // produce the prover and verifier keys for compressed snark - let (pk, vk) = CompressedSNARK::<_, _, _, _, S, S>::setup(&pp).unwrap(); - - // produce a compressed SNARK - let res = - CompressedSNARK::<_, _, _, _, S, S>::prove(&pp, &pk, &recursive_snark); - assert!(res.is_ok()); - let compressed_snark = res.unwrap(); - - // verify the compressed SNARK - let res = compressed_snark.verify( - &vk, - num_steps, - &[::Scalar::ONE], - &[::Scalar::ZERO], - ); - assert!(res.is_ok()); - } - - #[test] - fn test_ivc_nontrivial_with_compression() { - test_ivc_nontrivial_with_compression_with::, EE<_>>(); - test_ivc_nontrivial_with_compression_with::, EE<_>>( - ); - test_ivc_nontrivial_with_compression_with::, EE<_>>(); - - test_ivc_nontrivial_with_spark_compression_with::< - Bn256EngineKZG, - GrumpkinEngine, - provider::hyperkzg::EvaluationEngine<_>, - EE<_>, - >(); - } - - fn test_ivc_nontrivial_with_spark_compression_with() - where - E1: Engine::Scalar>, - E2: Engine::Scalar>, - EE1: EvaluationEngineTrait, - EE2: EvaluationEngineTrait, - { - let circuit_primary = TrivialCircuit::default(); - let circuit_secondary = CubicCircuit::default(); - - // produce public parameters, which we'll use with a spark-compressed SNARK - let pp = PublicParams::< - E1, - E2, - TrivialCircuit<::Scalar>, - CubicCircuit<::Scalar>, - >::setup( - &circuit_primary, - &circuit_secondary, - &*SPrime::::ck_floor(), - &*SPrime::::ck_floor(), - ) - .unwrap(); - - let num_steps = 3; - - // produce a recursive SNARK - let mut recursive_snark = RecursiveSNARK::< - E1, - E2, - TrivialCircuit<::Scalar>, - CubicCircuit<::Scalar>, - >::new( - &pp, - &circuit_primary, - &circuit_secondary, - &[::Scalar::ONE], - &[::Scalar::ZERO], - ) - .unwrap(); - - for _i in 0..num_steps { - let res = recursive_snark.prove_step(&pp, &circuit_primary, &circuit_secondary); - assert!(res.is_ok()); - } - - // verify the recursive SNARK - let res = recursive_snark.verify( - &pp, - num_steps, - &[::Scalar::ONE], - &[::Scalar::ZERO], - ); - assert!(res.is_ok()); - - let (zn_primary, zn_secondary) = res.unwrap(); - - // sanity: check the claimed output with a direct computation of the same - assert_eq!(zn_primary, vec![::Scalar::ONE]); - let mut zn_secondary_direct = vec![::Scalar::ZERO]; - for _i in 0..num_steps { - zn_secondary_direct = CubicCircuit::default().output(&zn_secondary_direct); - } - assert_eq!(zn_secondary, zn_secondary_direct); - assert_eq!(zn_secondary, vec![::Scalar::from(2460515u64)]); - - // run the compressed snark with Spark compiler - // produce the prover and verifier keys for compressed snark - let (pk, vk) = - CompressedSNARK::<_, _, _, _, SPrime, SPrime>::setup(&pp).unwrap(); - - // produce a compressed SNARK - let res = CompressedSNARK::<_, _, _, _, SPrime, SPrime>::prove( - &pp, - &pk, - &recursive_snark, - ); - assert!(res.is_ok()); - let compressed_snark = res.unwrap(); - - // verify the compressed SNARK - let res = compressed_snark.verify( - &vk, - num_steps, - &[::Scalar::ONE], - &[::Scalar::ZERO], - ); - assert!(res.is_ok()); - } - - #[test] - fn test_ivc_nontrivial_with_spark_compression() { - test_ivc_nontrivial_with_spark_compression_with::, EE<_>>(); - test_ivc_nontrivial_with_spark_compression_with::< - Bn256EngineKZG, - GrumpkinEngine, - EEPrime<_>, - EE<_>, - >(); - test_ivc_nontrivial_with_spark_compression_with::, EE<_>>( - ); - } - - fn test_ivc_nondet_with_compression_with() - where - E1: Engine::Scalar>, - E2: Engine::Scalar>, - EE1: EvaluationEngineTrait, - EE2: EvaluationEngineTrait, - { - // y is a non-deterministic advice representing the fifth root of the input at a step. - #[derive(Clone, Debug)] - struct FifthRootCheckingCircuit { - y: F, - } - - impl FifthRootCheckingCircuit { - fn new(num_steps: usize) -> (Vec, Vec) { - let mut powers = Vec::new(); - let rng = &mut rand::rngs::OsRng; - let mut seed = F::random(rng); - for _i in 0..num_steps + 1 { - seed *= seed.clone().square().square(); - - powers.push(Self { y: seed }); - } - - // reverse the powers to get roots - let roots = powers.into_iter().rev().collect::>(); - (vec![roots[0].y], roots[1..].to_vec()) - } - } - - impl StepCircuit for FifthRootCheckingCircuit - where - F: PrimeField, - { - fn arity(&self) -> usize { - 1 - } - - fn synthesize>( - &self, - cs: &mut CS, - z: &[AllocatedNum], - ) -> Result>, SynthesisError> { - let x = &z[0]; - - // we allocate a variable and set it to the provided non-deterministic advice. - let y = AllocatedNum::alloc_infallible(cs.namespace(|| "y"), || self.y); - - // We now check if y = x^{1/5} by checking if y^5 = x - let y_sq = y.square(cs.namespace(|| "y_sq"))?; - let y_quad = y_sq.square(cs.namespace(|| "y_quad"))?; - let y_pow_5 = y_quad.mul(cs.namespace(|| "y_fifth"), &y)?; - - cs.enforce( - || "y^5 = x", - |lc| lc + y_pow_5.get_variable(), - |lc| lc + CS::one(), - |lc| lc + x.get_variable(), - ); - - Ok(vec![y]) - } - } - - let circuit_primary = FifthRootCheckingCircuit { - y: ::Scalar::ZERO, - }; - - let circuit_secondary = TrivialCircuit::default(); - - // produce public parameters - let pp = PublicParams::< - E1, - E2, - FifthRootCheckingCircuit<::Scalar>, - TrivialCircuit<::Scalar>, - >::setup( - &circuit_primary, - &circuit_secondary, - &*default_ck_hint(), - &*default_ck_hint(), - ) - .unwrap(); - - let num_steps = 3; - - // produce non-deterministic advice - let (z0_primary, roots) = FifthRootCheckingCircuit::new(num_steps); - let z0_secondary = vec![::Scalar::ZERO]; - - // produce a recursive SNARK - let mut recursive_snark: RecursiveSNARK< - E1, - E2, - FifthRootCheckingCircuit<::Scalar>, - TrivialCircuit<::Scalar>, - > = RecursiveSNARK::< - E1, - E2, - FifthRootCheckingCircuit<::Scalar>, - TrivialCircuit<::Scalar>, - >::new( - &pp, - &roots[0], - &circuit_secondary, - &z0_primary, - &z0_secondary, - ) - .unwrap(); - - for circuit_primary in roots.iter().take(num_steps) { - let res = recursive_snark.prove_step(&pp, circuit_primary, &circuit_secondary); - assert!(res.is_ok()); - } - - // verify the recursive SNARK - let res = recursive_snark.verify(&pp, num_steps, &z0_primary, &z0_secondary); - assert!(res.is_ok()); - - // produce the prover and verifier keys for compressed snark - let (pk, vk) = CompressedSNARK::<_, _, _, _, S, S>::setup(&pp).unwrap(); - - // produce a compressed SNARK - let res = - CompressedSNARK::<_, _, _, _, S, S>::prove(&pp, &pk, &recursive_snark); - assert!(res.is_ok()); - let compressed_snark = res.unwrap(); - - // verify the compressed SNARK - let res = compressed_snark.verify(&vk, num_steps, &z0_primary, &z0_secondary); - assert!(res.is_ok()); - } - - #[test] - fn test_ivc_nondet_with_compression() { - test_ivc_nondet_with_compression_with::, EE<_>>(); - test_ivc_nondet_with_compression_with::, EE<_>>(); - test_ivc_nondet_with_compression_with::, EE<_>>(); - } - - fn test_ivc_base_with() - where - E1: Engine::Scalar>, - E2: Engine::Scalar>, - { - let test_circuit1 = TrivialCircuit::<::Scalar>::default(); - let test_circuit2 = CubicCircuit::<::Scalar>::default(); - - // produce public parameters - let pp = PublicParams::< - E1, - E2, - TrivialCircuit<::Scalar>, - CubicCircuit<::Scalar>, - >::setup( - &test_circuit1, - &test_circuit2, - &*default_ck_hint(), - &*default_ck_hint(), - ) - .unwrap(); - - let num_steps = 1; - - // produce a recursive SNARK - let mut recursive_snark = RecursiveSNARK::< - E1, - E2, - TrivialCircuit<::Scalar>, - CubicCircuit<::Scalar>, - >::new( - &pp, - &test_circuit1, - &test_circuit2, - &[::Scalar::ONE], - &[::Scalar::ZERO], - ) - .unwrap(); - - // produce a recursive SNARK - let res = recursive_snark.prove_step(&pp, &test_circuit1, &test_circuit2); - - assert!(res.is_ok()); - - // verify the recursive SNARK - let res = recursive_snark.verify( - &pp, - num_steps, - &[::Scalar::ONE], - &[::Scalar::ZERO], - ); - assert!(res.is_ok()); - - let (zn_primary, zn_secondary) = res.unwrap(); - - assert_eq!(zn_primary, vec![::Scalar::ONE]); - assert_eq!(zn_secondary, vec![::Scalar::from(5u64)]); - } - - #[test] - fn test_ivc_base() { - test_ivc_base_with::(); - test_ivc_base_with::(); - test_ivc_base_with::(); - } - - fn test_setup_with() - where - E1: Engine::Scalar>, - E2: Engine::Scalar>, - { - #[derive(Clone, Debug, Default)] - struct CircuitWithInputize { - _p: PhantomData, - } - - impl StepCircuit for CircuitWithInputize { - fn arity(&self) -> usize { - 1 - } - - fn synthesize>( - &self, - cs: &mut CS, - z: &[AllocatedNum], - ) -> Result>, SynthesisError> { - let x = &z[0]; - let y = x.square(cs.namespace(|| "x_sq"))?; - y.inputize(cs.namespace(|| "y"))?; // inputize y - Ok(vec![y]) - } - } - - // produce public parameters with trivial secondary - let circuit = CircuitWithInputize::<::Scalar>::default(); - let pp = - PublicParams::, TrivialCircuit>::setup( - &circuit, - &TrivialCircuit::default(), - &*default_ck_hint(), - &*default_ck_hint(), - ); - assert!(pp.is_err()); - assert_eq!(pp.err(), Some(NovaError::InvalidStepCircuitIO)); - - // produce public parameters with the trivial primary - let circuit = CircuitWithInputize::::default(); - let pp = - PublicParams::, CircuitWithInputize>::setup( - &TrivialCircuit::default(), - &circuit, - &*default_ck_hint(), - &*default_ck_hint(), - ); - assert!(pp.is_err()); - assert_eq!(pp.err(), Some(NovaError::InvalidStepCircuitIO)); - } - - #[test] - fn test_setup() { - test_setup_with::(); - } -} diff --git a/src/circuit.rs b/src/nova/circuit.rs similarity index 98% rename from src/circuit.rs rename to src/nova/circuit.rs index d9b2e36e..fb18ddb8 100644 --- a/src/circuit.rs +++ b/src/nova/circuit.rs @@ -5,7 +5,7 @@ //! Each circuit folds the last invocation of the other into the running instance use crate::{ - constants::{NUM_FE_WITHOUT_IO_FOR_CRHF, NUM_HASH_BITS}, + constants::NUM_HASH_BITS, frontend::{ num::AllocatedNum, AllocatedBit, Assignment, Boolean, ConstraintSystem, SynthesisError, }, @@ -228,13 +228,9 @@ impl<'a, E: Engine, SC: StepCircuit> NovaAugmentedCircuit<'a, E, SC> { r_i: &AllocatedNum, u: &AllocatedR1CSInstance, T: &AllocatedPoint, - arity: usize, ) -> Result<(AllocatedRelaxedR1CSInstance, AllocatedBit), SynthesisError> { // Check that u.x[0] = Hash(params, U, i, z0, zi) - let mut ro = E::ROCircuit::new( - self.ro_consts.clone(), - NUM_FE_WITHOUT_IO_FOR_CRHF + 2 * arity, - ); + let mut ro = E::ROCircuit::new(self.ro_consts.clone()); ro.absorb(params); ro.absorb(i); for e in z_0 { @@ -300,7 +296,6 @@ impl> NovaAugmentedCircuit<'_, E, SC> { &r_i, &u, &T, - arity, )?; // Either check_non_base_pass=true or we are in the base case @@ -353,7 +348,7 @@ impl> NovaAugmentedCircuit<'_, E, SC> { } // Compute the new hash H(params, Unew, i+1, z0, z_{i+1}) - let mut ro = E::ROCircuit::new(self.ro_consts, NUM_FE_WITHOUT_IO_FOR_CRHF + 2 * arity); + let mut ro = E::ROCircuit::new(self.ro_consts); ro.absorb(¶ms); ro.absorb(&i_new); for e in &z_0 { diff --git a/src/nova/mod.rs b/src/nova/mod.rs new file mode 100644 index 00000000..062a44fc --- /dev/null +++ b/src/nova/mod.rs @@ -0,0 +1,1673 @@ +//! This module implements Nova's IVC scheme including its folding scheme. + +use crate::{ + constants::{BN_LIMB_WIDTH, BN_N_LIMBS, NUM_HASH_BITS}, + digest::{DigestComputer, SimpleDigestible}, + errors::NovaError, + frontend::{ + r1cs::{NovaShape, NovaWitness}, + shape_cs::ShapeCS, + solver::SatisfyingAssignment, + ConstraintSystem, SynthesisError, + }, + gadgets::utils::scalar_as_base, + r1cs::{ + CommitmentKeyHint, R1CSInstance, R1CSShape, R1CSWitness, RelaxedR1CSInstance, + RelaxedR1CSWitness, + }, + traits::{ + circuit::StepCircuit, commitment::CommitmentEngineTrait, snark::RelaxedR1CSSNARKTrait, + AbsorbInROTrait, Engine, ROConstants, ROConstantsCircuit, ROTrait, + }, + CommitmentKey, DerandKey, +}; +use core::marker::PhantomData; +use ff::Field; +use once_cell::sync::OnceCell; +use rand_core::OsRng; +use serde::{Deserialize, Serialize}; + +mod circuit; +mod nifs; + +use circuit::{NovaAugmentedCircuit, NovaAugmentedCircuitInputs, NovaAugmentedCircuitParams}; +use nifs::{NIFSRelaxed, NIFS}; + +/// A type that holds public parameters of Nova +#[derive(Serialize, Deserialize)] +#[serde(bound = "")] +pub struct PublicParams +where + E1: Engine::Scalar>, + E2: Engine::Scalar>, + C1: StepCircuit, + C2: StepCircuit, +{ + F_arity_primary: usize, + F_arity_secondary: usize, + ro_consts_primary: ROConstants, + ro_consts_circuit_primary: ROConstantsCircuit, + ck_primary: CommitmentKey, + r1cs_shape_primary: R1CSShape, + ro_consts_secondary: ROConstants, + ro_consts_circuit_secondary: ROConstantsCircuit, + ck_secondary: CommitmentKey, + r1cs_shape_secondary: R1CSShape, + augmented_circuit_params_primary: NovaAugmentedCircuitParams, + augmented_circuit_params_secondary: NovaAugmentedCircuitParams, + #[serde(skip, default = "OnceCell::new")] + digest: OnceCell, + _p: PhantomData<(C1, C2)>, +} + +impl SimpleDigestible for PublicParams +where + E1: Engine::Scalar>, + E2: Engine::Scalar>, + C1: StepCircuit, + C2: StepCircuit, +{ +} + +impl PublicParams +where + E1: Engine::Scalar>, + E2: Engine::Scalar>, + C1: StepCircuit, + C2: StepCircuit, +{ + /// Creates a new `PublicParams` for a pair of circuits `C1` and `C2`. + /// + /// # Note + /// + /// Public parameters set up a number of bases for the homomorphic commitment scheme of Nova. + /// + /// Some final compressing SNARKs, like variants of Spartan, use computation commitments that require + /// larger sizes for these parameters. These SNARKs provide a hint for these values by + /// implementing `RelaxedR1CSSNARKTrait::ck_floor()`, which can be passed to this function. + /// + /// If you're not using such a SNARK, pass `nova_snark::traits::snark::default_ck_hint()` instead. + /// + /// # Arguments + /// + /// * `c_primary`: The primary circuit of type `C1`. + /// * `c_secondary`: The secondary circuit of type `C2`. + /// * `ck_hint1`: A `CommitmentKeyHint` for `G1`, which is a function that provides a hint + /// for the number of generators required in the commitment scheme for the primary circuit. + /// * `ck_hint2`: A `CommitmentKeyHint` for `G2`, similar to `ck_hint1`, but for the secondary circuit. + /// + /// # Example + /// + /// ```rust + /// # use nova_snark::spartan::ppsnark::RelaxedR1CSSNARK; + /// # use nova_snark::provider::ipa_pc::EvaluationEngine; + /// # use nova_snark::provider::{PallasEngine, VestaEngine}; + /// # use nova_snark::traits::{circuit::TrivialCircuit, Engine, snark::RelaxedR1CSSNARKTrait}; + /// # use nova_snark::nova::PublicParams; + /// + /// type E1 = PallasEngine; + /// type E2 = VestaEngine; + /// type EE = EvaluationEngine; + /// type SPrime = RelaxedR1CSSNARK>; + /// + /// let circuit1 = TrivialCircuit::<::Scalar>::default(); + /// let circuit2 = TrivialCircuit::<::Scalar>::default(); + /// // Only relevant for a SNARK using computational commitments, pass &(|_| 0) + /// // or &*nova_snark::traits::snark::default_ck_hint() otherwise. + /// let ck_hint1 = &*SPrime::::ck_floor(); + /// let ck_hint2 = &*SPrime::::ck_floor(); + /// + /// let pp = PublicParams::setup(&circuit1, &circuit2, ck_hint1, ck_hint2); + /// ``` + pub fn setup( + c_primary: &C1, + c_secondary: &C2, + ck_hint1: &CommitmentKeyHint, + ck_hint2: &CommitmentKeyHint, + ) -> Result { + let augmented_circuit_params_primary = + NovaAugmentedCircuitParams::new(BN_LIMB_WIDTH, BN_N_LIMBS, true); + let augmented_circuit_params_secondary = + NovaAugmentedCircuitParams::new(BN_LIMB_WIDTH, BN_N_LIMBS, false); + + let ro_consts_primary: ROConstants = ROConstants::::default(); + let ro_consts_secondary: ROConstants = ROConstants::::default(); + + let F_arity_primary = c_primary.arity(); + let F_arity_secondary = c_secondary.arity(); + + // ro_consts_circuit_primary are parameterized by E2 because the type alias uses E2::Base = E1::Scalar + let ro_consts_circuit_primary: ROConstantsCircuit = ROConstantsCircuit::::default(); + let ro_consts_circuit_secondary: ROConstantsCircuit = ROConstantsCircuit::::default(); + + // Initialize ck for the primary + let circuit_primary: NovaAugmentedCircuit<'_, E2, C1> = NovaAugmentedCircuit::new( + &augmented_circuit_params_primary, + None, + c_primary, + ro_consts_circuit_primary.clone(), + ); + let mut cs: ShapeCS = ShapeCS::new(); + let _ = circuit_primary.synthesize(&mut cs); + let (r1cs_shape_primary, ck_primary) = cs.r1cs_shape(ck_hint1); + + // Initialize ck for the secondary + let circuit_secondary: NovaAugmentedCircuit<'_, E1, C2> = NovaAugmentedCircuit::new( + &augmented_circuit_params_secondary, + None, + c_secondary, + ro_consts_circuit_secondary.clone(), + ); + let mut cs: ShapeCS = ShapeCS::new(); + let _ = circuit_secondary.synthesize(&mut cs); + let (r1cs_shape_secondary, ck_secondary) = cs.r1cs_shape(ck_hint2); + + if r1cs_shape_primary.num_io != 2 || r1cs_shape_secondary.num_io != 2 { + return Err(NovaError::InvalidStepCircuitIO); + } + + let pp = PublicParams { + F_arity_primary, + F_arity_secondary, + ro_consts_primary, + ro_consts_circuit_primary, + ck_primary, + r1cs_shape_primary, + ro_consts_secondary, + ro_consts_circuit_secondary, + ck_secondary, + r1cs_shape_secondary, + augmented_circuit_params_primary, + augmented_circuit_params_secondary, + digest: OnceCell::new(), + _p: Default::default(), + }; + + // call pp.digest() so the digest is computed here rather than in RecursiveSNARK methods + let _ = pp.digest(); + + Ok(pp) + } + + /// Retrieve the digest of the public parameters. + pub fn digest(&self) -> E1::Scalar { + self + .digest + .get_or_try_init(|| DigestComputer::new(self).digest()) + .cloned() + .expect("Failure in retrieving digest") + } + + /// Returns the number of constraints in the primary and secondary circuits + pub const fn num_constraints(&self) -> (usize, usize) { + ( + self.r1cs_shape_primary.num_cons, + self.r1cs_shape_secondary.num_cons, + ) + } + + /// Returns the number of variables in the primary and secondary circuits + pub const fn num_variables(&self) -> (usize, usize) { + ( + self.r1cs_shape_primary.num_vars, + self.r1cs_shape_secondary.num_vars, + ) + } +} + +/// A SNARK that proves the correct execution of an incremental computation +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(bound = "")] +pub struct RecursiveSNARK +where + E1: Engine::Scalar>, + E2: Engine::Scalar>, + C1: StepCircuit, + C2: StepCircuit, +{ + z0_primary: Vec, + z0_secondary: Vec, + r_W_primary: RelaxedR1CSWitness, + r_U_primary: RelaxedR1CSInstance, + ri_primary: E1::Scalar, + r_W_secondary: RelaxedR1CSWitness, + r_U_secondary: RelaxedR1CSInstance, + ri_secondary: E2::Scalar, + l_w_secondary: R1CSWitness, + l_u_secondary: R1CSInstance, + i: usize, + zi_primary: Vec, + zi_secondary: Vec, + _p: PhantomData<(C1, C2)>, +} + +impl RecursiveSNARK +where + E1: Engine::Scalar>, + E2: Engine::Scalar>, + C1: StepCircuit, + C2: StepCircuit, +{ + /// Create new instance of recursive SNARK + pub fn new( + pp: &PublicParams, + c_primary: &C1, + c_secondary: &C2, + z0_primary: &[E1::Scalar], + z0_secondary: &[E2::Scalar], + ) -> Result { + if z0_primary.len() != pp.F_arity_primary || z0_secondary.len() != pp.F_arity_secondary { + return Err(NovaError::InvalidInitialInputLength); + } + + let ri_primary = E1::Scalar::random(&mut OsRng); + let ri_secondary = E2::Scalar::random(&mut OsRng); + + // base case for the primary + let mut cs_primary = SatisfyingAssignment::::new(); + let inputs_primary: NovaAugmentedCircuitInputs = NovaAugmentedCircuitInputs::new( + scalar_as_base::(pp.digest()), + E1::Scalar::ZERO, + z0_primary.to_vec(), + None, + None, + None, + ri_primary, // "r next" + None, + None, + ); + + let circuit_primary: NovaAugmentedCircuit<'_, E2, C1> = NovaAugmentedCircuit::new( + &pp.augmented_circuit_params_primary, + Some(inputs_primary), + c_primary, + pp.ro_consts_circuit_primary.clone(), + ); + let zi_primary = circuit_primary.synthesize(&mut cs_primary)?; + let (u_primary, w_primary) = + cs_primary.r1cs_instance_and_witness(&pp.r1cs_shape_primary, &pp.ck_primary)?; + + // base case for the secondary + let mut cs_secondary = SatisfyingAssignment::::new(); + let inputs_secondary: NovaAugmentedCircuitInputs = NovaAugmentedCircuitInputs::new( + pp.digest(), + E2::Scalar::ZERO, + z0_secondary.to_vec(), + None, + None, + None, + ri_secondary, // "r next" + Some(u_primary.clone()), + None, + ); + let circuit_secondary: NovaAugmentedCircuit<'_, E1, C2> = NovaAugmentedCircuit::new( + &pp.augmented_circuit_params_secondary, + Some(inputs_secondary), + c_secondary, + pp.ro_consts_circuit_secondary.clone(), + ); + let zi_secondary = circuit_secondary.synthesize(&mut cs_secondary)?; + let (u_secondary, w_secondary) = + cs_secondary.r1cs_instance_and_witness(&pp.r1cs_shape_secondary, &pp.ck_secondary)?; + + // IVC proof for the primary circuit + let l_w_primary = w_primary; + let l_u_primary = u_primary; + let r_W_primary = RelaxedR1CSWitness::from_r1cs_witness(&pp.r1cs_shape_primary, &l_w_primary); + let r_U_primary = + RelaxedR1CSInstance::from_r1cs_instance(&pp.ck_primary, &pp.r1cs_shape_primary, &l_u_primary); + + // IVC proof for the secondary circuit + let l_w_secondary = w_secondary; + let l_u_secondary = u_secondary; + let r_W_secondary = RelaxedR1CSWitness::::default(&pp.r1cs_shape_secondary); + let r_U_secondary = + RelaxedR1CSInstance::::default(&pp.ck_secondary, &pp.r1cs_shape_secondary); + + assert!( + !(zi_primary.len() != pp.F_arity_primary || zi_secondary.len() != pp.F_arity_secondary), + "Invalid step length" + ); + + let zi_primary = zi_primary + .iter() + .map(|v| v.get_value().ok_or(SynthesisError::AssignmentMissing)) + .collect::::Scalar>, _>>()?; + + let zi_secondary = zi_secondary + .iter() + .map(|v| v.get_value().ok_or(SynthesisError::AssignmentMissing)) + .collect::::Scalar>, _>>()?; + + Ok(Self { + z0_primary: z0_primary.to_vec(), + z0_secondary: z0_secondary.to_vec(), + r_W_primary, + r_U_primary, + ri_primary, + r_W_secondary, + r_U_secondary, + ri_secondary, + l_w_secondary, + l_u_secondary, + i: 0, + zi_primary, + zi_secondary, + _p: Default::default(), + }) + } + + /// Create a new `RecursiveSNARK` (or updates the provided `RecursiveSNARK`) + /// by executing a step of the incremental computation + pub fn prove_step( + &mut self, + pp: &PublicParams, + c_primary: &C1, + c_secondary: &C2, + ) -> Result<(), NovaError> { + // first step was already done in the constructor + if self.i == 0 { + self.i = 1; + return Ok(()); + } + + // fold the secondary circuit's instance + let (nifs_secondary, (r_U_secondary, r_W_secondary)) = NIFS::prove( + &pp.ck_secondary, + &pp.ro_consts_secondary, + &scalar_as_base::(pp.digest()), + &pp.r1cs_shape_secondary, + &self.r_U_secondary, + &self.r_W_secondary, + &self.l_u_secondary, + &self.l_w_secondary, + )?; + + let r_next_primary = E1::Scalar::random(&mut OsRng); + + let mut cs_primary = SatisfyingAssignment::::new(); + let inputs_primary: NovaAugmentedCircuitInputs = NovaAugmentedCircuitInputs::new( + scalar_as_base::(pp.digest()), + E1::Scalar::from(self.i as u64), + self.z0_primary.to_vec(), + Some(self.zi_primary.clone()), + Some(self.r_U_secondary.clone()), + Some(self.ri_primary), + r_next_primary, + Some(self.l_u_secondary.clone()), + Some(nifs_secondary.comm_T), + ); + + let circuit_primary: NovaAugmentedCircuit<'_, E2, C1> = NovaAugmentedCircuit::new( + &pp.augmented_circuit_params_primary, + Some(inputs_primary), + c_primary, + pp.ro_consts_circuit_primary.clone(), + ); + let zi_primary = circuit_primary.synthesize(&mut cs_primary)?; + + let (l_u_primary, l_w_primary) = + cs_primary.r1cs_instance_and_witness(&pp.r1cs_shape_primary, &pp.ck_primary)?; + + // fold the primary circuit's instance + let (nifs_primary, (r_U_primary, r_W_primary)) = NIFS::prove( + &pp.ck_primary, + &pp.ro_consts_primary, + &pp.digest(), + &pp.r1cs_shape_primary, + &self.r_U_primary, + &self.r_W_primary, + &l_u_primary, + &l_w_primary, + )?; + + let r_next_secondary = E2::Scalar::random(&mut OsRng); + + let mut cs_secondary = SatisfyingAssignment::::new(); + let inputs_secondary: NovaAugmentedCircuitInputs = NovaAugmentedCircuitInputs::new( + pp.digest(), + E2::Scalar::from(self.i as u64), + self.z0_secondary.to_vec(), + Some(self.zi_secondary.clone()), + Some(self.r_U_primary.clone()), + Some(self.ri_secondary), + r_next_secondary, + Some(l_u_primary), + Some(nifs_primary.comm_T), + ); + + let circuit_secondary: NovaAugmentedCircuit<'_, E1, C2> = NovaAugmentedCircuit::new( + &pp.augmented_circuit_params_secondary, + Some(inputs_secondary), + c_secondary, + pp.ro_consts_circuit_secondary.clone(), + ); + let zi_secondary = circuit_secondary.synthesize(&mut cs_secondary)?; + + let (l_u_secondary, l_w_secondary) = cs_secondary + .r1cs_instance_and_witness(&pp.r1cs_shape_secondary, &pp.ck_secondary) + .map_err(|_e| NovaError::UnSat)?; + + // update the running instances and witnesses + self.zi_primary = zi_primary + .iter() + .map(|v| v.get_value().ok_or(SynthesisError::AssignmentMissing)) + .collect::::Scalar>, _>>()?; + self.zi_secondary = zi_secondary + .iter() + .map(|v| v.get_value().ok_or(SynthesisError::AssignmentMissing)) + .collect::::Scalar>, _>>()?; + + self.l_u_secondary = l_u_secondary; + self.l_w_secondary = l_w_secondary; + + self.r_U_primary = r_U_primary; + self.r_W_primary = r_W_primary; + + self.i += 1; + + self.r_U_secondary = r_U_secondary; + self.r_W_secondary = r_W_secondary; + + self.ri_primary = r_next_primary; + self.ri_secondary = r_next_secondary; + + Ok(()) + } + + /// Verify the correctness of the `RecursiveSNARK` + pub fn verify( + &self, + pp: &PublicParams, + num_steps: usize, + z0_primary: &[E1::Scalar], + z0_secondary: &[E2::Scalar], + ) -> Result<(Vec, Vec), NovaError> { + // number of steps cannot be zero + let is_num_steps_zero = num_steps == 0; + + // check if the provided proof has executed num_steps + let is_num_steps_not_match = self.i != num_steps; + + // check if the initial inputs match + let is_inputs_not_match = self.z0_primary != z0_primary || self.z0_secondary != z0_secondary; + + // check if the (relaxed) R1CS instances have two public outputs + let is_instance_has_two_outputs = self.l_u_secondary.X.len() != 2 + || self.r_U_primary.X.len() != 2 + || self.r_U_secondary.X.len() != 2; + + if is_num_steps_zero + || is_num_steps_not_match + || is_inputs_not_match + || is_instance_has_two_outputs + { + return Err(NovaError::ProofVerifyError); + } + + // check if the output hashes in R1CS instances point to the right running instances + let (hash_primary, hash_secondary) = { + let mut hasher = ::RO::new(pp.ro_consts_secondary.clone()); + hasher.absorb(pp.digest()); + hasher.absorb(E1::Scalar::from(num_steps as u64)); + for e in z0_primary { + hasher.absorb(*e); + } + for e in &self.zi_primary { + hasher.absorb(*e); + } + self.r_U_secondary.absorb_in_ro(&mut hasher); + hasher.absorb(self.ri_primary); + + let mut hasher2 = ::RO::new(pp.ro_consts_primary.clone()); + hasher2.absorb(scalar_as_base::(pp.digest())); + hasher2.absorb(E2::Scalar::from(num_steps as u64)); + for e in z0_secondary { + hasher2.absorb(*e); + } + for e in &self.zi_secondary { + hasher2.absorb(*e); + } + self.r_U_primary.absorb_in_ro(&mut hasher2); + hasher2.absorb(self.ri_secondary); + + ( + hasher.squeeze(NUM_HASH_BITS), + hasher2.squeeze(NUM_HASH_BITS), + ) + }; + + if hash_primary != self.l_u_secondary.X[0] + || hash_secondary != scalar_as_base::(self.l_u_secondary.X[1]) + { + return Err(NovaError::ProofVerifyError); + } + + // check the satisfiability of the provided instances + let (res_r_primary, (res_r_secondary, res_l_secondary)) = rayon::join( + || { + pp.r1cs_shape_primary + .is_sat_relaxed(&pp.ck_primary, &self.r_U_primary, &self.r_W_primary) + }, + || { + rayon::join( + || { + pp.r1cs_shape_secondary.is_sat_relaxed( + &pp.ck_secondary, + &self.r_U_secondary, + &self.r_W_secondary, + ) + }, + || { + pp.r1cs_shape_secondary.is_sat( + &pp.ck_secondary, + &self.l_u_secondary, + &self.l_w_secondary, + ) + }, + ) + }, + ); + + // check the returned res objects + res_r_primary?; + res_r_secondary?; + res_l_secondary?; + + Ok((self.zi_primary.clone(), self.zi_secondary.clone())) + } + + /// Get the outputs after the last step of computation. + pub fn outputs(&self) -> (&[E1::Scalar], &[E2::Scalar]) { + (&self.zi_primary, &self.zi_secondary) + } + + /// The number of steps which have been executed thus far. + pub fn num_steps(&self) -> usize { + self.i + } +} + +/// A type that holds the prover key for `CompressedSNARK` +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(bound = "")] +pub struct ProverKey +where + E1: Engine::Scalar>, + E2: Engine::Scalar>, + C1: StepCircuit, + C2: StepCircuit, + S1: RelaxedR1CSSNARKTrait, + S2: RelaxedR1CSSNARKTrait, +{ + pk_primary: S1::ProverKey, + pk_secondary: S2::ProverKey, + _p: PhantomData<(C1, C2)>, +} + +/// A type that holds the verifier key for `CompressedSNARK` +#[derive(Clone, Serialize, Deserialize)] +#[serde(bound = "")] +pub struct VerifierKey +where + E1: Engine::Scalar>, + E2: Engine::Scalar>, + C1: StepCircuit, + C2: StepCircuit, + S1: RelaxedR1CSSNARKTrait, + S2: RelaxedR1CSSNARKTrait, +{ + F_arity_primary: usize, + F_arity_secondary: usize, + ro_consts_primary: ROConstants, + ro_consts_secondary: ROConstants, + pp_digest: E1::Scalar, + vk_primary: S1::VerifierKey, + vk_secondary: S2::VerifierKey, + dk_primary: DerandKey, + dk_secondary: DerandKey, + _p: PhantomData<(C1, C2)>, +} + +/// A SNARK that proves the knowledge of a valid `RecursiveSNARK` +#[derive(Clone, Serialize, Deserialize)] +#[serde(bound = "")] +pub struct CompressedSNARK +where + E1: Engine::Scalar>, + E2: Engine::Scalar>, + C1: StepCircuit, + C2: StepCircuit, + S1: RelaxedR1CSSNARKTrait, + S2: RelaxedR1CSSNARKTrait, +{ + r_U_secondary: RelaxedR1CSInstance, + ri_secondary: E2::Scalar, + l_u_secondary: R1CSInstance, + nifs_Uf_secondary: NIFS, + + l_ur_secondary: RelaxedR1CSInstance, + nifs_Un_secondary: NIFSRelaxed, + + r_U_primary: RelaxedR1CSInstance, + ri_primary: E1::Scalar, + l_ur_primary: RelaxedR1CSInstance, + nifs_Un_primary: NIFSRelaxed, + + wit_blind_r_Wn_primary: E1::Scalar, + err_blind_r_Wn_primary: E1::Scalar, + wit_blind_r_Wn_secondary: E2::Scalar, + err_blind_r_Wn_secondary: E2::Scalar, + + snark_primary: S1, + snark_secondary: S2, + + zn_primary: Vec, + zn_secondary: Vec, + + _p: PhantomData<(C1, C2)>, +} + +impl CompressedSNARK +where + E1: Engine::Scalar>, + E2: Engine::Scalar>, + C1: StepCircuit, + C2: StepCircuit, + S1: RelaxedR1CSSNARKTrait, + S2: RelaxedR1CSSNARKTrait, +{ + /// Creates prover and verifier keys for `CompressedSNARK` + pub fn setup( + pp: &PublicParams, + ) -> Result< + ( + ProverKey, + VerifierKey, + ), + NovaError, + > { + let (pk_primary, vk_primary) = S1::setup(&pp.ck_primary, &pp.r1cs_shape_primary)?; + let (pk_secondary, vk_secondary) = S2::setup(&pp.ck_secondary, &pp.r1cs_shape_secondary)?; + + let pk = ProverKey { + pk_primary, + pk_secondary, + _p: Default::default(), + }; + + let vk = VerifierKey { + F_arity_primary: pp.F_arity_primary, + F_arity_secondary: pp.F_arity_secondary, + ro_consts_primary: pp.ro_consts_primary.clone(), + ro_consts_secondary: pp.ro_consts_secondary.clone(), + pp_digest: pp.digest(), + vk_primary, + vk_secondary, + dk_primary: E1::CE::derand_key(&pp.ck_primary), + dk_secondary: E2::CE::derand_key(&pp.ck_secondary), + _p: Default::default(), + }; + + Ok((pk, vk)) + } + + /// Create a new `CompressedSNARK` (provides zero-knowledge) + pub fn prove( + pp: &PublicParams, + pk: &ProverKey, + recursive_snark: &RecursiveSNARK, + ) -> Result { + // prove three foldings + + // fold secondary U/W with secondary u/w to get Uf/Wf + let (nifs_Uf_secondary, (r_Uf_secondary, r_Wf_secondary)) = NIFS::prove( + &pp.ck_secondary, + &pp.ro_consts_secondary, + &scalar_as_base::(pp.digest()), + &pp.r1cs_shape_secondary, + &recursive_snark.r_U_secondary, + &recursive_snark.r_W_secondary, + &recursive_snark.l_u_secondary, + &recursive_snark.l_w_secondary, + )?; + + // fold Uf/Wf with random inst/wit to get U1/W1 + let (l_ur_secondary, l_wr_secondary) = pp + .r1cs_shape_secondary + .sample_random_instance_witness(&pp.ck_secondary)?; + + let (nifs_Un_secondary, (r_Un_secondary, r_Wn_secondary)) = NIFSRelaxed::prove( + &pp.ck_secondary, + &pp.ro_consts_secondary, + &scalar_as_base::(pp.digest()), + &pp.r1cs_shape_secondary, + &r_Uf_secondary, + &r_Wf_secondary, + &l_ur_secondary, + &l_wr_secondary, + )?; + + // fold primary U/W with random inst/wit to get U2/W2 + let (l_ur_primary, l_wr_primary) = pp + .r1cs_shape_primary + .sample_random_instance_witness(&pp.ck_primary)?; + + let (nifs_Un_primary, (r_Un_primary, r_Wn_primary)) = NIFSRelaxed::prove( + &pp.ck_primary, + &pp.ro_consts_primary, + &pp.digest(), + &pp.r1cs_shape_primary, + &recursive_snark.r_U_primary, + &recursive_snark.r_W_primary, + &l_ur_primary, + &l_wr_primary, + )?; + + // derandomize/unblind commitments + let (derandom_r_Wn_primary, wit_blind_r_Wn_primary, err_blind_r_Wn_primary) = + r_Wn_primary.derandomize(); + let derandom_r_Un_primary = r_Un_primary.derandomize( + &E1::CE::derand_key(&pp.ck_primary), + &wit_blind_r_Wn_primary, + &err_blind_r_Wn_primary, + ); + + let (derandom_r_Wn_secondary, wit_blind_r_Wn_secondary, err_blind_r_Wn_secondary) = + r_Wn_secondary.derandomize(); + let derandom_r_Un_secondary = r_Un_secondary.derandomize( + &E2::CE::derand_key(&pp.ck_secondary), + &wit_blind_r_Wn_secondary, + &err_blind_r_Wn_secondary, + ); + + // create SNARKs proving the knowledge of Wn primary/secondary + let (snark_primary, snark_secondary) = rayon::join( + || { + S1::prove( + &pp.ck_primary, + &pk.pk_primary, + &pp.r1cs_shape_primary, + &derandom_r_Un_primary, + &derandom_r_Wn_primary, + ) + }, + || { + S2::prove( + &pp.ck_secondary, + &pk.pk_secondary, + &pp.r1cs_shape_secondary, + &derandom_r_Un_secondary, + &derandom_r_Wn_secondary, + ) + }, + ); + + Ok(Self { + r_U_secondary: recursive_snark.r_U_secondary.clone(), + ri_secondary: recursive_snark.ri_secondary, + l_u_secondary: recursive_snark.l_u_secondary.clone(), + nifs_Uf_secondary: nifs_Uf_secondary.clone(), + + l_ur_secondary: l_ur_secondary.clone(), + nifs_Un_secondary: nifs_Un_secondary.clone(), + + r_U_primary: recursive_snark.r_U_primary.clone(), + ri_primary: recursive_snark.ri_primary, + l_ur_primary: l_ur_primary.clone(), + nifs_Un_primary: nifs_Un_primary.clone(), + + wit_blind_r_Wn_primary, + err_blind_r_Wn_primary, + wit_blind_r_Wn_secondary, + err_blind_r_Wn_secondary, + + snark_primary: snark_primary?, + snark_secondary: snark_secondary?, + + zn_primary: recursive_snark.zi_primary.clone(), + zn_secondary: recursive_snark.zi_secondary.clone(), + + _p: Default::default(), + }) + } + + /// Verify the correctness of the `CompressedSNARK` (provides zero-knowledge) + pub fn verify( + &self, + vk: &VerifierKey, + num_steps: usize, + z0_primary: &[E1::Scalar], + z0_secondary: &[E2::Scalar], + ) -> Result<(Vec, Vec), NovaError> { + // the number of steps cannot be zero + if num_steps == 0 { + return Err(NovaError::ProofVerifyError); + } + + // check if the (relaxed) R1CS instances have two public outputs + if self.l_u_secondary.X.len() != 2 + || self.r_U_primary.X.len() != 2 + || self.r_U_secondary.X.len() != 2 + || self.l_ur_primary.X.len() != 2 + || self.l_ur_secondary.X.len() != 2 + { + return Err(NovaError::ProofVerifyError); + } + + // check if the output hashes in R1CS instances point to the right running instances + let (hash_primary, hash_secondary) = { + let mut hasher = ::RO::new(vk.ro_consts_secondary.clone()); + hasher.absorb(vk.pp_digest); + hasher.absorb(E1::Scalar::from(num_steps as u64)); + for e in z0_primary { + hasher.absorb(*e); + } + for e in &self.zn_primary { + hasher.absorb(*e); + } + self.r_U_secondary.absorb_in_ro(&mut hasher); + hasher.absorb(self.ri_primary); + + let mut hasher2 = ::RO::new(vk.ro_consts_primary.clone()); + hasher2.absorb(scalar_as_base::(vk.pp_digest)); + hasher2.absorb(E2::Scalar::from(num_steps as u64)); + for e in z0_secondary { + hasher2.absorb(*e); + } + for e in &self.zn_secondary { + hasher2.absorb(*e); + } + self.r_U_primary.absorb_in_ro(&mut hasher2); + hasher2.absorb(self.ri_secondary); + + ( + hasher.squeeze(NUM_HASH_BITS), + hasher2.squeeze(NUM_HASH_BITS), + ) + }; + + if hash_primary != self.l_u_secondary.X[0] + || hash_secondary != scalar_as_base::(self.l_u_secondary.X[1]) + { + return Err(NovaError::ProofVerifyError); + } + + // fold secondary U/W with secondary u/w to get Uf/Wf + let r_Uf_secondary = self.nifs_Uf_secondary.verify( + &vk.ro_consts_secondary, + &scalar_as_base::(vk.pp_digest), + &self.r_U_secondary, + &self.l_u_secondary, + )?; + + // fold Uf/Wf with random inst/wit to get U1/W1 + let r_Un_secondary = self.nifs_Un_secondary.verify( + &vk.ro_consts_secondary, + &scalar_as_base::(vk.pp_digest), + &r_Uf_secondary, + &self.l_ur_secondary, + )?; + + // fold primary U/W with random inst/wit to get U2/W2 + let r_Un_primary = self.nifs_Un_primary.verify( + &vk.ro_consts_primary, + &vk.pp_digest, + &self.r_U_primary, + &self.l_ur_primary, + )?; + + // derandomize/unblind commitments + let derandom_r_Un_primary = r_Un_primary.derandomize( + &vk.dk_primary, + &self.wit_blind_r_Wn_primary, + &self.err_blind_r_Wn_primary, + ); + let derandom_r_Un_secondary = r_Un_secondary.derandomize( + &vk.dk_secondary, + &self.wit_blind_r_Wn_secondary, + &self.err_blind_r_Wn_secondary, + ); + + // check the satisfiability of the folded instances using + // SNARKs proving the knowledge of their satisfying witnesses + let (res_primary, res_secondary) = rayon::join( + || { + self + .snark_primary + .verify(&vk.vk_primary, &derandom_r_Un_primary) + }, + || { + self + .snark_secondary + .verify(&vk.vk_secondary, &derandom_r_Un_secondary) + }, + ); + + res_primary?; + res_secondary?; + + Ok((self.zn_primary.clone(), self.zn_secondary.clone())) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + frontend::{num::AllocatedNum, ConstraintSystem, SynthesisError}, + provider::{ + pedersen::CommitmentKeyExtTrait, traits::DlogGroup, Bn256EngineIPA, Bn256EngineKZG, + GrumpkinEngine, PallasEngine, Secp256k1Engine, Secq256k1Engine, VestaEngine, + }, + traits::{circuit::TrivialCircuit, evaluation::EvaluationEngineTrait, snark::default_ck_hint}, + }; + use core::{fmt::Write, marker::PhantomData}; + use expect_test::{expect, Expect}; + use ff::PrimeField; + + type EE = crate::provider::ipa_pc::EvaluationEngine; + type EEPrime = crate::provider::hyperkzg::EvaluationEngine; + type S = crate::spartan::snark::RelaxedR1CSSNARK; + type SPrime = crate::spartan::ppsnark::RelaxedR1CSSNARK; + + #[derive(Clone, Debug, Default)] + struct CubicCircuit { + _p: PhantomData, + } + + impl StepCircuit for CubicCircuit { + fn arity(&self) -> usize { + 1 + } + + fn synthesize>( + &self, + cs: &mut CS, + z: &[AllocatedNum], + ) -> Result>, SynthesisError> { + // Consider a cubic equation: `x^3 + x + 5 = y`, where `x` and `y` are respectively the input and output. + let x = &z[0]; + let x_sq = x.square(cs.namespace(|| "x_sq"))?; + let x_cu = x_sq.mul(cs.namespace(|| "x_cu"), x)?; + let y = AllocatedNum::alloc(cs.namespace(|| "y"), || { + Ok(x_cu.get_value().unwrap() + x.get_value().unwrap() + F::from(5u64)) + })?; + + cs.enforce( + || "y = x^3 + x + 5", + |lc| { + lc + x_cu.get_variable() + + x.get_variable() + + CS::one() + + CS::one() + + CS::one() + + CS::one() + + CS::one() + }, + |lc| lc + CS::one(), + |lc| lc + y.get_variable(), + ); + + Ok(vec![y]) + } + } + + impl CubicCircuit { + fn output(&self, z: &[F]) -> Vec { + vec![z[0] * z[0] * z[0] + z[0] + F::from(5u64)] + } + } + + fn test_pp_digest_with(circuit1: &T1, circuit2: &T2, expected: &Expect) + where + E1: Engine::Scalar>, + E2: Engine::Scalar>, + E1::GE: DlogGroup, + E2::GE: DlogGroup, + T1: StepCircuit, + T2: StepCircuit, + // required to use the IPA in the initialization of the commitment key hints below + >::CommitmentKey: CommitmentKeyExtTrait, + >::CommitmentKey: CommitmentKeyExtTrait, + { + // this tests public parameters with a size specifically intended for a spark-compressed SNARK + let ck_hint1 = &*SPrime::>::ck_floor(); + let ck_hint2 = &*SPrime::>::ck_floor(); + let pp = PublicParams::::setup(circuit1, circuit2, ck_hint1, ck_hint2).unwrap(); + + let digest_str = pp + .digest() + .to_repr() + .as_ref() + .iter() + .fold(String::new(), |mut output, b| { + let _ = write!(output, "{b:02x}"); + output + }); + expected.assert_eq(&digest_str); + } + + #[test] + fn test_pp_digest() { + test_pp_digest_with::( + &TrivialCircuit::<_>::default(), + &TrivialCircuit::<_>::default(), + &expect!["b3da591d9a3c7dc2632e550e009f2b745d60cf919956cf02e9ca68e8e5e17603"], + ); + + test_pp_digest_with::( + &TrivialCircuit::<_>::default(), + &TrivialCircuit::<_>::default(), + &expect!["aaf1f0b723e281603838004327e73a02f3a2b5e2f2087e34b6f4f2c8f34e8401"], + ); + + test_pp_digest_with::( + &TrivialCircuit::<_>::default(), + &TrivialCircuit::<_>::default(), + &expect!["890b992d9c431625610659fe62b5c00859188e60802a5852cf5db0d10ca59403"], + ); + } + + fn test_ivc_trivial_with() + where + E1: Engine::Scalar>, + E2: Engine::Scalar>, + { + let test_circuit1 = TrivialCircuit::<::Scalar>::default(); + let test_circuit2 = TrivialCircuit::<::Scalar>::default(); + + // produce public parameters + let pp = PublicParams::< + E1, + E2, + TrivialCircuit<::Scalar>, + TrivialCircuit<::Scalar>, + >::setup( + &test_circuit1, + &test_circuit2, + &*default_ck_hint(), + &*default_ck_hint(), + ) + .unwrap(); + + let num_steps = 1; + + // produce a recursive SNARK + let mut recursive_snark = RecursiveSNARK::new( + &pp, + &test_circuit1, + &test_circuit2, + &[::Scalar::ZERO], + &[::Scalar::ZERO], + ) + .unwrap(); + + let res = recursive_snark.prove_step(&pp, &test_circuit1, &test_circuit2); + + assert!(res.is_ok()); + + // verify the recursive SNARK + let res = recursive_snark.verify( + &pp, + num_steps, + &[::Scalar::ZERO], + &[::Scalar::ZERO], + ); + assert!(res.is_ok()); + } + + #[test] + fn test_ivc_trivial() { + test_ivc_trivial_with::(); + test_ivc_trivial_with::(); + test_ivc_trivial_with::(); + } + + fn test_ivc_nontrivial_with() + where + E1: Engine::Scalar>, + E2: Engine::Scalar>, + { + let circuit_primary = TrivialCircuit::default(); + let circuit_secondary = CubicCircuit::default(); + + // produce public parameters + let pp = PublicParams::< + E1, + E2, + TrivialCircuit<::Scalar>, + CubicCircuit<::Scalar>, + >::setup( + &circuit_primary, + &circuit_secondary, + &*default_ck_hint(), + &*default_ck_hint(), + ) + .unwrap(); + + let num_steps = 3; + + // produce a recursive SNARK + let mut recursive_snark = RecursiveSNARK::< + E1, + E2, + TrivialCircuit<::Scalar>, + CubicCircuit<::Scalar>, + >::new( + &pp, + &circuit_primary, + &circuit_secondary, + &[::Scalar::ONE], + &[::Scalar::ZERO], + ) + .unwrap(); + + for i in 0..num_steps { + let res = recursive_snark.prove_step(&pp, &circuit_primary, &circuit_secondary); + assert!(res.is_ok()); + + // verify the recursive snark at each step of recursion + let res = recursive_snark.verify( + &pp, + i + 1, + &[::Scalar::ONE], + &[::Scalar::ZERO], + ); + assert!(res.is_ok()); + } + + // verify the recursive SNARK + let res = recursive_snark.verify( + &pp, + num_steps, + &[::Scalar::ONE], + &[::Scalar::ZERO], + ); + assert!(res.is_ok()); + + let (zn_primary, zn_secondary) = res.unwrap(); + + // sanity: check the claimed output with a direct computation of the same + assert_eq!(zn_primary, vec![::Scalar::ONE]); + let mut zn_secondary_direct = vec![::Scalar::ZERO]; + for _i in 0..num_steps { + zn_secondary_direct = circuit_secondary.clone().output(&zn_secondary_direct); + } + assert_eq!(zn_secondary, zn_secondary_direct); + assert_eq!(zn_secondary, vec![::Scalar::from(2460515u64)]); + } + + #[test] + fn test_ivc_nontrivial() { + test_ivc_nontrivial_with::(); + test_ivc_nontrivial_with::(); + test_ivc_nontrivial_with::(); + } + + fn test_ivc_nontrivial_with_compression_with() + where + E1: Engine::Scalar>, + E2: Engine::Scalar>, + EE1: EvaluationEngineTrait, + EE2: EvaluationEngineTrait, + { + let circuit_primary = TrivialCircuit::default(); + let circuit_secondary = CubicCircuit::default(); + + // produce public parameters + let pp = PublicParams::< + E1, + E2, + TrivialCircuit<::Scalar>, + CubicCircuit<::Scalar>, + >::setup( + &circuit_primary, + &circuit_secondary, + &*default_ck_hint(), + &*default_ck_hint(), + ) + .unwrap(); + + let num_steps = 3; + + // produce a recursive SNARK + let mut recursive_snark = RecursiveSNARK::< + E1, + E2, + TrivialCircuit<::Scalar>, + CubicCircuit<::Scalar>, + >::new( + &pp, + &circuit_primary, + &circuit_secondary, + &[::Scalar::ONE], + &[::Scalar::ZERO], + ) + .unwrap(); + + for _i in 0..num_steps { + let res = recursive_snark.prove_step(&pp, &circuit_primary, &circuit_secondary); + assert!(res.is_ok()); + } + + // verify the recursive SNARK + let res = recursive_snark.verify( + &pp, + num_steps, + &[::Scalar::ONE], + &[::Scalar::ZERO], + ); + assert!(res.is_ok()); + + let (zn_primary, zn_secondary) = res.unwrap(); + + // sanity: check the claimed output with a direct computation of the same + assert_eq!(zn_primary, vec![::Scalar::ONE]); + let mut zn_secondary_direct = vec![::Scalar::ZERO]; + for _i in 0..num_steps { + zn_secondary_direct = circuit_secondary.clone().output(&zn_secondary_direct); + } + assert_eq!(zn_secondary, zn_secondary_direct); + assert_eq!(zn_secondary, vec![::Scalar::from(2460515u64)]); + + // produce the prover and verifier keys for compressed snark + let (pk, vk) = CompressedSNARK::<_, _, _, _, S, S>::setup(&pp).unwrap(); + + // produce a compressed SNARK + let res = + CompressedSNARK::<_, _, _, _, S, S>::prove(&pp, &pk, &recursive_snark); + assert!(res.is_ok()); + let compressed_snark = res.unwrap(); + + // verify the compressed SNARK + let res = compressed_snark.verify( + &vk, + num_steps, + &[::Scalar::ONE], + &[::Scalar::ZERO], + ); + assert!(res.is_ok()); + } + + #[test] + fn test_ivc_nontrivial_with_compression() { + test_ivc_nontrivial_with_compression_with::, EE<_>>(); + test_ivc_nontrivial_with_compression_with::, EE<_>>( + ); + test_ivc_nontrivial_with_compression_with::, EE<_>>(); + + test_ivc_nontrivial_with_spark_compression_with::< + Bn256EngineKZG, + GrumpkinEngine, + crate::provider::hyperkzg::EvaluationEngine<_>, + EE<_>, + >(); + } + + fn test_ivc_nontrivial_with_spark_compression_with() + where + E1: Engine::Scalar>, + E2: Engine::Scalar>, + EE1: EvaluationEngineTrait, + EE2: EvaluationEngineTrait, + { + let circuit_primary = TrivialCircuit::default(); + let circuit_secondary = CubicCircuit::default(); + + // produce public parameters, which we'll use with a spark-compressed SNARK + let pp = PublicParams::< + E1, + E2, + TrivialCircuit<::Scalar>, + CubicCircuit<::Scalar>, + >::setup( + &circuit_primary, + &circuit_secondary, + &*SPrime::::ck_floor(), + &*SPrime::::ck_floor(), + ) + .unwrap(); + + let num_steps = 3; + + // produce a recursive SNARK + let mut recursive_snark = RecursiveSNARK::< + E1, + E2, + TrivialCircuit<::Scalar>, + CubicCircuit<::Scalar>, + >::new( + &pp, + &circuit_primary, + &circuit_secondary, + &[::Scalar::ONE], + &[::Scalar::ZERO], + ) + .unwrap(); + + for _i in 0..num_steps { + let res = recursive_snark.prove_step(&pp, &circuit_primary, &circuit_secondary); + assert!(res.is_ok()); + } + + // verify the recursive SNARK + let res = recursive_snark.verify( + &pp, + num_steps, + &[::Scalar::ONE], + &[::Scalar::ZERO], + ); + assert!(res.is_ok()); + + let (zn_primary, zn_secondary) = res.unwrap(); + + // sanity: check the claimed output with a direct computation of the same + assert_eq!(zn_primary, vec![::Scalar::ONE]); + let mut zn_secondary_direct = vec![::Scalar::ZERO]; + for _i in 0..num_steps { + zn_secondary_direct = CubicCircuit::default().output(&zn_secondary_direct); + } + assert_eq!(zn_secondary, zn_secondary_direct); + assert_eq!(zn_secondary, vec![::Scalar::from(2460515u64)]); + + // run the compressed snark with Spark compiler + // produce the prover and verifier keys for compressed snark + let (pk, vk) = + CompressedSNARK::<_, _, _, _, SPrime, SPrime>::setup(&pp).unwrap(); + + // produce a compressed SNARK + let res = CompressedSNARK::<_, _, _, _, SPrime, SPrime>::prove( + &pp, + &pk, + &recursive_snark, + ); + assert!(res.is_ok()); + let compressed_snark = res.unwrap(); + + // verify the compressed SNARK + let res = compressed_snark.verify( + &vk, + num_steps, + &[::Scalar::ONE], + &[::Scalar::ZERO], + ); + assert!(res.is_ok()); + } + + #[test] + fn test_ivc_nontrivial_with_spark_compression() { + test_ivc_nontrivial_with_spark_compression_with::, EE<_>>(); + test_ivc_nontrivial_with_spark_compression_with::< + Bn256EngineKZG, + GrumpkinEngine, + EEPrime<_>, + EE<_>, + >(); + test_ivc_nontrivial_with_spark_compression_with::, EE<_>>( + ); + } + + fn test_ivc_nondet_with_compression_with() + where + E1: Engine::Scalar>, + E2: Engine::Scalar>, + EE1: EvaluationEngineTrait, + EE2: EvaluationEngineTrait, + { + // y is a non-deterministic advice representing the fifth root of the input at a step. + #[derive(Clone, Debug)] + struct FifthRootCheckingCircuit { + y: F, + } + + impl FifthRootCheckingCircuit { + fn new(num_steps: usize) -> (Vec, Vec) { + let mut powers = Vec::new(); + let rng = &mut rand::rngs::OsRng; + let mut seed = F::random(rng); + for _i in 0..num_steps + 1 { + seed *= seed.clone().square().square(); + + powers.push(Self { y: seed }); + } + + // reverse the powers to get roots + let roots = powers.into_iter().rev().collect::>(); + (vec![roots[0].y], roots[1..].to_vec()) + } + } + + impl StepCircuit for FifthRootCheckingCircuit + where + F: PrimeField, + { + fn arity(&self) -> usize { + 1 + } + + fn synthesize>( + &self, + cs: &mut CS, + z: &[AllocatedNum], + ) -> Result>, SynthesisError> { + let x = &z[0]; + + // we allocate a variable and set it to the provided non-deterministic advice. + let y = AllocatedNum::alloc_infallible(cs.namespace(|| "y"), || self.y); + + // We now check if y = x^{1/5} by checking if y^5 = x + let y_sq = y.square(cs.namespace(|| "y_sq"))?; + let y_quad = y_sq.square(cs.namespace(|| "y_quad"))?; + let y_pow_5 = y_quad.mul(cs.namespace(|| "y_fifth"), &y)?; + + cs.enforce( + || "y^5 = x", + |lc| lc + y_pow_5.get_variable(), + |lc| lc + CS::one(), + |lc| lc + x.get_variable(), + ); + + Ok(vec![y]) + } + } + + let circuit_primary = FifthRootCheckingCircuit { + y: ::Scalar::ZERO, + }; + + let circuit_secondary = TrivialCircuit::default(); + + // produce public parameters + let pp = PublicParams::< + E1, + E2, + FifthRootCheckingCircuit<::Scalar>, + TrivialCircuit<::Scalar>, + >::setup( + &circuit_primary, + &circuit_secondary, + &*default_ck_hint(), + &*default_ck_hint(), + ) + .unwrap(); + + let num_steps = 3; + + // produce non-deterministic advice + let (z0_primary, roots) = FifthRootCheckingCircuit::new(num_steps); + let z0_secondary = vec![::Scalar::ZERO]; + + // produce a recursive SNARK + let mut recursive_snark: RecursiveSNARK< + E1, + E2, + FifthRootCheckingCircuit<::Scalar>, + TrivialCircuit<::Scalar>, + > = RecursiveSNARK::< + E1, + E2, + FifthRootCheckingCircuit<::Scalar>, + TrivialCircuit<::Scalar>, + >::new( + &pp, + &roots[0], + &circuit_secondary, + &z0_primary, + &z0_secondary, + ) + .unwrap(); + + for circuit_primary in roots.iter().take(num_steps) { + let res = recursive_snark.prove_step(&pp, circuit_primary, &circuit_secondary); + assert!(res.is_ok()); + } + + // verify the recursive SNARK + let res = recursive_snark.verify(&pp, num_steps, &z0_primary, &z0_secondary); + assert!(res.is_ok()); + + // produce the prover and verifier keys for compressed snark + let (pk, vk) = CompressedSNARK::<_, _, _, _, S, S>::setup(&pp).unwrap(); + + // produce a compressed SNARK + let res = + CompressedSNARK::<_, _, _, _, S, S>::prove(&pp, &pk, &recursive_snark); + assert!(res.is_ok()); + let compressed_snark = res.unwrap(); + + // verify the compressed SNARK + let res = compressed_snark.verify(&vk, num_steps, &z0_primary, &z0_secondary); + assert!(res.is_ok()); + } + + #[test] + fn test_ivc_nondet_with_compression() { + test_ivc_nondet_with_compression_with::, EE<_>>(); + test_ivc_nondet_with_compression_with::, EE<_>>(); + test_ivc_nondet_with_compression_with::, EE<_>>(); + } + + fn test_ivc_base_with() + where + E1: Engine::Scalar>, + E2: Engine::Scalar>, + { + let test_circuit1 = TrivialCircuit::<::Scalar>::default(); + let test_circuit2 = CubicCircuit::<::Scalar>::default(); + + // produce public parameters + let pp = PublicParams::< + E1, + E2, + TrivialCircuit<::Scalar>, + CubicCircuit<::Scalar>, + >::setup( + &test_circuit1, + &test_circuit2, + &*default_ck_hint(), + &*default_ck_hint(), + ) + .unwrap(); + + let num_steps = 1; + + // produce a recursive SNARK + let mut recursive_snark = RecursiveSNARK::< + E1, + E2, + TrivialCircuit<::Scalar>, + CubicCircuit<::Scalar>, + >::new( + &pp, + &test_circuit1, + &test_circuit2, + &[::Scalar::ONE], + &[::Scalar::ZERO], + ) + .unwrap(); + + // produce a recursive SNARK + let res = recursive_snark.prove_step(&pp, &test_circuit1, &test_circuit2); + + assert!(res.is_ok()); + + // verify the recursive SNARK + let res = recursive_snark.verify( + &pp, + num_steps, + &[::Scalar::ONE], + &[::Scalar::ZERO], + ); + assert!(res.is_ok()); + + let (zn_primary, zn_secondary) = res.unwrap(); + + assert_eq!(zn_primary, vec![::Scalar::ONE]); + assert_eq!(zn_secondary, vec![::Scalar::from(5u64)]); + } + + #[test] + fn test_ivc_base() { + test_ivc_base_with::(); + test_ivc_base_with::(); + test_ivc_base_with::(); + } + + fn test_setup_with() + where + E1: Engine::Scalar>, + E2: Engine::Scalar>, + { + #[derive(Clone, Debug, Default)] + struct CircuitWithInputize { + _p: PhantomData, + } + + impl StepCircuit for CircuitWithInputize { + fn arity(&self) -> usize { + 1 + } + + fn synthesize>( + &self, + cs: &mut CS, + z: &[AllocatedNum], + ) -> Result>, SynthesisError> { + let x = &z[0]; + let y = x.square(cs.namespace(|| "x_sq"))?; + y.inputize(cs.namespace(|| "y"))?; // inputize y + Ok(vec![y]) + } + } + + // produce public parameters with trivial secondary + let circuit = CircuitWithInputize::<::Scalar>::default(); + let pp = + PublicParams::, TrivialCircuit>::setup( + &circuit, + &TrivialCircuit::default(), + &*default_ck_hint(), + &*default_ck_hint(), + ); + assert!(pp.is_err()); + assert_eq!(pp.err(), Some(NovaError::InvalidStepCircuitIO)); + + // produce public parameters with the trivial primary + let circuit = CircuitWithInputize::::default(); + let pp = + PublicParams::, CircuitWithInputize>::setup( + &TrivialCircuit::default(), + &circuit, + &*default_ck_hint(), + &*default_ck_hint(), + ); + assert!(pp.is_err()); + assert_eq!(pp.err(), Some(NovaError::InvalidStepCircuitIO)); + } + + #[test] + fn test_setup() { + test_setup_with::(); + } +} diff --git a/src/nifs.rs b/src/nova/nifs.rs similarity index 98% rename from src/nifs.rs rename to src/nova/nifs.rs index dd28195f..8547dbc8 100644 --- a/src/nifs.rs +++ b/src/nova/nifs.rs @@ -1,10 +1,10 @@ //! This module implements a non-interactive folding scheme #![allow(non_snake_case)] use crate::{ - constants::{NUM_CHALLENGE_BITS, NUM_FE_FOR_RO, NUM_FE_FOR_RO_RELAXED}, + constants::NUM_CHALLENGE_BITS, errors::NovaError, + gadgets::utils::scalar_as_base, r1cs::{R1CSInstance, R1CSShape, R1CSWitness, RelaxedR1CSInstance, RelaxedR1CSWitness}, - scalar_as_base, traits::{AbsorbInROTrait, Engine, ROTrait}, Commitment, CommitmentKey, }; @@ -47,7 +47,7 @@ impl NIFS { W2: &R1CSWitness, ) -> Result<(NIFS, (RelaxedR1CSInstance, RelaxedR1CSWitness)), NovaError> { // initialize a new RO - let mut ro = E::RO::new(ro_consts.clone(), NUM_FE_FOR_RO); + let mut ro = E::RO::new(ro_consts.clone()); // append the digest of pp to the transcript ro.absorb(scalar_as_base::(*pp_digest)); @@ -88,7 +88,7 @@ impl NIFS { U2: &R1CSInstance, ) -> Result, NovaError> { // initialize a new RO - let mut ro = E::RO::new(ro_consts.clone(), NUM_FE_FOR_RO); + let mut ro = E::RO::new(ro_consts.clone()); // append the digest of pp to the transcript ro.absorb(scalar_as_base::(*pp_digest)); @@ -137,7 +137,7 @@ impl NIFSRelaxed { NovaError, > { // initialize a new RO - let mut ro = E::RO::new(ro_consts.clone(), NUM_FE_FOR_RO_RELAXED); + let mut ro = E::RO::new(ro_consts.clone()); // append the digest of pp to the transcript ro.absorb(scalar_as_base::(*pp_digest)); @@ -179,7 +179,7 @@ impl NIFSRelaxed { U2: &RelaxedR1CSInstance, ) -> Result, NovaError> { // initialize a new RO - let mut ro = E::RO::new(ro_consts.clone(), NUM_FE_FOR_RO_RELAXED); + let mut ro = E::RO::new(ro_consts.clone()); // append the digest of pp to the transcript ro.absorb(scalar_as_base::(*pp_digest)); diff --git a/src/provider/poseidon.rs b/src/provider/poseidon.rs index 0ad56f36..3231a276 100644 --- a/src/provider/poseidon.rs +++ b/src/provider/poseidon.rs @@ -36,7 +36,6 @@ where // Internal State state: Vec, constants: PoseidonConstantsCircuit, - num_absorbs: usize, squeezed: bool, _p: PhantomData, } @@ -49,11 +48,10 @@ where type CircuitRO = PoseidonROCircuit; type Constants = PoseidonConstantsCircuit; - fn new(constants: PoseidonConstantsCircuit, num_absorbs: usize) -> Self { + fn new(constants: PoseidonConstantsCircuit) -> Self { Self { state: Vec::new(), constants, - num_absorbs, squeezed: false, _p: PhantomData, } @@ -74,13 +72,12 @@ where let mut sponge = Sponge::new_with_constants(&self.constants.0, Simplex); let acc = &mut (); let parameter = IOPattern(vec![ - SpongeOp::Absorb(self.num_absorbs as u32), + SpongeOp::Absorb(self.state.len() as u32), SpongeOp::Squeeze(1u32), ]); sponge.start(parameter, None, acc); - assert_eq!(self.num_absorbs, self.state.len()); - SpongeAPI::absorb(&mut sponge, self.num_absorbs as u32, &self.state, acc); + SpongeAPI::absorb(&mut sponge, self.state.len() as u32, &self.state, acc); let hash = SpongeAPI::squeeze(&mut sponge, 1, acc); sponge.finish(acc).unwrap(); @@ -104,7 +101,6 @@ pub struct PoseidonROCircuit { // Internal state state: Vec>, constants: PoseidonConstantsCircuit, - num_absorbs: usize, squeezed: bool, } @@ -116,11 +112,10 @@ where type Constants = PoseidonConstantsCircuit; /// Initialize the internal state and set the poseidon constants - fn new(constants: PoseidonConstantsCircuit, num_absorbs: usize) -> Self { + fn new(constants: PoseidonConstantsCircuit) -> Self { Self { state: Vec::new(), constants, - num_absorbs, squeezed: false, } } @@ -141,7 +136,7 @@ where assert!(!self.squeezed, "Cannot squeeze again after squeezing"); self.squeezed = true; let parameter = IOPattern(vec![ - SpongeOp::Absorb(self.num_absorbs as u32), + SpongeOp::Absorb(self.state.len() as u32), SpongeOp::Squeeze(1u32), ]); let mut ns = cs.namespace(|| "ns"); @@ -149,12 +144,11 @@ where let hash = { let mut sponge = SpongeCircuit::new_with_constants(&self.constants.0, Simplex); let acc = &mut ns; - assert_eq!(self.num_absorbs, self.state.len()); sponge.start(parameter, None, acc); SpongeAPI::absorb( &mut sponge, - self.num_absorbs as u32, + self.state.len() as u32, &(0..self.state.len()) .map(|i| Elt::Allocated(self.state[i].clone())) .collect::>>(), @@ -204,9 +198,8 @@ mod tests { let mut csprng: OsRng = OsRng; let constants = PoseidonConstantsCircuit::::default(); let num_absorbs = 32; - let mut ro: PoseidonRO = PoseidonRO::new(constants.clone(), num_absorbs); - let mut ro_gadget: PoseidonROCircuit = - PoseidonROCircuit::new(constants, num_absorbs); + let mut ro: PoseidonRO = PoseidonRO::new(constants.clone()); + let mut ro_gadget: PoseidonROCircuit = PoseidonROCircuit::new(constants); let mut cs = SatisfyingAssignment::::new(); for i in 0..num_absorbs { let num = E::Scalar::random(&mut csprng); diff --git a/src/traits/mod.rs b/src/traits/mod.rs index 8f128fb5..de499443 100644 --- a/src/traits/mod.rs +++ b/src/traits/mod.rs @@ -71,12 +71,13 @@ pub trait ROTrait { type Constants: Default + Clone + Send + Sync + Serialize + for<'de> Deserialize<'de>; /// Initializes the hash function - fn new(constants: Self::Constants, num_absorbs: usize) -> Self; + fn new(constants: Self::Constants) -> Self; /// Adds a scalar to the internal state fn absorb(&mut self, e: Base); /// Returns a challenge of `num_bits` by hashing the internal state + /// The `squeeze` method can be called only once fn squeeze(&mut self, num_bits: usize) -> Scalar; } @@ -89,12 +90,13 @@ pub trait ROCircuitTrait { type Constants: Default + Clone + Send + Sync + Serialize + for<'de> Deserialize<'de>; /// Initializes the hash function - fn new(constants: Self::Constants, num_absorbs: usize) -> Self; + fn new(constants: Self::Constants) -> Self; /// Adds a scalar to the internal state fn absorb(&mut self, e: &AllocatedNum); /// Returns a challenge of `num_bits` by hashing the internal state + /// The `squeeze` method can be called only once fn squeeze>( &mut self, cs: CS,