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
@@ -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"] }
@@ -8,7 +8,7 @@ //! the other into the running instance
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 @@ u: AllocatedR1CSInstance<G>,
T: AllocatedPoint<G::Base>, ) -> Result<(AllocatedRelaxedR1CSInstance<G>, 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 @@ .step_circuit
.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 @@ traits::{circuit::TrivialTestCircuit, ROConstantsTrait},
}; #[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 @@ );
let mut cs: ShapeCS<G1> = 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<G1, TrivialTestCircuit<<G1 as Group>::Base>> =@@ -392,7 +392,7 @@ );
let mut cs: ShapeCS<G2> = 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 = <<G2 as Group>::Base as Field>::zero();
@@ -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;
@@ -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 @@ limb_width: usize,
n_limbs: usize, ) -> Result<AllocatedRelaxedR1CSInstance<G>, 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);
@@ -26,8 +26,7 @@ solver::SatisfyingAssignment,
}; 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 @@ }
// check if the output hashes in R1CS instances point to the right running instances let (hash_primary, hash_secondary) = { - let mut hasher = <G2 as Group>::RO::new(pp.ro_consts_secondary.clone()); + let mut hasher = <G2 as Group>::RO::new(pp.ro_consts_secondary.clone(), NUM_FE_FOR_HASH); hasher.absorb(scalar_as_base::<G2>(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 = <G1 as Group>::RO::new(pp.ro_consts_primary.clone()); + let mut hasher2 = <G1 as Group>::RO::new(pp.ro_consts_primary.clone(), NUM_FE_FOR_HASH); hasher2.absorb(scalar_as_base::<G1>(pp.r1cs_shape_primary.get_digest())); hasher2.absorb(G2::Scalar::from(num_steps as u64)); hasher2.absorb(z0_secondary);@@ -607,14 +606,14 @@ }
// check if the output hashes in R1CS instances point to the right running instances let (hash_primary, hash_secondary) = { - let mut hasher = <G2 as Group>::RO::new(pp.ro_consts_secondary.clone()); + let mut hasher = <G2 as Group>::RO::new(pp.ro_consts_secondary.clone(), NUM_FE_FOR_HASH); hasher.absorb(scalar_as_base::<G2>(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 = <G1 as Group>::RO::new(pp.ro_consts_primary.clone()); + let mut hasher2 = <G1 as Group>::RO::new(pp.ro_consts_primary.clone(), NUM_FE_FOR_HASH); hasher2.absorb(scalar_as_base::<G1>(pp.r1cs_shape_primary.get_digest())); hasher2.absorb(G2::Scalar::from(num_steps as u64)); hasher2.absorb(z0_secondary);
@@ -4,7 +4,7 @@ #![allow(clippy::type_complexity)]
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 @@ U2: &R1CSInstance<G>,
W2: &R1CSWitness<G>, ) -> Result<(NIFS<G>, (RelaxedR1CSInstance<G>, RelaxedR1CSWitness<G>)), 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 @@ U1: &RelaxedR1CSInstance<G>,
U2: &R1CSInstance<G>, ) -> Result<RelaxedR1CSInstance<G>, 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);
@@ -9,36 +9,30 @@ ConstraintSystem, SynthesisError,
}; 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<Scalar> -where - Scalar: PrimeField, -{ - constants19: PoseidonConstants<Scalar, U19>, - constants24: PoseidonConstants<Scalar, U24>, -} +pub struct PoseidonConstantsCircuit<Scalar: PrimeField>(PoseidonConstants<Scalar, U24>); impl<Scalar> ROConstantsTrait<Scalar> for PoseidonConstantsCircuit<Scalar> 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::<Scalar, U19>::new_with_strength(Strength::Standard); - let constants24 = PoseidonConstants::<Scalar, U24>::new_with_strength(Strength::Standard); - Self { - constants19, - constants24, - } + Self(Sponge::<Scalar, U24>::api_constants(Strength::Standard)) } }@@ -50,8 +44,9 @@ Scalar: PrimeField + PrimeFieldBits,
{ // Internal State state: Vec<Base>, - // Constants for Poseidon constants: PoseidonConstantsCircuit<Base>, + num_absorbs: usize, + squeezed: bool, _p: PhantomData<Scalar>, }@@ -62,38 +57,43 @@ Scalar: PrimeField + PrimeFieldBits,
{ type Constants = PoseidonConstantsCircuit<Base>; - fn new(constants: PoseidonConstantsCircuit<Base>) -> Self { + fn new(constants: PoseidonConstantsCircuit<Base>, 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::<Base, U19>::new_with_preimage(&self.state, &self.constants.constants19).hash() - } - 24 => { - Poseidon::<Base, U24>::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 @@ {
// Internal state state: Vec<AllocatedNum<Scalar>>, constants: PoseidonConstantsCircuit<Scalar>, + num_absorbs: usize, + squeezed: bool, } impl<Scalar> ROCircuitTrait<Scalar> for PoseidonROCircuit<Scalar>@@ -123,15 +125,18 @@ {
type Constants = PoseidonConstantsCircuit<Scalar>; /// Initialize the internal state and set the poseidon constants - fn new(constants: PoseidonConstantsCircuit<Scalar>) -> Self { + fn new(constants: PoseidonConstantsCircuit<Scalar>, 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<Scalar>) { + assert!(!self.squeezed, "Cannot absorb after squeezing"); self.state.push(e); }@@ -144,29 +149,41 @@ ) -> Result<Vec<AllocatedBit>, SynthesisError>
where CS: ConstraintSystem<Scalar>, { - 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::<Vec<Elt<Scalar>>>(), + 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 @@ fn test_poseidon_ro() {
// 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<S, B> = PoseidonRO::new(constants.clone()); - let mut ro_gadget: PoseidonROCircuit<S> = PoseidonROCircuit::new(constants); + let num_absorbs = 32; + let mut ro: PoseidonRO<S, B> = PoseidonRO::new(constants.clone(), num_absorbs); + let mut ro_gadget: PoseidonROCircuit<S> = PoseidonROCircuit::new(constants, num_absorbs); let mut cs: SatisfyingAssignment<G> = 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 =
@@ -108,13 +108,13 @@ /// A type representing constants/parameters associated with the hash function
type Constants: ROConstantsTrait<Base> + 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 @@ /// A type representing constants/parameters associated with the hash function
type Constants: ROConstantsTrait<Base> + 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<Base>);