//! This library implements core components of Nova. #![allow(non_snake_case)] #![allow(clippy::type_complexity)] #![deny(missing_docs)] pub mod bellperson; mod circuit; mod commitments; mod constants; pub mod errors; pub mod gadgets; pub mod nifs; pub mod pasta; mod poseidon; pub mod r1cs; pub mod traits; use crate::bellperson::{ r1cs::{NovaShape, NovaWitness}, shape_cs::ShapeCS, solver::SatisfyingAssignment, }; use ::bellperson::{Circuit, ConstraintSystem}; use circuit::{NIFSVerifierCircuit, NIFSVerifierCircuitInputs, NIFSVerifierCircuitParams}; use constants::{BN_LIMB_WIDTH, BN_N_LIMBS}; use core::marker::PhantomData; use errors::NovaError; use ff::Field; use gadgets::utils::scalar_as_base; use nifs::NIFS; use poseidon::ROConstantsCircuit; // TODO: make this a trait so we can use it without the concrete implementation use r1cs::{ R1CSGens, R1CSInstance, R1CSShape, R1CSWitness, RelaxedR1CSInstance, RelaxedR1CSWitness, }; use traits::{AbsorbInROTrait, Group, HashFuncConstantsTrait, HashFuncTrait, StepCircuit}; type ROConstants = <::HashFunc as HashFuncTrait<::Base, ::Scalar>>::Constants; /// A type that holds public parameters of Nova pub struct PublicParams where G1: Group::Scalar>, G2: Group::Scalar>, C1: StepCircuit + Clone, C2: StepCircuit + Clone, { ro_consts_primary: ROConstants, ro_consts_circuit_primary: ROConstantsCircuit<::Base>, r1cs_gens_primary: R1CSGens, r1cs_shape_primary: R1CSShape, ro_consts_secondary: ROConstants, ro_consts_circuit_secondary: ROConstantsCircuit<::Base>, r1cs_gens_secondary: R1CSGens, r1cs_shape_secondary: R1CSShape, c_primary: C1, c_secondary: C2, params_primary: NIFSVerifierCircuitParams, params_secondary: NIFSVerifierCircuitParams, } impl PublicParams where G1: Group::Scalar>, G2: Group::Scalar>, C1: StepCircuit + Clone, C2: StepCircuit + Clone, { /// Create a new `PublicParams` pub fn setup(c_primary: C1, c_secondary: C2) -> Self { let params_primary = NIFSVerifierCircuitParams::new(BN_LIMB_WIDTH, BN_N_LIMBS, true); let params_secondary = NIFSVerifierCircuitParams::new(BN_LIMB_WIDTH, BN_N_LIMBS, false); let ro_consts_primary: ROConstants = ROConstants::::new(); let ro_consts_secondary: ROConstants = ROConstants::::new(); let ro_consts_circuit_primary: ROConstantsCircuit<::Base> = ROConstantsCircuit::new(); let ro_consts_circuit_secondary: ROConstantsCircuit<::Base> = ROConstantsCircuit::new(); // Initialize gens for the primary let circuit_primary: NIFSVerifierCircuit = NIFSVerifierCircuit::new( params_primary.clone(), None, c_primary.clone(), ro_consts_circuit_primary.clone(), ); let mut cs: ShapeCS = ShapeCS::new(); let _ = circuit_primary.synthesize(&mut cs); let (r1cs_shape_primary, r1cs_gens_primary) = (cs.r1cs_shape(), cs.r1cs_gens()); // Initialize gens for the secondary let circuit_secondary: NIFSVerifierCircuit = NIFSVerifierCircuit::new( params_secondary.clone(), None, c_secondary.clone(), ro_consts_circuit_secondary.clone(), ); let mut cs: ShapeCS = ShapeCS::new(); let _ = circuit_secondary.synthesize(&mut cs); let (r1cs_shape_secondary, r1cs_gens_secondary) = (cs.r1cs_shape(), cs.r1cs_gens()); Self { ro_consts_primary, ro_consts_circuit_primary, r1cs_gens_primary, r1cs_shape_primary, ro_consts_secondary, ro_consts_circuit_secondary, r1cs_gens_secondary, r1cs_shape_secondary, c_primary, c_secondary, params_primary, params_secondary, } } } /// A SNARK that proves the correct execution of an incremental computation pub struct RecursiveSNARK where G1: Group::Scalar>, G2: Group::Scalar>, C1: StepCircuit + Clone, C2: StepCircuit + Clone, { r_W_primary: RelaxedR1CSWitness, r_U_primary: RelaxedR1CSInstance, l_w_primary: R1CSWitness, l_u_primary: R1CSInstance, r_W_secondary: RelaxedR1CSWitness, r_U_secondary: RelaxedR1CSInstance, l_w_secondary: R1CSWitness, l_u_secondary: R1CSInstance, zn_primary: G1::Scalar, zn_secondary: G2::Scalar, _p_c1: PhantomData, _p_c2: PhantomData, } impl RecursiveSNARK where G1: Group::Scalar>, G2: Group::Scalar>, C1: StepCircuit + Clone, C2: StepCircuit + Clone, { /// Create a new `RecursiveSNARK` pub fn prove( pp: &PublicParams, num_steps: usize, z0_primary: G1::Scalar, z0_secondary: G2::Scalar, ) -> Result { if num_steps == 0 { return Err(NovaError::InvalidNumSteps); } // Execute the base case for the primary let mut cs_primary: SatisfyingAssignment = SatisfyingAssignment::new(); let inputs_primary: NIFSVerifierCircuitInputs = NIFSVerifierCircuitInputs::new( pp.r1cs_shape_secondary.get_digest(), G1::Scalar::zero(), z0_primary, None, None, None, None, ); let circuit_primary: NIFSVerifierCircuit = NIFSVerifierCircuit::new( pp.params_primary.clone(), Some(inputs_primary), pp.c_primary.clone(), pp.ro_consts_circuit_primary.clone(), ); let _ = circuit_primary.synthesize(&mut cs_primary); let (u_primary, w_primary) = cs_primary .r1cs_instance_and_witness(&pp.r1cs_shape_primary, &pp.r1cs_gens_primary) .map_err(|_e| NovaError::UnSat)?; // Execute the base case for the secondary let mut cs_secondary: SatisfyingAssignment = SatisfyingAssignment::new(); let inputs_secondary: NIFSVerifierCircuitInputs = NIFSVerifierCircuitInputs::new( pp.r1cs_shape_primary.get_digest(), G2::Scalar::zero(), z0_secondary, None, None, Some(u_primary.clone()), None, ); let circuit_secondary: NIFSVerifierCircuit = NIFSVerifierCircuit::new( pp.params_secondary.clone(), Some(inputs_secondary), pp.c_secondary.clone(), pp.ro_consts_circuit_secondary.clone(), ); let _ = circuit_secondary.synthesize(&mut cs_secondary); let (u_secondary, w_secondary) = cs_secondary .r1cs_instance_and_witness(&pp.r1cs_shape_secondary, &pp.r1cs_gens_secondary) .map_err(|_e| NovaError::UnSat)?; // execute the remaining steps, alternating between G1 and G2 let mut l_w_primary = w_primary; let mut l_u_primary = u_primary; let mut r_W_primary = RelaxedR1CSWitness::from_r1cs_witness(&pp.r1cs_shape_primary, &l_w_primary); let mut r_U_primary = RelaxedR1CSInstance::from_r1cs_instance( &pp.r1cs_gens_primary, &pp.r1cs_shape_primary, &l_u_primary, ); let mut r_W_secondary = RelaxedR1CSWitness::::default(&pp.r1cs_shape_secondary); let mut r_U_secondary = RelaxedR1CSInstance::::default(&pp.r1cs_gens_secondary, &pp.r1cs_shape_secondary); let mut l_w_secondary = w_secondary; let mut l_u_secondary = u_secondary; let mut z_next_primary = z0_primary; let mut z_next_secondary = z0_secondary; z_next_primary = pp.c_primary.compute(&z_next_primary); z_next_secondary = pp.c_secondary.compute(&z_next_secondary); for i in 1..num_steps { // fold the secondary circuit's instance let (nifs_secondary, (r_U_next_secondary, r_W_next_secondary)) = NIFS::prove( &pp.r1cs_gens_secondary, &pp.ro_consts_secondary, &pp.r1cs_shape_secondary, &r_U_secondary, &r_W_secondary, &l_u_secondary, &l_w_secondary, )?; let mut cs_primary: SatisfyingAssignment = SatisfyingAssignment::new(); let inputs_primary: NIFSVerifierCircuitInputs = NIFSVerifierCircuitInputs::new( pp.r1cs_shape_secondary.get_digest(), G1::Scalar::from(i as u64), z0_primary, Some(z_next_primary), Some(r_U_secondary), Some(l_u_secondary), Some(nifs_secondary.comm_T.decompress()?), ); let circuit_primary: NIFSVerifierCircuit = NIFSVerifierCircuit::new( pp.params_primary.clone(), Some(inputs_primary), pp.c_primary.clone(), pp.ro_consts_circuit_primary.clone(), ); let _ = circuit_primary.synthesize(&mut cs_primary); (l_u_primary, l_w_primary) = cs_primary .r1cs_instance_and_witness(&pp.r1cs_shape_primary, &pp.r1cs_gens_primary) .map_err(|_e| NovaError::UnSat)?; // fold the primary circuit's instance let (nifs_primary, (r_U_next_primary, r_W_next_primary)) = NIFS::prove( &pp.r1cs_gens_primary, &pp.ro_consts_primary, &pp.r1cs_shape_primary, &r_U_primary.clone(), &r_W_primary.clone(), &l_u_primary.clone(), &l_w_primary.clone(), )?; let mut cs_secondary: SatisfyingAssignment = SatisfyingAssignment::new(); let inputs_secondary: NIFSVerifierCircuitInputs = NIFSVerifierCircuitInputs::new( pp.r1cs_shape_primary.get_digest(), G2::Scalar::from(i as u64), z0_secondary, Some(z_next_secondary), Some(r_U_primary.clone()), Some(l_u_primary.clone()), Some(nifs_primary.comm_T.decompress()?), ); let circuit_secondary: NIFSVerifierCircuit = NIFSVerifierCircuit::new( pp.params_secondary.clone(), Some(inputs_secondary), pp.c_secondary.clone(), pp.ro_consts_circuit_secondary.clone(), ); let _ = circuit_secondary.synthesize(&mut cs_secondary); (l_u_secondary, l_w_secondary) = cs_secondary .r1cs_instance_and_witness(&pp.r1cs_shape_secondary, &pp.r1cs_gens_secondary) .map_err(|_e| NovaError::UnSat)?; // update the running instances and witnesses r_U_secondary = r_U_next_secondary; r_W_secondary = r_W_next_secondary; r_U_primary = r_U_next_primary; r_W_primary = r_W_next_primary; z_next_primary = pp.c_primary.compute(&z_next_primary); z_next_secondary = pp.c_secondary.compute(&z_next_secondary); } Ok(Self { r_W_primary, r_U_primary, l_w_primary, l_u_primary, r_W_secondary, r_U_secondary, l_w_secondary, l_u_secondary, zn_primary: z_next_primary, zn_secondary: z_next_secondary, _p_c1: Default::default(), _p_c2: Default::default(), }) } /// Verify the correctness of the `RecursiveSNARK` pub fn verify( &self, pp: &PublicParams, num_steps: usize, z0_primary: G1::Scalar, z0_secondary: G2::Scalar, ) -> Result<(G1::Scalar, G2::Scalar), NovaError> { // 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_primary.X.len() != 2 || self.l_u_secondary.X.len() != 2 || self.r_U_primary.X.len() != 2 || self.r_U_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 = ::HashFunc::new(pp.ro_consts_secondary.clone()); hasher.absorb(scalar_as_base::(pp.r1cs_shape_secondary.get_digest())); hasher.absorb(G1::Scalar::from(num_steps as u64)); hasher.absorb(z0_primary); hasher.absorb(self.zn_primary); self.r_U_secondary.absorb_in_ro(&mut hasher); let mut hasher2 = ::HashFunc::new(pp.ro_consts_primary.clone()); hasher2.absorb(scalar_as_base::(pp.r1cs_shape_primary.get_digest())); hasher2.absorb(G2::Scalar::from(num_steps as u64)); hasher2.absorb(z0_secondary); hasher2.absorb(self.zn_secondary); self.r_U_primary.absorb_in_ro(&mut hasher2); (hasher.get_hash(), hasher2.get_hash()) }; if hash_primary != scalar_as_base::(self.l_u_primary.X[1]) || hash_secondary != scalar_as_base::(self.l_u_secondary.X[1]) { return Err(NovaError::ProofVerifyError); } // check the satisfiability of the provided instances pp.r1cs_shape_primary.is_sat_relaxed( &pp.r1cs_gens_primary, &self.r_U_primary, &self.r_W_primary, )?; pp.r1cs_shape_primary .is_sat(&pp.r1cs_gens_primary, &self.l_u_primary, &self.l_w_primary)?; pp.r1cs_shape_secondary.is_sat_relaxed( &pp.r1cs_gens_secondary, &self.r_U_secondary, &self.r_W_secondary, )?; pp.r1cs_shape_secondary.is_sat( &pp.r1cs_gens_secondary, &self.l_u_secondary, &self.l_w_secondary, )?; Ok((self.zn_primary, self.zn_secondary)) } } /// A SNARK that proves the knowledge of a valid `RecursiveSNARK` /// For now, it implements a constant factor compression. /// In the future, we will implement an exponential reduction in proof sizes pub struct CompressedSNARK where G1: Group::Scalar>, G2: Group::Scalar>, C1: StepCircuit + Clone, C2: StepCircuit + Clone, { r_U_primary: RelaxedR1CSInstance, l_u_primary: R1CSInstance, nifs_primary: NIFS, f_W_primary: RelaxedR1CSWitness, r_U_secondary: RelaxedR1CSInstance, l_u_secondary: R1CSInstance, nifs_secondary: NIFS, f_W_secondary: RelaxedR1CSWitness, zn_primary: G1::Scalar, zn_secondary: G2::Scalar, _p_c1: PhantomData, _p_c2: PhantomData, } impl CompressedSNARK where G1: Group::Scalar>, G2: Group::Scalar>, C1: StepCircuit + Clone, C2: StepCircuit + Clone, { /// Create a new `CompressedSNARK` pub fn prove( pp: &PublicParams, recursive_snark: &RecursiveSNARK, ) -> Result { // fold the primary circuit's instance let (nifs_primary, (_f_U_primary, f_W_primary)) = NIFS::prove( &pp.r1cs_gens_primary, &pp.ro_consts_primary, &pp.r1cs_shape_primary, &recursive_snark.r_U_primary, &recursive_snark.r_W_primary, &recursive_snark.l_u_primary, &recursive_snark.l_w_primary, )?; // fold the secondary circuit's instance let (nifs_secondary, (_f_U_secondary, f_W_secondary)) = NIFS::prove( &pp.r1cs_gens_secondary, &pp.ro_consts_secondary, &pp.r1cs_shape_secondary, &recursive_snark.r_U_secondary, &recursive_snark.r_W_secondary, &recursive_snark.l_u_secondary, &recursive_snark.l_w_secondary, )?; Ok(Self { r_U_primary: recursive_snark.r_U_primary.clone(), l_u_primary: recursive_snark.l_u_primary.clone(), nifs_primary, f_W_primary, r_U_secondary: recursive_snark.r_U_secondary.clone(), l_u_secondary: recursive_snark.l_u_secondary.clone(), nifs_secondary, f_W_secondary, zn_primary: recursive_snark.zn_primary, zn_secondary: recursive_snark.zn_secondary, _p_c1: Default::default(), _p_c2: Default::default(), }) } /// Verify the correctness of the `CompressedSNARK` pub fn verify( &self, pp: &PublicParams, num_steps: usize, z0_primary: G1::Scalar, z0_secondary: G2::Scalar, ) -> Result<(G1::Scalar, G2::Scalar), NovaError> { // 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_primary.X.len() != 2 || self.l_u_secondary.X.len() != 2 || self.r_U_primary.X.len() != 2 || self.r_U_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 = ::HashFunc::new(pp.ro_consts_secondary.clone()); hasher.absorb(scalar_as_base::(pp.r1cs_shape_secondary.get_digest())); hasher.absorb(G1::Scalar::from(num_steps as u64)); hasher.absorb(z0_primary); hasher.absorb(self.zn_primary); self.r_U_secondary.absorb_in_ro(&mut hasher); let mut hasher2 = ::HashFunc::new(pp.ro_consts_primary.clone()); hasher2.absorb(scalar_as_base::(pp.r1cs_shape_primary.get_digest())); hasher2.absorb(G2::Scalar::from(num_steps as u64)); hasher2.absorb(z0_secondary); hasher2.absorb(self.zn_secondary); self.r_U_primary.absorb_in_ro(&mut hasher2); (hasher.get_hash(), hasher2.get_hash()) }; if hash_primary != scalar_as_base::(self.l_u_primary.X[1]) || hash_secondary != scalar_as_base::(self.l_u_secondary.X[1]) { return Err(NovaError::ProofVerifyError); } // fold the running instance and last instance to get a folded instance let f_U_primary = self.nifs_primary.verify( &pp.ro_consts_primary, &pp.r1cs_shape_primary, &self.r_U_primary, &self.l_u_primary, )?; let f_U_secondary = self.nifs_secondary.verify( &pp.ro_consts_secondary, &pp.r1cs_shape_secondary, &self.r_U_secondary, &self.l_u_secondary, )?; // check the satisfiability of the folded instances using the purported folded witnesses pp.r1cs_shape_primary .is_sat_relaxed(&pp.r1cs_gens_primary, &f_U_primary, &self.f_W_primary)?; pp.r1cs_shape_secondary.is_sat_relaxed( &pp.r1cs_gens_secondary, &f_U_secondary, &self.f_W_secondary, )?; Ok((self.zn_primary, self.zn_secondary)) } } #[cfg(test)] mod tests { use super::*; type G1 = pasta_curves::pallas::Point; type G2 = pasta_curves::vesta::Point; use ::bellperson::{gadgets::num::AllocatedNum, ConstraintSystem, SynthesisError}; use ff::PrimeField; use std::marker::PhantomData; #[derive(Clone, Debug)] struct TrivialTestCircuit { _p: PhantomData, } impl StepCircuit for TrivialTestCircuit where F: PrimeField, { fn synthesize>( &self, _cs: &mut CS, z: AllocatedNum, ) -> Result, SynthesisError> { Ok(z) } fn compute(&self, z: &F) -> F { *z } } #[derive(Clone, Debug)] struct CubicCircuit { _p: PhantomData, } impl StepCircuit for CubicCircuit where F: PrimeField, { 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; 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(y) } fn compute(&self, z: &F) -> F { *z * *z * *z + z + F::from(5u64) } } #[test] fn test_ivc_trivial() { // produce public parameters let pp = PublicParams::< G1, G2, TrivialTestCircuit<::Scalar>, TrivialTestCircuit<::Scalar>, >::setup( TrivialTestCircuit { _p: Default::default(), }, TrivialTestCircuit { _p: Default::default(), }, ); // produce a recursive SNARK let res = RecursiveSNARK::prove( &pp, 3, ::Scalar::zero(), ::Scalar::zero(), ); assert!(res.is_ok()); let recursive_snark = res.unwrap(); // verify the recursive SNARK let res = recursive_snark.verify( &pp, 3, ::Scalar::zero(), ::Scalar::zero(), ); assert!(res.is_ok()); } #[test] fn test_ivc_nontrivial() { // produce public parameters let pp = PublicParams::< G1, G2, TrivialTestCircuit<::Scalar>, CubicCircuit<::Scalar>, >::setup( TrivialTestCircuit { _p: Default::default(), }, CubicCircuit { _p: Default::default(), }, ); let num_steps = 3; // produce a recursive SNARK let res = RecursiveSNARK::prove( &pp, num_steps, ::Scalar::one(), ::Scalar::zero(), ); assert!(res.is_ok()); let recursive_snark = res.unwrap(); // 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, ::Scalar::one()); let mut zn_secondary_direct = ::Scalar::zero(); for _i in 0..num_steps { zn_secondary_direct = CubicCircuit { _p: Default::default(), } .compute(&zn_secondary_direct); } assert_eq!(zn_secondary, zn_secondary_direct); assert_eq!(zn_secondary, ::Scalar::from(2460515u64)); // produce a compressed SNARK let res = CompressedSNARK::prove(&pp, &recursive_snark); assert!(res.is_ok()); let compressed_snark = res.unwrap(); // verify the compressed SNARK let res = compressed_snark.verify( &pp, num_steps, ::Scalar::one(), ::Scalar::zero(), ); assert!(res.is_ok()); } #[test] fn test_ivc_base() { // produce public parameters let pp = PublicParams::< G1, G2, TrivialTestCircuit<::Scalar>, CubicCircuit<::Scalar>, >::setup( TrivialTestCircuit { _p: Default::default(), }, CubicCircuit { _p: Default::default(), }, ); let num_steps = 1; // produce a recursive SNARK let res = RecursiveSNARK::prove( &pp, num_steps, ::Scalar::one(), ::Scalar::zero(), ); assert!(res.is_ok()); let recursive_snark = res.unwrap(); // 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, ::Scalar::one()); assert_eq!(zn_secondary, ::Scalar::from(5u64)); } }