Browse Source

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
main
Srinath Setty 2 years ago
committed by GitHub
parent
commit
0a7cbf925f
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 97 additions and 78 deletions
  1. +1
    -1
      Cargo.toml
  2. +6
    -6
      src/circuit.rs
  3. +2
    -0
      src/constants.rs
  4. +2
    -2
      src/gadgets/r1cs.rs
  5. +5
    -6
      src/lib.rs
  6. +3
    -3
      src/nifs.rs
  7. +75
    -57
      src/poseidon.rs
  8. +3
    -3
      src/traits/mod.rs

+ 1
- 1
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"] }

+ 6
- 6
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<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 @@ 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<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 @@ mod tests {
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
- 0
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;

+ 2
- 2
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<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);

+ 5
- 6
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 = <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 @@ where
// 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);

+ 3
- 3
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<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 @@ impl NIFS {
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);

+ 75
- 57
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<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 @@ where
{
// Internal State
state: Vec<Base>,
// Constants for Poseidon
constants: PoseidonConstantsCircuit<Base>,
num_absorbs: usize,
squeezed: bool,
_p: PhantomData<Scalar>,
}
@ -62,38 +57,43 @@ where
{
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 @@ where
// 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 @@ where
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 @@ where
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 @@ 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<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 =

+ 3
- 3
src/traits/mod.rs

@ -108,13 +108,13 @@ pub trait ROTrait {
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 @@ pub trait ROCircuitTrait {
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>);

Loading…
Cancel
Save