From 0a7cbf925f2322b25b9891277de2270614b0fef2 Mon Sep 17 00:00:00 2001 From: Srinath Setty Date: Thu, 11 Aug 2022 19:03:44 -0700 Subject: [PATCH] integrate with neptune's sponge (#105) * integrate with neptune's sponge * fix clippy warning * add checks to ensure at most one squeeze * add checks to ensure at most one squeeze --- Cargo.toml | 2 +- src/circuit.rs | 12 ++-- src/constants.rs | 2 + src/gadgets/r1cs.rs | 4 +- src/lib.rs | 11 ++-- src/nifs.rs | 6 +- src/poseidon.rs | 132 +++++++++++++++++++++++++------------------- src/traits/mod.rs | 6 +- 8 files changed, 97 insertions(+), 78 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4ec5424..98739ad 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,7 @@ itertools = "0.9.0" subtle = "2.4" pasta_curves = { version = "0.4.0", features = ["repr-c"] } pasta-msm = "0.1.3" -neptune = { version = "7.1", default-features = false } +neptune = { version = "7.2.0", default-features = false } generic-array = "0.14.4" bellperson-nonnative = { version = "0.3.1", default-features = false, features = ["wasm"] } num-bigint = { version = "0.4", features = ["serde", "rand"] } diff --git a/src/circuit.rs b/src/circuit.rs index 84ad403..6213453 100644 --- a/src/circuit.rs +++ b/src/circuit.rs @@ -8,7 +8,7 @@ use super::{ commitments::Commitment, - constants::NUM_HASH_BITS, + constants::{NUM_FE_FOR_HASH, NUM_HASH_BITS}, gadgets::{ ecc::AllocatedPoint, r1cs::{AllocatedR1CSInstance, AllocatedRelaxedR1CSInstance}, @@ -222,7 +222,7 @@ where T: AllocatedPoint, ) -> Result<(AllocatedRelaxedR1CSInstance, AllocatedBit), SynthesisError> { // Check that u.x[0] = Hash(params, U, i, z0, zi) - let mut ro = G::ROCircuit::new(self.ro_consts.clone()); + let mut ro = G::ROCircuit::new(self.ro_consts.clone(), NUM_FE_FOR_HASH); ro.absorb(params.clone()); ro.absorb(i); ro.absorb(z_0); @@ -329,7 +329,7 @@ where .synthesize(&mut cs.namespace(|| "F"), z_input)?; // Compute the new hash H(params, Unew, i+1, z0, z_{i+1}) - let mut ro = G::ROCircuit::new(self.ro_consts); + let mut ro = G::ROCircuit::new(self.ro_consts, NUM_FE_FOR_HASH); ro.absorb(params); ro.absorb(i_new.clone()); ro.absorb(z_0); @@ -361,7 +361,7 @@ mod tests { }; #[test] - fn test_verification_circuit() { + fn test_recursive_circuit() { // In the following we use 1 to refer to the primary, and 2 to refer to the secondary circuit let params1 = NovaAugmentedCircuitParams::new(BN_LIMB_WIDTH, BN_N_LIMBS, true); let params2 = NovaAugmentedCircuitParams::new(BN_LIMB_WIDTH, BN_N_LIMBS, false); @@ -379,7 +379,7 @@ mod tests { let mut cs: ShapeCS = ShapeCS::new(); let _ = circuit1.synthesize(&mut cs); let (shape1, gens1) = (cs.r1cs_shape(), cs.r1cs_gens()); - assert_eq!(cs.num_constraints(), 20122); + assert_eq!(cs.num_constraints(), 19739); // Initialize the shape and gens for the secondary let circuit2: NovaAugmentedCircuit::Base>> = @@ -392,7 +392,7 @@ mod tests { let mut cs: ShapeCS = ShapeCS::new(); let _ = circuit2.synthesize(&mut cs); let (shape2, gens2) = (cs.r1cs_shape(), cs.r1cs_gens()); - assert_eq!(cs.num_constraints(), 20654); + assert_eq!(cs.num_constraints(), 20271); // Execute the base case for the primary let zero1 = <::Base as Field>::zero(); diff --git a/src/constants.rs b/src/constants.rs index c458a5d..aeb53e6 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -2,3 +2,5 @@ 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_FOR_HASH: usize = 19; +pub(crate) const NUM_FE_FOR_RO: usize = 24; diff --git a/src/gadgets/r1cs.rs b/src/gadgets/r1cs.rs index 2fc7ff1..229f073 100644 --- a/src/gadgets/r1cs.rs +++ b/src/gadgets/r1cs.rs @@ -1,6 +1,6 @@ //! This module implements various gadgets necessary for folding R1CS types. use crate::{ - constants::NUM_CHALLENGE_BITS, + constants::{NUM_CHALLENGE_BITS, NUM_FE_FOR_RO}, gadgets::{ ecc::AllocatedPoint, utils::{ @@ -268,7 +268,7 @@ where n_limbs: usize, ) -> Result, SynthesisError> { // Compute r: - let mut ro = G::ROCircuit::new(ro_consts); + let mut ro = G::ROCircuit::new(ro_consts, NUM_FE_FOR_RO); ro.absorb(params); self.absorb_in_ro(cs.namespace(|| "absorb running instance"), &mut ro)?; u.absorb_in_ro(&mut ro); diff --git a/src/lib.rs b/src/lib.rs index aead67a..b40d0d9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,8 +26,7 @@ use crate::bellperson::{ }; use ::bellperson::{Circuit, ConstraintSystem}; use circuit::{NovaAugmentedCircuit, NovaAugmentedCircuitInputs, NovaAugmentedCircuitParams}; -use constants::NUM_HASH_BITS; -use constants::{BN_LIMB_WIDTH, BN_N_LIMBS}; +use constants::{BN_LIMB_WIDTH, BN_N_LIMBS, NUM_FE_FOR_HASH, NUM_HASH_BITS}; use core::marker::PhantomData; use errors::NovaError; use ff::Field; @@ -392,14 +391,14 @@ where // 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()); + let mut hasher = ::RO::new(pp.ro_consts_secondary.clone(), NUM_FE_FOR_HASH); 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.zi_primary); self.r_U_secondary.absorb_in_ro(&mut hasher); - let mut hasher2 = ::RO::new(pp.ro_consts_primary.clone()); + let mut hasher2 = ::RO::new(pp.ro_consts_primary.clone(), NUM_FE_FOR_HASH); hasher2.absorb(scalar_as_base::(pp.r1cs_shape_primary.get_digest())); hasher2.absorb(G2::Scalar::from(num_steps as u64)); hasher2.absorb(z0_secondary); @@ -607,14 +606,14 @@ where // 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()); + let mut hasher = ::RO::new(pp.ro_consts_secondary.clone(), NUM_FE_FOR_HASH); 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 = ::RO::new(pp.ro_consts_primary.clone()); + let mut hasher2 = ::RO::new(pp.ro_consts_primary.clone(), NUM_FE_FOR_HASH); hasher2.absorb(scalar_as_base::(pp.r1cs_shape_primary.get_digest())); hasher2.absorb(G2::Scalar::from(num_steps as u64)); hasher2.absorb(z0_secondary); diff --git a/src/nifs.rs b/src/nifs.rs index 8335df1..b6ac037 100644 --- a/src/nifs.rs +++ b/src/nifs.rs @@ -4,7 +4,7 @@ use super::{ commitments::CompressedCommitment, - constants::NUM_CHALLENGE_BITS, + constants::{NUM_CHALLENGE_BITS, NUM_FE_FOR_RO}, errors::NovaError, r1cs::{R1CSGens, R1CSInstance, R1CSShape, R1CSWitness, RelaxedR1CSInstance, RelaxedR1CSWitness}, traits::{AbsorbInROTrait, Group, ROTrait}, @@ -39,7 +39,7 @@ impl NIFS { W2: &R1CSWitness, ) -> Result<(NIFS, (RelaxedR1CSInstance, RelaxedR1CSWitness)), NovaError> { // initialize a new RO - let mut ro = G::RO::new(ro_consts.clone()); + let mut ro = G::RO::new(ro_consts.clone(), NUM_FE_FOR_RO); // append S to the transcript S.absorb_in_ro(&mut ro); @@ -86,7 +86,7 @@ impl NIFS { U2: &R1CSInstance, ) -> Result, NovaError> { // initialize a new RO - let mut ro = G::RO::new(ro_consts.clone()); + let mut ro = G::RO::new(ro_consts.clone(), NUM_FE_FOR_RO); // append S to the transcript S.absorb_in_ro(&mut ro); diff --git a/src/poseidon.rs b/src/poseidon.rs index 13537b0..ca1740d 100644 --- a/src/poseidon.rs +++ b/src/poseidon.rs @@ -9,36 +9,30 @@ use bellperson::{ }; use core::marker::PhantomData; use ff::{PrimeField, PrimeFieldBits}; -use generic_array::typenum::{U19, U24}; +use generic_array::typenum::U24; use neptune::{ - circuit::poseidon_hash, - poseidon::{Poseidon, PoseidonConstants}, + circuit2::Elt, + poseidon::PoseidonConstants, + sponge::{ + api::{IOPattern, SpongeAPI, SpongeOp}, + circuit::SpongeCircuit, + vanilla::{Mode::Simplex, Sponge, SpongeTrait}, + }, Strength, }; /// All Poseidon Constants that are used in Nova #[derive(Clone)] -pub struct PoseidonConstantsCircuit -where - Scalar: PrimeField, -{ - constants19: PoseidonConstants, - constants24: PoseidonConstants, -} +pub struct PoseidonConstantsCircuit(PoseidonConstants); impl ROConstantsTrait for PoseidonConstantsCircuit where Scalar: PrimeField + PrimeFieldBits, { - /// Generate Poseidon constants for the arities that Nova uses + /// Generate Poseidon constants #[allow(clippy::new_without_default)] fn new() -> Self { - let constants19 = PoseidonConstants::::new_with_strength(Strength::Standard); - let constants24 = PoseidonConstants::::new_with_strength(Strength::Standard); - Self { - constants19, - constants24, - } + Self(Sponge::::api_constants(Strength::Standard)) } } @@ -50,8 +44,9 @@ where { // Internal State state: Vec, - // Constants for Poseidon constants: PoseidonConstantsCircuit, + num_absorbs: usize, + squeezed: bool, _p: PhantomData, } @@ -62,38 +57,43 @@ where { type Constants = PoseidonConstantsCircuit; - fn new(constants: PoseidonConstantsCircuit) -> Self { + fn new(constants: PoseidonConstantsCircuit, num_absorbs: usize) -> Self { Self { state: Vec::new(), constants, + num_absorbs, + squeezed: false, _p: PhantomData::default(), } } /// Absorb a new number into the state of the oracle fn absorb(&mut self, e: Base) { + assert!(!self.squeezed, "Cannot absorb after squeezing"); self.state.push(e); } /// Compute a challenge by hashing the current state - fn squeeze(&self, num_bits: usize) -> Scalar { - let hash = match self.state.len() { - 19 => { - Poseidon::::new_with_preimage(&self.state, &self.constants.constants19).hash() - } - 24 => { - Poseidon::::new_with_preimage(&self.state, &self.constants.constants24).hash() - } - _ => { - panic!( - "Number of elements in the RO state does not match any of the arities used in Nova: {:?}", - self.state.len() - ); - } - }; + fn squeeze(&mut self, num_bits: usize) -> Scalar { + // check if we have squeezed already + assert!(!self.squeezed, "Cannot squeeze again after squeezing"); + self.squeezed = true; + + 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::Squeeze(1u32), + ]); + + sponge.start(parameter, Some(1u32), acc); + assert_eq!(self.num_absorbs, self.state.len()); + SpongeAPI::absorb(&mut sponge, self.num_absorbs as u32, &self.state, acc); + let hash = SpongeAPI::squeeze(&mut sponge, 1, acc); + sponge.finish(acc).unwrap(); // Only return `num_bits` - let bits = hash.to_le_bits(); + let bits = hash[0].to_le_bits(); let mut res = Scalar::zero(); let mut coeff = Scalar::one(); for bit in bits[0..num_bits].into_iter() { @@ -114,6 +114,8 @@ where // Internal state state: Vec>, constants: PoseidonConstantsCircuit, + num_absorbs: usize, + squeezed: bool, } impl ROCircuitTrait for PoseidonROCircuit @@ -123,15 +125,18 @@ where type Constants = PoseidonConstantsCircuit; /// Initialize the internal state and set the poseidon constants - fn new(constants: PoseidonConstantsCircuit) -> Self { + fn new(constants: PoseidonConstantsCircuit, num_absorbs: usize) -> Self { Self { state: Vec::new(), constants, + num_absorbs, + squeezed: false, } } /// Absorb a new number into the state of the oracle fn absorb(&mut self, e: AllocatedNum) { + assert!(!self.squeezed, "Cannot absorb after squeezing"); self.state.push(e); } @@ -144,29 +149,41 @@ where where CS: ConstraintSystem, { - let hash = match self.state.len() { - 19 => poseidon_hash( - cs.namespace(|| "Poseidon hash"), - self.state.clone(), - &self.constants.constants19, - )?, - 24 => poseidon_hash( - cs.namespace(|| "Posideon hash"), - self.state.clone(), - &self.constants.constants24, - )?, - _ => { - panic!( - "Number of elements in the RO state does not match any of the arities used in Nova: {}", - self.state.len() - ) - } + // check if we have squeezed already + assert!(!self.squeezed, "Cannot squeeze again after squeezing"); + self.squeezed = true; + let parameter = IOPattern(vec![ + SpongeOp::Absorb(self.num_absorbs as u32), + SpongeOp::Squeeze(1u32), + ]); + let mut ns = cs.namespace(|| "ns"); + + 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, Some(1u32), acc); + neptune::sponge::api::SpongeAPI::absorb( + &mut sponge, + self.num_absorbs as u32, + &(0..self.state.len()) + .map(|i| Elt::Allocated(self.state[i].clone())) + .collect::>>(), + acc, + ); + + let output = neptune::sponge::api::SpongeAPI::squeeze(&mut sponge, 1, acc); + sponge.finish(acc).unwrap(); + output }; + let hash = Elt::ensure_allocated(&hash[0], &mut ns.namespace(|| "ensure allocated"), true)?; + // return the hash as a vector of bits, truncated Ok( hash - .to_bits_le_strict(cs.namespace(|| "poseidon hash to boolean"))? + .to_bits_le_strict(ns.namespace(|| "poseidon hash to boolean"))? .iter() .map(|boolean| match boolean { Boolean::Is(ref x) => x.clone(), @@ -196,10 +213,11 @@ mod tests { // Check that the number computed inside the circuit is equal to the number computed outside the circuit let mut csprng: OsRng = OsRng; let constants = PoseidonConstantsCircuit::new(); - let mut ro: PoseidonRO = PoseidonRO::new(constants.clone()); - let mut ro_gadget: PoseidonROCircuit = PoseidonROCircuit::new(constants); + 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 cs: SatisfyingAssignment = SatisfyingAssignment::new(); - for i in 0..19 { + for i in 0..num_absorbs { let num = S::random(&mut csprng); ro.absorb(num); let num_gadget = diff --git a/src/traits/mod.rs b/src/traits/mod.rs index ddcd97b..4f1868f 100644 --- a/src/traits/mod.rs +++ b/src/traits/mod.rs @@ -108,13 +108,13 @@ pub trait ROTrait { type Constants: ROConstantsTrait + Clone + Send + Sync; /// Initializes the hash function - fn new(constants: Self::Constants) -> Self; + fn new(constants: Self::Constants, num_absorbs: usize) -> 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 - fn squeeze(&self, num_bits: usize) -> Scalar; + fn squeeze(&mut self, num_bits: usize) -> Scalar; } /// A helper trait that defines the behavior of a hash function that we use as an RO in the circuit model @@ -123,7 +123,7 @@ pub trait ROCircuitTrait { type Constants: ROConstantsTrait + Clone + Send + Sync; /// Initializes the hash function - fn new(constants: Self::Constants) -> Self; + fn new(constants: Self::Constants, num_absorbs: usize) -> Self; /// Adds a scalar to the internal state fn absorb(&mut self, e: AllocatedNum);