mirror of
https://github.com/arnaucube/Nova.git
synced 2026-01-11 00:21:29 +01:00
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
This commit is contained in:
@@ -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 @@
|
||||
|
||||
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,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 @@ 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);
|
||||
|
||||
11
src/lib.rs
11
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);
|
||||
|
||||
@@ -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<G: Group> NIFS<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 @@ impl<G: Group> NIFS<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);
|
||||
|
||||
132
src/poseidon.rs
132
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 =
|
||||
|
||||
@@ -108,13 +108,13 @@ pub trait ROTrait<Base, Scalar> {
|
||||
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<Base: PrimeField> {
|
||||
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>);
|
||||
|
||||
Reference in New Issue
Block a user