Simplifications in Nova's RO (#98) * rename methods for better clarity * rename * Bump version
@@ -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"
@@ -145,7 +145,7 @@ }
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 {
@@ -146,7 +146,7 @@ }
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 {
@@ -289,7 +289,7 @@ &self.pc,
) } - 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,
@@ -148,7 +148,7 @@
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();
@@ -8,6 +8,7 @@ //! the other into the running instance
use super::{ commitments::Commitment, + constants::NUM_HASH_BITS, gadgets::{ ecc::AllocatedPoint, r1cs::{AllocatedR1CSInstance, AllocatedRelaxedR1CSInstance},@@ -16,7 +17,7 @@ alloc_num_equals, alloc_scalar_as_base, alloc_zero, conditionally_select, le_bits_to_num,
}, }, r1cs::{R1CSInstance, RelaxedR1CSInstance}, - traits::{circuit::StepCircuit, Group, HashFuncCircuitTrait, HashFuncConstantsCircuit}, + traits::{circuit::StepCircuit, Group, ROCircuitTrait, ROConstantsCircuit}, }; use bellperson::{ gadgets::{@@ -91,7 +92,7 @@ G: Group,
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 @@ pub fn new(
params: NovaAugmentedCircuitParams, inputs: Option<NovaAugmentedCircuitInputs<G>>, step_circuit: SC, - ro_consts: HashFuncConstantsCircuit<G>, + ro_consts: ROConstantsCircuit<G>, ) -> Self { Self { params,@@ -221,14 +222,14 @@ 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::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 @@ .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::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 @@ use crate::constants::{BN_LIMB_WIDTH, BN_N_LIMBS};
use crate::{ bellperson::r1cs::{NovaShape, NovaWitness}, poseidon::PoseidonConstantsCircuit, - traits::{circuit::TrivialTestCircuit, HashFuncConstantsTrait}, + traits::{circuit::TrivialTestCircuit, ROConstantsTrait}, }; #[test]@@ -364,8 +365,8 @@ fn test_verification_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); - 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>> =
@@ -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<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);
@@ -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 @@ conditionally_select_bignat, le_bits_to_num,
}, }, r1cs::{R1CSInstance, RelaxedR1CSInstance}, - traits::{Group, HashFuncCircuitTrait, HashFuncConstantsCircuit}, + traits::{Group, ROCircuitTrait, ROConstantsCircuit}, }; use bellperson::{ gadgets::{boolean::Boolean, num::AllocatedNum, Assignment},@@ -60,7 +61,7 @@ Ok(AllocatedR1CSInstance { W, X0, X1 })
} /// 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 @@ /// Absorb the provided instance in the RO
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 @@ mut cs: CS,
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
@@ -26,6 +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 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 @@ G2: Group<Base = <G1 as Group>::Scalar>,
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 @@ NovaAugmentedCircuitParams::new(BN_LIMB_WIDTH, BN_N_LIMBS, true);
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 @@ let r_U_secondary =
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 @@ .r1cs_instance_and_witness(&pp.r1cs_shape_secondary, &pp.r1cs_gens_secondary)
.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 @@ }
// 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 @@ }
// 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 @@
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 @@ // sanity: check the claimed output with a direct computation of the same
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 @@ // sanity: check the claimed output with a direct computation of the same
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 @@
Ok(y) } - fn compute(&self, z: &F) -> F { + fn output(&self, z: &F) -> F { // sanity check let x = *z; let y_pow_5 = {
@@ -2,12 +2,13 @@ //! This module implements a non-interactive folding scheme
#![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(crate) comm_T: CompressedCommitment<G::CompressedGroupElement>,
_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 @@ /// with the guarantee that the folded witness `W` satisfies the folded instance `U`
/// 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 @@ U2: &R1CSInstance<G>,
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 @@ // append `comm_T` to the transcript and obtain a challenge
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 @@ /// with the guarantee that the folded instance `U`
/// 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 @@ let comm_T = self.comm_T.decompress()?;
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 @@
#[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 @@ let mut cs: ShapeCS<G> = ShapeCS::new();
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 @@ }
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 @@ };
// 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>) {
@@ -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 @@ type Base = pallas::Base;
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 @@ type Base = vesta::Base;
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],
@@ -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 @@ constants27: PoseidonConstants<Scalar, U27>,
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 @@ }
} /// 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 @@ constants: PoseidonConstantsCircuit<Base>,
_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 @@ self.state.push(e);
} /// 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() + } + 32 => { + Poseidon::<Base, U32>::new_with_preimage(&self.state, &self.constants.constants32).hash() } - coeff += coeff; - } - res - } + _ => { + 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 @@ }
} /// 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 @@ state: Vec<AllocatedNum<Scalar>>,
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 @@ )
} }; - // 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 @@ 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: 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 @@ .inputize(&mut cs.namespace(|| format!("input {}", i)))
.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()); }
@@ -5,7 +5,7 @@ commitments::{CommitGens, CommitTrait, Commitment},
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<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<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<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));
@@ -13,8 +13,8 @@ cs: &mut CS,
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 @@ ) -> Result<AllocatedNum<F>, SynthesisError> {
Ok(z) } - fn compute(&self, z: &F) -> F { + fn output(&self, z: &F) -> F { *z } }
@@ -39,10 +39,10 @@ type PreprocessedGroupElement: Clone + Send + Sync;
/// 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 @@
/// 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 @@ fn challenge(label: &'static [u8], transcript: &mut Transcript) -> Self;
} /// 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 @@
/// 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 @@
/// 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>: