Browse Source

Simplifications in Nova's RO (#98)

* rename methods for better clarity

* rename

* Bump version
main
Srinath Setty 2 years ago
committed by GitHub
parent
commit
3dc26fd7e4
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 163 additions and 210 deletions
  1. +1
    -1
      Cargo.toml
  2. +1
    -1
      benches/compressed-snark.rs
  3. +1
    -1
      benches/recursive-snark.rs
  4. +1
    -1
      examples/ecdsa/circuit.rs
  5. +1
    -1
      examples/minroot.rs
  6. +11
    -10
      src/circuit.rs
  7. +2
    -2
      src/commitments.rs
  8. +7
    -6
      src/gadgets/r1cs.rs
  9. +31
    -26
      src/lib.rs
  10. +20
    -26
      src/nifs.rs
  11. +5
    -5
      src/pasta.rs
  12. +56
    -96
      src/poseidon.rs
  13. +4
    -4
      src/r1cs.rs
  14. +3
    -3
      src/traits/circuit.rs
  15. +19
    -27
      src/traits/mod.rs

+ 1
- 1
Cargo.toml

@ -1,6 +1,6 @@
[package]
name = "nova-snark"
version = "0.7.1"
version = "0.7.2"
authors = ["Srinath Setty <srinath@microsoft.com>"]
edition = "2021"
description = "Recursive zkSNARKs without trusted setup"

+ 1
- 1
benches/compressed-snark.rs

@ -145,7 +145,7 @@ where
Ok(y)
}
fn compute(&self, z: &F) -> F {
fn output(&self, z: &F) -> F {
let mut x = *z;
let mut y = x;
for _i in 0..self.num_cons {

+ 1
- 1
benches/recursive-snark.rs

@ -146,7 +146,7 @@ where
Ok(y)
}
fn compute(&self, z: &F) -> F {
fn output(&self, z: &F) -> F {
let mut x = *z;
let mut y = x;
for _i in 0..self.num_cons {

+ 1
- 1
examples/ecdsa/circuit.rs

@ -289,7 +289,7 @@ where
)
}
fn compute(&self, z: &F) -> F {
fn output(&self, z: &F) -> F {
let z_hash = Poseidon::<F, U8>::new_with_preimage(
&[
self.z_r.x,

+ 1
- 1
examples/minroot.rs

@ -148,7 +148,7 @@ where
z_out
}
fn compute(&self, z: &F) -> F {
fn output(&self, z: &F) -> F {
// sanity check
let z_hash =
Poseidon::<F, U2>::new_with_preimage(&[self.seq[0].x_i, self.seq[0].y_i], &self.pc).hash();

+ 11
- 10
src/circuit.rs

@ -8,6 +8,7 @@
use super::{
commitments::Commitment,
constants::NUM_HASH_BITS,
gadgets::{
ecc::AllocatedPoint,
r1cs::{AllocatedR1CSInstance, AllocatedRelaxedR1CSInstance},
@ -16,7 +17,7 @@ use super::{
},
},
r1cs::{R1CSInstance, RelaxedR1CSInstance},
traits::{circuit::StepCircuit, Group, HashFuncCircuitTrait, HashFuncConstantsCircuit},
traits::{circuit::StepCircuit, Group, ROCircuitTrait, ROConstantsCircuit},
};
use bellperson::{
gadgets::{
@ -91,7 +92,7 @@ where
SC: StepCircuit<G::Base>,
{
params: NovaAugmentedCircuitParams,
ro_consts: HashFuncConstantsCircuit<G>,
ro_consts: ROConstantsCircuit<G>,
inputs: Option<NovaAugmentedCircuitInputs<G>>,
step_circuit: SC, // The function that is applied for each step
}
@ -106,7 +107,7 @@ where
params: NovaAugmentedCircuitParams,
inputs: Option<NovaAugmentedCircuitInputs<G>>,
step_circuit: SC,
ro_consts: HashFuncConstantsCircuit<G>,
ro_consts: ROConstantsCircuit<G>,
) -> Self {
Self {
params,
@ -221,14 +222,14 @@ 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::HashFuncCircuit::new(self.ro_consts.clone());
let mut ro = G::ROCircuit::new(self.ro_consts.clone());
ro.absorb(params.clone());
ro.absorb(i);
ro.absorb(z_0);
ro.absorb(z_i);
U.absorb_in_ro(cs.namespace(|| "absorb U"), &mut ro)?;
let hash_bits = ro.get_hash(cs.namespace(|| "Input hash"))?;
let hash_bits = ro.squeeze(cs.namespace(|| "Input hash"), NUM_HASH_BITS)?;
let hash = le_bits_to_num(cs.namespace(|| "bits to hash"), hash_bits)?;
let check_pass = alloc_num_equals(
cs.namespace(|| "check consistency of u.X[0] with H(params, U, i, z0, zi)"),
@ -328,13 +329,13 @@ 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::HashFuncCircuit::new(self.ro_consts);
let mut ro = G::ROCircuit::new(self.ro_consts);
ro.absorb(params);
ro.absorb(i_new.clone());
ro.absorb(z_0);
ro.absorb(z_next);
Unew.absorb_in_ro(cs.namespace(|| "absorb U_new"), &mut ro)?;
let hash_bits = ro.get_hash(cs.namespace(|| "output hash bits"))?;
let hash_bits = ro.squeeze(cs.namespace(|| "output hash bits"), NUM_HASH_BITS)?;
let hash = le_bits_to_num(cs.namespace(|| "convert hash to num"), hash_bits)?;
// Outputs the computed hash and u.X[1] that corresponds to the hash of the other circuit
@ -356,7 +357,7 @@ mod tests {
use crate::{
bellperson::r1cs::{NovaShape, NovaWitness},
poseidon::PoseidonConstantsCircuit,
traits::{circuit::TrivialTestCircuit, HashFuncConstantsTrait},
traits::{circuit::TrivialTestCircuit, ROConstantsTrait},
};
#[test]
@ -364,8 +365,8 @@ mod tests {
// 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);
let ro_consts1: HashFuncConstantsCircuit<G2> = PoseidonConstantsCircuit::new();
let ro_consts2: HashFuncConstantsCircuit<G1> = PoseidonConstantsCircuit::new();
let ro_consts1: ROConstantsCircuit<G2> = PoseidonConstantsCircuit::new();
let ro_consts2: ROConstantsCircuit<G1> = PoseidonConstantsCircuit::new();
// Initialize the shape and gens for the primary
let circuit1: NovaAugmentedCircuit<G2, TrivialTestCircuit<<G2 as Group>::Base>> =

+ 2
- 2
src/commitments.rs

@ -1,6 +1,6 @@
use super::{
errors::NovaError,
traits::{AbsorbInROTrait, AppendToTranscriptTrait, CompressedGroup, Group, HashFuncTrait},
traits::{AbsorbInROTrait, AppendToTranscriptTrait, CompressedGroup, Group, ROTrait},
};
use core::{
fmt::Debug,
@ -166,7 +166,7 @@ impl AppendToTranscriptTrait for Commitment {
}
impl<G: Group> AbsorbInROTrait<G> for Commitment<G> {
fn absorb_in_ro(&self, ro: &mut G::HashFunc) {
fn absorb_in_ro(&self, ro: &mut G::RO) {
let (x, y, is_infinity) = self.comm.to_coordinates();
ro.absorb(x);
ro.absorb(y);

+ 7
- 6
src/gadgets/r1cs.rs

@ -1,5 +1,6 @@
//! This module implements various gadgets necessary for folding R1CS types.
use crate::{
constants::NUM_CHALLENGE_BITS,
gadgets::{
ecc::AllocatedPoint,
utils::{
@ -8,7 +9,7 @@ use crate::{
},
},
r1cs::{R1CSInstance, RelaxedR1CSInstance},
traits::{Group, HashFuncCircuitTrait, HashFuncConstantsCircuit},
traits::{Group, ROCircuitTrait, ROConstantsCircuit},
};
use bellperson::{
gadgets::{boolean::Boolean, num::AllocatedNum, Assignment},
@ -60,7 +61,7 @@ where
}
/// Absorb the provided instance in the RO
pub fn absorb_in_ro(&self, ro: &mut G::HashFuncCircuit) {
pub fn absorb_in_ro(&self, ro: &mut G::ROCircuit) {
ro.absorb(self.W.x.clone());
ro.absorb(self.W.y.clone());
ro.absorb(self.W.is_infinity.clone());
@ -207,7 +208,7 @@ where
pub fn absorb_in_ro<CS: ConstraintSystem<<G as Group>::Base>>(
&self,
mut cs: CS,
ro: &mut G::HashFuncCircuit,
ro: &mut G::ROCircuit,
) -> Result<(), SynthesisError> {
ro.absorb(self.W.x.clone());
ro.absorb(self.W.y.clone());
@ -262,19 +263,19 @@ where
params: AllocatedNum<G::Base>, // hash of R1CSShape of F'
u: AllocatedR1CSInstance<G>,
T: AllocatedPoint<G::Base>,
ro_consts: HashFuncConstantsCircuit<G>,
ro_consts: ROConstantsCircuit<G>,
limb_width: usize,
n_limbs: usize,
) -> Result<AllocatedRelaxedR1CSInstance<G>, SynthesisError> {
// Compute r:
let mut ro = G::HashFuncCircuit::new(ro_consts);
let mut ro = G::ROCircuit::new(ro_consts);
ro.absorb(params);
self.absorb_in_ro(cs.namespace(|| "absorb running instance"), &mut ro)?;
u.absorb_in_ro(&mut ro);
ro.absorb(T.x.clone());
ro.absorb(T.y.clone());
ro.absorb(T.is_infinity.clone());
let r_bits = ro.get_challenge(cs.namespace(|| "r bits"))?;
let r_bits = ro.squeeze(cs.namespace(|| "r bits"), NUM_CHALLENGE_BITS)?;
let r = le_bits_to_num(cs.namespace(|| "r"), r_bits.clone())?;
// W_fold = self.W + r * u.W

+ 31
- 26
src/lib.rs

@ -26,6 +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 core::marker::PhantomData;
use errors::NovaError;
@ -36,8 +37,8 @@ use r1cs::{
R1CSGens, R1CSInstance, R1CSShape, R1CSWitness, RelaxedR1CSInstance, RelaxedR1CSWitness,
};
use traits::{
circuit::StepCircuit, snark::RelaxedR1CSSNARKTrait, AbsorbInROTrait, Group, HashFuncConstants,
HashFuncConstantsCircuit, HashFuncConstantsTrait, HashFuncTrait,
circuit::StepCircuit, snark::RelaxedR1CSSNARKTrait, AbsorbInROTrait, Group, ROConstants,
ROConstantsCircuit, ROConstantsTrait, ROTrait,
};
/// A type that holds public parameters of Nova
@ -48,13 +49,13 @@ where
C1: StepCircuit<G1::Scalar>,
C2: StepCircuit<G2::Scalar>,
{
ro_consts_primary: HashFuncConstants<G1>,
ro_consts_circuit_primary: HashFuncConstantsCircuit<G2>,
ro_consts_primary: ROConstants<G1>,
ro_consts_circuit_primary: ROConstantsCircuit<G2>,
r1cs_gens_primary: R1CSGens<G1>,
r1cs_shape_primary: R1CSShape<G1>,
r1cs_shape_padded_primary: R1CSShape<G1>,
ro_consts_secondary: HashFuncConstants<G2>,
ro_consts_circuit_secondary: HashFuncConstantsCircuit<G1>,
ro_consts_secondary: ROConstants<G2>,
ro_consts_circuit_secondary: ROConstantsCircuit<G1>,
r1cs_gens_secondary: R1CSGens<G2>,
r1cs_shape_secondary: R1CSShape<G2>,
r1cs_shape_padded_secondary: R1CSShape<G2>,
@ -78,14 +79,12 @@ where
let augmented_circuit_params_secondary =
NovaAugmentedCircuitParams::new(BN_LIMB_WIDTH, BN_N_LIMBS, false);
let ro_consts_primary: HashFuncConstants<G1> = HashFuncConstants::<G1>::new();
let ro_consts_secondary: HashFuncConstants<G2> = HashFuncConstants::<G2>::new();
let ro_consts_primary: ROConstants<G1> = ROConstants::<G1>::new();
let ro_consts_secondary: ROConstants<G2> = ROConstants::<G2>::new();
// ro_consts_circuit_primart are parameterized by G2 because the type alias uses G2::Base = G1::Scalar
let ro_consts_circuit_primary: HashFuncConstantsCircuit<G2> =
HashFuncConstantsCircuit::<G2>::new();
let ro_consts_circuit_secondary: HashFuncConstantsCircuit<G1> =
HashFuncConstantsCircuit::<G1>::new();
let ro_consts_circuit_primary: ROConstantsCircuit<G2> = ROConstantsCircuit::<G2>::new();
let ro_consts_circuit_secondary: ROConstantsCircuit<G1> = ROConstantsCircuit::<G1>::new();
// Initialize gens for the primary
let circuit_primary: NovaAugmentedCircuit<G2, C1> = NovaAugmentedCircuit::new(
@ -245,8 +244,8 @@ where
RelaxedR1CSInstance::<G2>::default(&pp.r1cs_gens_secondary, &pp.r1cs_shape_secondary);
// Outputs of the two circuits thus far
let zi_primary = c_primary.compute(&z0_primary);
let zi_secondary = c_secondary.compute(&z0_secondary);
let zi_primary = c_primary.output(&z0_primary);
let zi_secondary = c_secondary.output(&z0_secondary);
Ok(Self {
r_W_primary,
@ -334,8 +333,8 @@ where
.map_err(|_e| NovaError::UnSat)?;
// update the running instances and witnesses
let zi_primary = c_primary.compute(&r_snark.zi_primary);
let zi_secondary = c_secondary.compute(&r_snark.zi_secondary);
let zi_primary = c_primary.output(&r_snark.zi_primary);
let zi_secondary = c_secondary.output(&r_snark.zi_secondary);
Ok(Self {
r_W_primary,
@ -385,21 +384,24 @@ 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>::HashFunc::new(pp.ro_consts_secondary.clone());
let mut hasher = <G2 as Group>::RO::new(pp.ro_consts_secondary.clone());
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>::HashFunc::new(pp.ro_consts_primary.clone());
let mut hasher2 = <G1 as Group>::RO::new(pp.ro_consts_primary.clone());
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);
hasher2.absorb(self.zi_secondary);
self.r_U_primary.absorb_in_ro(&mut hasher2);
(hasher.get_hash(), hasher2.get_hash())
(
hasher.squeeze(NUM_HASH_BITS),
hasher2.squeeze(NUM_HASH_BITS),
)
};
if hash_primary != scalar_as_base::<G1>(self.l_u_primary.X[1])
@ -597,21 +599,24 @@ 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>::HashFunc::new(pp.ro_consts_secondary.clone());
let mut hasher = <G2 as Group>::RO::new(pp.ro_consts_secondary.clone());
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>::HashFunc::new(pp.ro_consts_primary.clone());
let mut hasher2 = <G1 as Group>::RO::new(pp.ro_consts_primary.clone());
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);
hasher2.absorb(self.zn_secondary);
self.r_U_primary.absorb_in_ro(&mut hasher2);
(hasher.get_hash(), hasher2.get_hash())
(
hasher.squeeze(NUM_HASH_BITS),
hasher2.squeeze(NUM_HASH_BITS),
)
};
if hash_primary != scalar_as_base::<G1>(self.l_u_primary.X[1])
@ -709,7 +714,7 @@ mod tests {
Ok(y)
}
fn compute(&self, z: &F) -> F {
fn output(&self, z: &F) -> F {
*z * *z * *z + z + F::from(5u64)
}
}
@ -816,7 +821,7 @@ mod tests {
assert_eq!(zn_primary, <G1 as Group>::Scalar::one());
let mut zn_secondary_direct = <G2 as Group>::Scalar::zero();
for _i in 0..num_steps {
zn_secondary_direct = CubicCircuit::default().compute(&zn_secondary_direct);
zn_secondary_direct = CubicCircuit::default().output(&zn_secondary_direct);
}
assert_eq!(zn_secondary, zn_secondary_direct);
assert_eq!(zn_secondary, <G2 as Group>::Scalar::from(2460515u64));
@ -878,7 +883,7 @@ mod tests {
assert_eq!(zn_primary, <G1 as Group>::Scalar::one());
let mut zn_secondary_direct = <G2 as Group>::Scalar::zero();
for _i in 0..num_steps {
zn_secondary_direct = CubicCircuit::default().compute(&zn_secondary_direct);
zn_secondary_direct = CubicCircuit::default().output(&zn_secondary_direct);
}
assert_eq!(zn_secondary, zn_secondary_direct);
assert_eq!(zn_secondary, <G2 as Group>::Scalar::from(2460515u64));
@ -960,7 +965,7 @@ mod tests {
Ok(y)
}
fn compute(&self, z: &F) -> F {
fn output(&self, z: &F) -> F {
// sanity check
let x = *z;
let y_pow_5 = {

+ 20
- 26
src/nifs.rs

@ -2,12 +2,13 @@
#![allow(non_snake_case)]
#![allow(clippy::type_complexity)]
use super::commitments::CompressedCommitment;
use super::errors::NovaError;
use super::r1cs::{
R1CSGens, R1CSInstance, R1CSShape, R1CSWitness, RelaxedR1CSInstance, RelaxedR1CSWitness,
use super::{
commitments::CompressedCommitment,
constants::NUM_CHALLENGE_BITS,
errors::NovaError,
r1cs::{R1CSGens, R1CSInstance, R1CSShape, R1CSWitness, RelaxedR1CSInstance, RelaxedR1CSWitness},
traits::{AbsorbInROTrait, Group, ROTrait},
};
use super::traits::{AbsorbInROTrait, Group, HashFuncTrait};
use core::marker::PhantomData;
/// A SNARK that holds the proof of a step of an incremental computation
@ -18,8 +19,8 @@ pub struct NIFS {
_p: PhantomData<G>,
}
type HashFuncConstants<G> =
<<G as Group>::HashFunc as HashFuncTrait<<G as Group>::Base, <G as Group>::Scalar>>::Constants;
type ROConstants<G> =
<<G as Group>::RO as ROTrait<<G as Group>::Base, <G as Group>::Scalar>>::Constants;
impl<G: Group> NIFS<G> {
/// Takes as input a Relaxed R1CS instance-witness tuple `(U1, W1)` and
@ -30,7 +31,7 @@ impl NIFS {
/// if and only if `W1` satisfies `U1` and `W2` satisfies `U2`.
pub fn prove(
gens: &R1CSGens<G>,
ro_consts: &HashFuncConstants<G>,
ro_consts: &ROConstants<G>,
S: &R1CSShape<G>,
U1: &RelaxedR1CSInstance<G>,
W1: &RelaxedR1CSWitness<G>,
@ -38,7 +39,7 @@ impl NIFS {
W2: &R1CSWitness<G>,
) -> Result<(NIFS<G>, (RelaxedR1CSInstance<G>, RelaxedR1CSWitness<G>)), NovaError> {
// initialize a new RO
let mut ro = G::HashFunc::new(ro_consts.clone());
let mut ro = G::RO::new(ro_consts.clone());
// append S to the transcript
S.absorb_in_ro(&mut ro);
@ -54,7 +55,7 @@ impl NIFS {
comm_T.absorb_in_ro(&mut ro);
// compute a challenge from the RO
let r = ro.get_challenge();
let r = ro.squeeze(NUM_CHALLENGE_BITS);
// fold the instance using `r` and `comm_T`
let U = U1.fold(U2, &comm_T, &r)?;
@ -79,13 +80,13 @@ impl NIFS {
/// if and only if `U1` and `U2` are satisfiable.
pub fn verify(
&self,
ro_consts: &HashFuncConstants<G>,
ro_consts: &ROConstants<G>,
S: &R1CSShape<G>,
U1: &RelaxedR1CSInstance<G>,
U2: &R1CSInstance<G>,
) -> Result<RelaxedR1CSInstance<G>, NovaError> {
// initialize a new RO
let mut ro = G::HashFunc::new(ro_consts.clone());
let mut ro = G::RO::new(ro_consts.clone());
// append S to the transcript
S.absorb_in_ro(&mut ro);
@ -99,7 +100,7 @@ impl NIFS {
comm_T.absorb_in_ro(&mut ro);
// compute a challenge from the RO
let r = ro.get_challenge();
let r = ro.squeeze(NUM_CHALLENGE_BITS);
// fold the instance using `r` and `comm_T`
let U = U1.fold(U2, &comm_T, &r)?;
@ -112,7 +113,7 @@ impl NIFS {
#[cfg(test)]
mod tests {
use super::*;
use crate::traits::{Group, HashFuncConstantsTrait};
use crate::traits::{Group, ROConstantsTrait};
use ::bellperson::{gadgets::num::AllocatedNum, ConstraintSystem, SynthesisError};
use ff::{Field, PrimeField};
use rand::rngs::OsRng;
@ -166,10 +167,8 @@ mod tests {
let _ = synthesize_tiny_r1cs_bellperson(&mut cs, None);
let shape = cs.r1cs_shape();
let gens = cs.r1cs_gens();
let ro_consts = <<G as Group>::HashFunc as HashFuncTrait<
<G as Group>::Base,
<G as Group>::Scalar,
>>::Constants::new();
let ro_consts =
<<G as Group>::RO as ROTrait<<G as Group>::Base, <G as Group>::Scalar>>::Constants::new();
// Now get the instance and assignment for one instance
let mut cs: SatisfyingAssignment<G> = SatisfyingAssignment::new();
@ -193,10 +192,7 @@ mod tests {
fn execute_sequence(
gens: &R1CSGens<G>,
ro_consts: &<<G as Group>::HashFunc as HashFuncTrait<
<G as Group>::Base,
<G as Group>::Scalar,
>>::Constants,
ro_consts: &<<G as Group>::RO as ROTrait<<G as Group>::Base, <G as Group>::Scalar>>::Constants,
shape: &R1CSShape<G>,
U1: &R1CSInstance<G>,
W1: &R1CSWitness<G>,
@ -304,10 +300,8 @@ mod tests {
// generate generators and ro constants
let gens = R1CSGens::new(num_cons, num_vars);
let ro_consts = <<G as Group>::HashFunc as HashFuncTrait<
<G as Group>::Base,
<G as Group>::Scalar,
>>::Constants::new();
let ro_consts =
<<G as Group>::RO as ROTrait<<G as Group>::Base, <G as Group>::Scalar>>::Constants::new();
let rand_inst_witness_generator =
|gens: &R1CSGens<G>, I: &S| -> (S, R1CSInstance<G>, R1CSWitness<G>) {

+ 5
- 5
src/pasta.rs

@ -1,6 +1,6 @@
//! This module implements the Nova traits for pallas::Point, pallas::Scalar, vesta::Point, vesta::Scalar.
use crate::{
poseidon::{PoseidonHashFunc, PoseidonHashFuncCircuit},
poseidon::{PoseidonRO, PoseidonROCircuit},
traits::{ChallengeTrait, CompressedGroup, Group},
};
use digest::{ExtendableOutput, Input};
@ -40,8 +40,8 @@ impl Group for pallas::Point {
type Scalar = pallas::Scalar;
type CompressedGroupElement = PallasCompressedElementWrapper;
type PreprocessedGroupElement = pallas::Affine;
type HashFunc = PoseidonHashFunc<Self::Base, Self::Scalar>;
type HashFuncCircuit = PoseidonHashFuncCircuit<Self::Base>;
type RO = PoseidonRO<Self::Base, Self::Scalar>;
type ROCircuit = PoseidonROCircuit<Self::Base>;
fn vartime_multiscalar_mul(
scalars: &[Self::Scalar],
@ -138,8 +138,8 @@ impl Group for vesta::Point {
type Scalar = vesta::Scalar;
type CompressedGroupElement = VestaCompressedElementWrapper;
type PreprocessedGroupElement = vesta::Affine;
type HashFunc = PoseidonHashFunc<Self::Base, Self::Scalar>;
type HashFuncCircuit = PoseidonHashFuncCircuit<Self::Base>;
type RO = PoseidonRO<Self::Base, Self::Scalar>;
type ROCircuit = PoseidonROCircuit<Self::Base>;
fn vartime_multiscalar_mul(
scalars: &[Self::Scalar],

+ 56
- 96
src/poseidon.rs

@ -1,8 +1,5 @@
//! Poseidon Constants and Poseidon-based RO used in Nova
use super::{
constants::{NUM_CHALLENGE_BITS, NUM_HASH_BITS},
traits::{HashFuncCircuitTrait, HashFuncConstantsTrait, HashFuncTrait},
};
use super::traits::{ROCircuitTrait, ROConstantsTrait, ROTrait};
use bellperson::{
gadgets::{
boolean::{AllocatedBit, Boolean},
@ -29,7 +26,7 @@ where
constants32: PoseidonConstants<Scalar, U32>,
}
impl<Scalar> HashFuncConstantsTrait<Scalar> for PoseidonConstantsCircuit<Scalar>
impl<Scalar> ROConstantsTrait<Scalar> for PoseidonConstantsCircuit<Scalar>
where
Scalar: PrimeField + PrimeFieldBits,
{
@ -46,7 +43,7 @@ where
}
/// A Poseidon-based RO to use outside circuits
pub struct PoseidonHashFunc<Base, Scalar>
pub struct PoseidonRO<Base, Scalar>
where
Base: PrimeField + PrimeFieldBits,
Scalar: PrimeField + PrimeFieldBits,
@ -58,30 +55,7 @@ where
_p: PhantomData<Scalar>,
}
impl<Base, Scalar> PoseidonHashFunc<Base, Scalar>
where
Base: PrimeField + PrimeFieldBits,
Scalar: PrimeField + PrimeFieldBits,
{
fn hash_inner(&self) -> Base {
match self.state.len() {
27 => {
Poseidon::<Base, U27>::new_with_preimage(&self.state, &self.constants.constants27).hash()
}
32 => {
Poseidon::<Base, U32>::new_with_preimage(&self.state, &self.constants.constants32).hash()
}
_ => {
panic!(
"Number of elements in the RO state does not match any of the arities used in Nova: {:?}",
self.state.len()
);
}
}
}
}
impl<Base, Scalar> HashFuncTrait<Base, Scalar> for PoseidonHashFunc<Base, Scalar>
impl<Base, Scalar> ROTrait<Base, Scalar> for PoseidonRO<Base, Scalar>
where
Base: PrimeField + PrimeFieldBits,
Scalar: PrimeField + PrimeFieldBits,
@ -102,28 +76,27 @@ where
}
/// Compute a challenge by hashing the current state
fn get_challenge(&self) -> Scalar {
let hash = self.hash_inner();
// Only keep NUM_CHALLENGE_BITS bits
let bits = hash.to_le_bits();
let mut res = Scalar::zero();
let mut coeff = Scalar::one();
for bit in bits[0..NUM_CHALLENGE_BITS].into_iter() {
if *bit {
res += coeff;
fn squeeze(&self, num_bits: usize) -> Scalar {
let hash = match self.state.len() {
27 => {
Poseidon::<Base, U27>::new_with_preimage(&self.state, &self.constants.constants27).hash()
}
coeff += coeff;
}
res
}
32 => {
Poseidon::<Base, U32>::new_with_preimage(&self.state, &self.constants.constants32).hash()
}
_ => {
panic!(
"Number of elements in the RO state does not match any of the arities used in Nova: {:?}",
self.state.len()
);
}
};
fn get_hash(&self) -> Scalar {
let hash = self.hash_inner();
// Only keep NUM_HASH_BITS bits
// Only return `num_bits`
let bits = hash.to_le_bits();
let mut res = Scalar::zero();
let mut coeff = Scalar::one();
for bit in bits[0..NUM_HASH_BITS].into_iter() {
for bit in bits[0..num_bits].into_iter() {
if *bit {
res += coeff;
}
@ -134,7 +107,7 @@ where
}
/// A Poseidon-based RO gadget to use inside the verifier circuit.
pub struct PoseidonHashFuncCircuit<Scalar>
pub struct PoseidonROCircuit<Scalar>
where
Scalar: PrimeField + PrimeFieldBits,
{
@ -143,15 +116,35 @@ where
constants: PoseidonConstantsCircuit<Scalar>,
}
impl<Scalar> PoseidonHashFuncCircuit<Scalar>
impl<Scalar> ROCircuitTrait<Scalar> for PoseidonROCircuit<Scalar>
where
Scalar: PrimeField + PrimeFieldBits,
{
fn hash_inner<CS>(&mut self, mut cs: CS) -> Result<Vec<AllocatedBit>, SynthesisError>
type Constants = PoseidonConstantsCircuit<Scalar>;
/// Initialize the internal state and set the poseidon constants
fn new(constants: PoseidonConstantsCircuit<Scalar>) -> Self {
Self {
state: Vec::new(),
constants,
}
}
/// Absorb a new number into the state of the oracle
fn absorb(&mut self, e: AllocatedNum<Scalar>) {
self.state.push(e);
}
/// Compute a challenge by hashing the current state
fn squeeze<CS>(
&mut self,
mut cs: CS,
num_bits: usize,
) -> Result<Vec<AllocatedBit>, SynthesisError>
where
CS: ConstraintSystem<Scalar>,
{
let out = match self.state.len() {
let hash = match self.state.len() {
27 => poseidon_hash(
cs.namespace(|| "Poseidon hash"),
self.state.clone(),
@ -170,64 +163,31 @@ where
}
};
// return the hash as a vector of bits
// return the hash as a vector of bits, truncated
Ok(
out
hash
.to_bits_le_strict(cs.namespace(|| "poseidon hash to boolean"))?
.iter()
.map(|boolean| match boolean {
Boolean::Is(ref x) => x.clone(),
_ => panic!("Wrong type of input. We should have never reached there"),
})
.collect(),
.collect::<Vec<AllocatedBit>>()[..num_bits]
.into(),
)
}
}
impl<Scalar> HashFuncCircuitTrait<Scalar> for PoseidonHashFuncCircuit<Scalar>
where
Scalar: PrimeField + PrimeFieldBits,
{
type Constants = PoseidonConstantsCircuit<Scalar>;
/// Initialize the internal state and set the poseidon constants
fn new(constants: PoseidonConstantsCircuit<Scalar>) -> Self {
Self {
state: Vec::new(),
constants,
}
}
/// Absorb a new number into the state of the oracle
fn absorb(&mut self, e: AllocatedNum<Scalar>) {
self.state.push(e);
}
/// Compute a challenge by hashing the current state
fn get_challenge<CS>(&mut self, mut cs: CS) -> Result<Vec<AllocatedBit>, SynthesisError>
where
CS: ConstraintSystem<Scalar>,
{
let bits = self.hash_inner(cs.namespace(|| "hash"))?;
Ok(bits[..NUM_CHALLENGE_BITS].into())
}
fn get_hash<CS>(&mut self, mut cs: CS) -> Result<Vec<AllocatedBit>, SynthesisError>
where
CS: ConstraintSystem<Scalar>,
{
let bits = self.hash_inner(cs.namespace(|| "hash"))?;
Ok(bits[..NUM_HASH_BITS].into())
}
}
#[cfg(test)]
mod tests {
use super::*;
type S = pasta_curves::pallas::Scalar;
type B = pasta_curves::vesta::Scalar;
type G = pasta_curves::pallas::Point;
use crate::{bellperson::solver::SatisfyingAssignment, gadgets::utils::le_bits_to_num};
use crate::{
bellperson::solver::SatisfyingAssignment, constants::NUM_CHALLENGE_BITS,
gadgets::utils::le_bits_to_num,
};
use ff::Field;
use rand::rngs::OsRng;
@ -236,8 +196,8 @@ 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: PoseidonHashFunc<S, B> = PoseidonHashFunc::new(constants.clone());
let mut ro_gadget: PoseidonHashFuncCircuit<S> = PoseidonHashFuncCircuit::new(constants);
let mut ro: PoseidonRO<S, B> = PoseidonRO::new(constants.clone());
let mut ro_gadget: PoseidonROCircuit<S> = PoseidonROCircuit::new(constants);
let mut cs: SatisfyingAssignment<G> = SatisfyingAssignment::new();
for i in 0..27 {
let num = S::random(&mut csprng);
@ -249,8 +209,8 @@ mod tests {
.unwrap();
ro_gadget.absorb(num_gadget);
}
let num = ro.get_challenge();
let num2_bits = ro_gadget.get_challenge(&mut cs).unwrap();
let num = ro.squeeze(NUM_CHALLENGE_BITS);
let num2_bits = ro_gadget.squeeze(&mut cs, NUM_CHALLENGE_BITS).unwrap();
let num2 = le_bits_to_num(&mut cs, num2_bits).unwrap();
assert_eq!(num.to_repr(), num2.get_value().unwrap().to_repr());
}

+ 4
- 4
src/r1cs.rs

@ -5,7 +5,7 @@ use super::{
constants::{BN_LIMB_WIDTH, BN_N_LIMBS, NUM_HASH_BITS},
errors::NovaError,
gadgets::utils::scalar_as_base,
traits::{AbsorbInROTrait, AppendToTranscriptTrait, Group, HashFuncTrait},
traits::{AbsorbInROTrait, AppendToTranscriptTrait, Group, ROTrait},
};
use bellperson_nonnative::{mp::bignat::nat_to_limbs, util::convert::f_to_nat};
use core::cmp::max;
@ -453,7 +453,7 @@ impl AppendToTranscriptTrait for R1CSShape {
}
impl<G: Group> AbsorbInROTrait<G> for R1CSShape<G> {
fn absorb_in_ro(&self, ro: &mut G::HashFunc) {
fn absorb_in_ro(&self, ro: &mut G::RO) {
ro.absorb(scalar_as_base::<G>(self.get_digest()));
}
}
@ -500,7 +500,7 @@ impl AppendToTranscriptTrait for R1CSInstance {
}
impl<G: Group> AbsorbInROTrait<G> for R1CSInstance<G> {
fn absorb_in_ro(&self, ro: &mut G::HashFunc) {
fn absorb_in_ro(&self, ro: &mut G::RO) {
self.comm_W.absorb_in_ro(ro);
for x in &self.X {
ro.absorb(scalar_as_base::<G>(*x));
@ -640,7 +640,7 @@ impl AppendToTranscriptTrait for RelaxedR1CSInstance {
}
impl<G: Group> AbsorbInROTrait<G> for RelaxedR1CSInstance<G> {
fn absorb_in_ro(&self, ro: &mut G::HashFunc) {
fn absorb_in_ro(&self, ro: &mut G::RO) {
self.comm_W.absorb_in_ro(ro);
self.comm_E.absorb_in_ro(ro);
ro.absorb(scalar_as_base::<G>(self.u));

+ 3
- 3
src/traits/circuit.rs

@ -13,8 +13,8 @@ pub trait StepCircuit: Send + Sync + Clone {
z: AllocatedNum<F>,
) -> Result<AllocatedNum<F>, SynthesisError>;
/// Execute the circuit for a computation step and return output
fn compute(&self, z: &F) -> F;
/// return the output of the step when provided with with the step's input
fn output(&self, z: &F) -> F;
}
/// A trivial step circuit that simply returns the input
@ -35,7 +35,7 @@ where
Ok(z)
}
fn compute(&self, z: &F) -> F {
fn output(&self, z: &F) -> F {
*z
}
}

+ 19
- 27
src/traits/mod.rs

@ -39,10 +39,10 @@ pub trait Group:
/// A type that represents a hash function that consumes elements
/// from the base field and squeezes out elements of the scalar field
type HashFunc: HashFuncTrait<Self::Base, Self::Scalar>;
type RO: ROTrait<Self::Base, Self::Scalar>;
/// An alternate implementation of Self::HashFunc in the circuit model
type HashFuncCircuit: HashFuncCircuitTrait<Self::Base>;
/// An alternate implementation of Self::RO in the circuit model
type ROCircuit: ROCircuitTrait<Self::Base>;
/// A method to compute a multiexponentation
fn vartime_multiscalar_mul(
@ -87,7 +87,7 @@ pub trait AppendToTranscriptTrait {
/// A helper trait to absorb different objects in RO
pub trait AbsorbInROTrait<G: Group> {
/// Absorbs the value in the provided RO
fn absorb_in_ro(&self, ro: &mut G::HashFunc);
fn absorb_in_ro(&self, ro: &mut G::RO);
}
/// A helper trait to generate challenges using a transcript object
@ -97,9 +97,9 @@ pub trait ChallengeTrait {
}
/// A helper trait that defines the behavior of a hash function that we use as an RO
pub trait HashFuncTrait<Base, Scalar> {
pub trait ROTrait<Base, Scalar> {
/// A type representing constants/parameters associated with the hash function
type Constants: HashFuncConstantsTrait<Base> + Clone + Send + Sync;
type Constants: ROConstantsTrait<Base> + Clone + Send + Sync;
/// Initializes the hash function
fn new(constants: Self::Constants) -> Self;
@ -107,17 +107,14 @@ pub trait HashFuncTrait {
/// Adds a scalar to the internal state
fn absorb(&mut self, e: Base);
/// Returns a random challenge by hashing the internal state
fn get_challenge(&self) -> Scalar;
/// Returns a hash of the internal state
fn get_hash(&self) -> Scalar;
/// Returns a challenge of `num_bits` by hashing the internal state
fn squeeze(&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
pub trait HashFuncCircuitTrait<Base: PrimeField> {
pub trait ROCircuitTrait<Base: PrimeField> {
/// A type representing constants/parameters associated with the hash function
type Constants: HashFuncConstantsTrait<Base> + Clone + Send + Sync;
type Constants: ROConstantsTrait<Base> + Clone + Send + Sync;
/// Initializes the hash function
fn new(constants: Self::Constants) -> Self;
@ -125,30 +122,25 @@ pub trait HashFuncCircuitTrait {
/// Adds a scalar to the internal state
fn absorb(&mut self, e: AllocatedNum<Base>);
/// Returns a random challenge by hashing the internal state
fn get_challenge<CS>(&mut self, cs: CS) -> Result<Vec<AllocatedBit>, SynthesisError>
where
CS: ConstraintSystem<Base>;
/// Returns a hash of the internal state
fn get_hash<CS>(&mut self, cs: CS) -> Result<Vec<AllocatedBit>, SynthesisError>
/// Returns a challenge of `num_bits` by hashing the internal state
fn squeeze<CS>(&mut self, cs: CS, num_bits: usize) -> Result<Vec<AllocatedBit>, SynthesisError>
where
CS: ConstraintSystem<Base>;
}
/// A helper trait that defines the constants associated with a hash function
pub trait HashFuncConstantsTrait<Base> {
pub trait ROConstantsTrait<Base> {
/// produces constants/parameters associated with the hash function
fn new() -> Self;
}
/// An alias for constants associated with G::HashFunc
pub type HashFuncConstants<G> =
<<G as Group>::HashFunc as HashFuncTrait<<G as Group>::Base, <G as Group>::Scalar>>::Constants;
/// An alias for constants associated with G::RO
pub type ROConstants<G> =
<<G as Group>::RO as ROTrait<<G as Group>::Base, <G as Group>::Scalar>>::Constants;
/// An alias for constants associated with G::HashFuncCircuit
pub type HashFuncConstantsCircuit<G> =
<<G as Group>::HashFuncCircuit as HashFuncCircuitTrait<<G as Group>::Base>>::Constants;
/// An alias for constants associated with G::ROCircuit
pub type ROConstantsCircuit<G> =
<<G as Group>::ROCircuit as ROCircuitTrait<<G as Group>::Base>>::Constants;
/// A helper trait for types with a group operation.
pub trait GroupOps<Rhs = Self, Output = Self>:

Loading…
Cancel
Save