arnaucube
arnaucube/NovaPublic Self-hosted repository
Abstract the RO used in the circuit with traits (#84)

* cleanup RO usage inside the circuit: use traits

* Add a note

* rename types for clarity
Srinath Setty srinath@microsoft.com
Tue, 05 Jul 2022 16:24:12 -0700
commit

6667d2f8b56e34de589e2a80dfd4270a1b5bb60e

parent

0863a732ff7ae49c664594c162fdacdf2cde13bf

7 files changed, 118 insertions(+), 74 deletions(-)

jump to
M src/circuit.rs → src/circuit.rs

@@ -15,9 +15,8 @@ utils::{

alloc_num_equals, alloc_scalar_as_base, alloc_zero, conditionally_select, le_bits_to_num, }, }, - poseidon::{PoseidonROGadget, ROConstantsCircuit}, r1cs::{R1CSInstance, RelaxedR1CSInstance}, - traits::{Group, StepCircuit}, + traits::{Group, HashFuncCircuitTrait, HashFuncConstantsCircuit, StepCircuit}, }; use bellperson::{ gadgets::{

@@ -91,7 +90,7 @@ G: Group,

SC: StepCircuit<G::Base>, { params: NIFSVerifierCircuitParams, - ro_consts: ROConstantsCircuit<G::Base>, + ro_consts: HashFuncConstantsCircuit<G>, inputs: Option<NIFSVerifierCircuitInputs<G>>, step_circuit: SC, // The function that is applied for each step }

@@ -106,7 +105,7 @@ pub fn new(

params: NIFSVerifierCircuitParams, inputs: Option<NIFSVerifierCircuitInputs<G>>, step_circuit: SC, - ro_consts: ROConstantsCircuit<G::Base>, + ro_consts: HashFuncConstantsCircuit<G>, ) -> Self { Self { params,

@@ -221,7 +220,7 @@ u: AllocatedR1CSInstance<G>,

T: AllocatedPoint<G::Base>, ) -> Result<(AllocatedRelaxedR1CSInstance<G>, AllocatedBit), SynthesisError> { // Check that u.x[0] = Hash(params, U, i, z0, zi) - let mut ro: PoseidonROGadget<G::Base> = PoseidonROGadget::new(self.ro_consts.clone()); + let mut ro = G::HashFuncCircuit::new(self.ro_consts.clone()); ro.absorb(params.clone()); ro.absorb(i); ro.absorb(z_0);

@@ -328,7 +327,7 @@ .step_circuit

.synthesize(&mut cs.namespace(|| "F"), z_input)?; // Compute the new hash H(params, Unew, i+1, z0, z_{i+1}) - let mut ro: PoseidonROGadget<G::Base> = PoseidonROGadget::new(self.ro_consts); + let mut ro = G::HashFuncCircuit::new(self.ro_consts); ro.absorb(params); ro.absorb(i_new.clone()); ro.absorb(z_0);

@@ -356,6 +355,7 @@ type G2 = pasta_curves::vesta::Point;

use crate::constants::{BN_LIMB_WIDTH, BN_N_LIMBS}; use crate::{ bellperson::r1cs::{NovaShape, NovaWitness}, + poseidon::PoseidonConstantsCircuit, traits::HashFuncConstantsTrait, }; use ff::PrimeField;

@@ -388,8 +388,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 = NIFSVerifierCircuitParams::new(BN_LIMB_WIDTH, BN_N_LIMBS, true); let params2 = NIFSVerifierCircuitParams::new(BN_LIMB_WIDTH, BN_N_LIMBS, false); - let ro_consts1: ROConstantsCircuit<<G2 as Group>::Base> = ROConstantsCircuit::new(); - let ro_consts2: ROConstantsCircuit<<G1 as Group>::Base> = ROConstantsCircuit::new(); + let ro_consts1: HashFuncConstantsCircuit<G2> = PoseidonConstantsCircuit::new(); + let ro_consts2: HashFuncConstantsCircuit<G1> = PoseidonConstantsCircuit::new(); // Initialize the shape and gens for the primary let circuit1: NIFSVerifierCircuit<G2, TestCircuit<<G2 as Group>::Base>> =
M src/gadgets/r1cs.rs → src/gadgets/r1cs.rs

@@ -7,9 +7,8 @@ alloc_bignat_constant, alloc_one, alloc_scalar_as_base, conditionally_select,

conditionally_select_bignat, le_bits_to_num, }, }, - poseidon::{PoseidonROGadget, ROConstantsCircuit}, r1cs::{R1CSInstance, RelaxedR1CSInstance}, - traits::Group, + traits::{Group, HashFuncCircuitTrait, HashFuncConstantsCircuit}, }; use bellperson::{ gadgets::{boolean::Boolean, num::AllocatedNum, Assignment},

@@ -61,7 +60,7 @@ Ok(AllocatedR1CSInstance { W, X0, X1 })

} /// Absorb the provided instance in the RO - pub fn absorb_in_ro(&self, ro: &mut PoseidonROGadget<G::Base>) { + pub fn absorb_in_ro(&self, ro: &mut G::HashFuncCircuit) { ro.absorb(self.W.x.clone()); ro.absorb(self.W.y.clone()); ro.absorb(self.W.is_infinity.clone());

@@ -208,7 +207,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 PoseidonROGadget<G::Base>, + ro: &mut G::HashFuncCircuit, ) -> Result<(), SynthesisError> { ro.absorb(self.W.x.clone()); ro.absorb(self.W.y.clone());

@@ -263,12 +262,12 @@ mut cs: CS,

params: AllocatedNum<G::Base>, // hash of R1CSShape of F' u: AllocatedR1CSInstance<G>, T: AllocatedPoint<G::Base>, - ro_consts: ROConstantsCircuit<G::Base>, + ro_consts: HashFuncConstantsCircuit<G>, limb_width: usize, n_limbs: usize, ) -> Result<AllocatedRelaxedR1CSInstance<G>, SynthesisError> { // Compute r: - let mut ro: PoseidonROGadget<G::Base> = PoseidonROGadget::new(ro_consts); + let mut ro = G::HashFuncCircuit::new(ro_consts); ro.absorb(params); self.absorb_in_ro(cs.namespace(|| "absorb running instance"), &mut ro)?; u.absorb_in_ro(&mut ro);
M src/lib.rs → src/lib.rs

@@ -33,31 +33,30 @@ use errors::NovaError;

use ff::Field; use gadgets::utils::scalar_as_base; use nifs::NIFS; -use poseidon::ROConstantsCircuit; // TODO: make this a trait so we can use it without the concrete implementation use r1cs::{ R1CSGens, R1CSInstance, R1CSShape, R1CSWitness, RelaxedR1CSInstance, RelaxedR1CSWitness, }; use snark::RelaxedR1CSSNARKTrait; -use traits::{AbsorbInROTrait, Group, HashFuncConstantsTrait, HashFuncTrait, StepCircuit}; - -type ROConstants<G> = - <<G as Group>::HashFunc as HashFuncTrait<<G as Group>::Base, <G as Group>::Scalar>>::Constants; +use traits::{ + AbsorbInROTrait, Group, HashFuncConstants, HashFuncConstantsCircuit, HashFuncConstantsTrait, + HashFuncTrait, StepCircuit, +}; /// A type that holds public parameters of Nova pub struct PublicParams<G1, G2, C1, C2> where G1: Group<Base = <G2 as Group>::Scalar>, G2: Group<Base = <G1 as Group>::Scalar>, - C1: StepCircuit<G1::Scalar> + Clone, - C2: StepCircuit<G2::Scalar> + Clone, + C1: StepCircuit<G1::Scalar>, + C2: StepCircuit<G2::Scalar>, { - ro_consts_primary: ROConstants<G1>, - ro_consts_circuit_primary: ROConstantsCircuit<<G2 as Group>::Base>, + ro_consts_primary: HashFuncConstants<G1>, + ro_consts_circuit_primary: HashFuncConstantsCircuit<G2>, r1cs_gens_primary: R1CSGens<G1>, r1cs_shape_primary: R1CSShape<G1>, r1cs_shape_padded_primary: R1CSShape<G1>, - ro_consts_secondary: ROConstants<G2>, - ro_consts_circuit_secondary: ROConstantsCircuit<<G1 as Group>::Base>, + ro_consts_secondary: HashFuncConstants<G2>, + ro_consts_circuit_secondary: HashFuncConstantsCircuit<G1>, r1cs_gens_secondary: R1CSGens<G2>, r1cs_shape_secondary: R1CSShape<G2>, r1cs_shape_padded_secondary: R1CSShape<G2>,

@@ -71,21 +70,22 @@ impl<G1, G2, C1, C2> PublicParams<G1, G2, C1, C2>

where G1: Group<Base = <G2 as Group>::Scalar>, G2: Group<Base = <G1 as Group>::Scalar>, - C1: StepCircuit<G1::Scalar> + Clone, - C2: StepCircuit<G2::Scalar> + Clone, + C1: StepCircuit<G1::Scalar>, + C2: StepCircuit<G2::Scalar>, { /// Create a new `PublicParams` pub fn setup(c_primary: C1, c_secondary: C2) -> Self { let params_primary = NIFSVerifierCircuitParams::new(BN_LIMB_WIDTH, BN_N_LIMBS, true); let params_secondary = NIFSVerifierCircuitParams::new(BN_LIMB_WIDTH, BN_N_LIMBS, false); - let ro_consts_primary: ROConstants<G1> = ROConstants::<G1>::new(); - let ro_consts_secondary: ROConstants<G2> = ROConstants::<G2>::new(); + let ro_consts_primary: HashFuncConstants<G1> = HashFuncConstants::<G1>::new(); + let ro_consts_secondary: HashFuncConstants<G2> = HashFuncConstants::<G2>::new(); - let ro_consts_circuit_primary: ROConstantsCircuit<<G2 as Group>::Base> = - ROConstantsCircuit::new(); - let ro_consts_circuit_secondary: ROConstantsCircuit<<G1 as Group>::Base> = - ROConstantsCircuit::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(); // Initialize gens for the primary let circuit_primary: NIFSVerifierCircuit<G2, C1> = NIFSVerifierCircuit::new(

@@ -135,8 +135,8 @@ pub struct RecursiveSNARK<G1, G2, C1, C2>

where G1: Group<Base = <G2 as Group>::Scalar>, G2: Group<Base = <G1 as Group>::Scalar>, - C1: StepCircuit<G1::Scalar> + Clone, - C2: StepCircuit<G2::Scalar> + Clone, + C1: StepCircuit<G1::Scalar>, + C2: StepCircuit<G2::Scalar>, { r_W_primary: RelaxedR1CSWitness<G1>, r_U_primary: RelaxedR1CSInstance<G1>,

@@ -156,8 +156,8 @@ impl<G1, G2, C1, C2> RecursiveSNARK<G1, G2, C1, C2>

where G1: Group<Base = <G2 as Group>::Scalar>, G2: Group<Base = <G1 as Group>::Scalar>, - C1: StepCircuit<G1::Scalar> + Clone, - C2: StepCircuit<G2::Scalar> + Clone, + C1: StepCircuit<G1::Scalar>, + C2: StepCircuit<G2::Scalar>, { /// Create a new `RecursiveSNARK` pub fn prove(
M src/nifs.rs → src/nifs.rs

@@ -17,7 +17,7 @@ pub(crate) comm_T: CompressedCommitment<G::CompressedGroupElement>,

_p: PhantomData<G>, } -type ROConstants<G> = +type HashFuncConstants<G> = <<G as Group>::HashFunc as HashFuncTrait<<G as Group>::Base, <G as Group>::Scalar>>::Constants; impl<G: Group> NIFS<G> {

@@ -29,7 +29,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: &ROConstants<G>, + ro_consts: &HashFuncConstants<G>, S: &R1CSShape<G>, U1: &RelaxedR1CSInstance<G>, W1: &RelaxedR1CSWitness<G>,

@@ -78,7 +78,7 @@ /// with the guarantee that the folded instance `U`

/// if and only if `U1` and `U2` are satisfiable. pub fn verify( &self, - ro_consts: &ROConstants<G>, + ro_consts: &HashFuncConstants<G>, S: &R1CSShape<G>, U1: &RelaxedR1CSInstance<G>, U2: &R1CSInstance<G>,
M src/pasta.rs → 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::PoseidonRO, + poseidon::{PoseidonHashFunc, PoseidonHashFuncCircuit}, traits::{ChallengeTrait, CompressedGroup, Group}, }; use digest::{ExtendableOutput, Input};

@@ -40,7 +40,8 @@ type Base = pallas::Base;

type Scalar = pallas::Scalar; type CompressedGroupElement = PallasCompressedElementWrapper; type PreprocessedGroupElement = pallas::Affine; - type HashFunc = PoseidonRO<Self::Base, Self::Scalar>; + type HashFunc = PoseidonHashFunc<Self::Base, Self::Scalar>; + type HashFuncCircuit = PoseidonHashFuncCircuit<Self::Base>; fn vartime_multiscalar_mul( scalars: &[Self::Scalar],

@@ -137,7 +138,8 @@ type Base = vesta::Base;

type Scalar = vesta::Scalar; type CompressedGroupElement = VestaCompressedElementWrapper; type PreprocessedGroupElement = vesta::Affine; - type HashFunc = PoseidonRO<Self::Base, Self::Scalar>; + type HashFunc = PoseidonHashFunc<Self::Base, Self::Scalar>; + type HashFuncCircuit = PoseidonHashFuncCircuit<Self::Base>; fn vartime_multiscalar_mul( scalars: &[Self::Scalar],
M src/poseidon.rs → src/poseidon.rs

@@ -1,7 +1,7 @@

//! Poseidon Constants and Poseidon-based RO used in Nova use super::{ constants::{NUM_CHALLENGE_BITS, NUM_HASH_BITS}, - traits::{HashFuncConstantsTrait, HashFuncTrait}, + traits::{HashFuncCircuitTrait, HashFuncConstantsTrait, HashFuncTrait}, }; use bellperson::{ gadgets::{

@@ -21,7 +21,7 @@ };

/// All Poseidon Constants that are used in Nova #[derive(Clone)] -pub struct ROConstantsCircuit<Scalar> +pub struct PoseidonConstantsCircuit<Scalar> where Scalar: PrimeField, {

@@ -29,7 +29,7 @@ constants27: PoseidonConstants<Scalar, U27>,

constants32: PoseidonConstants<Scalar, U32>, } -impl<Scalar> HashFuncConstantsTrait<Scalar> for ROConstantsCircuit<Scalar> +impl<Scalar> HashFuncConstantsTrait<Scalar> for PoseidonConstantsCircuit<Scalar> where Scalar: PrimeField + PrimeFieldBits, {

@@ -46,7 +46,7 @@ }

} /// A Poseidon-based RO to use outside circuits -pub struct PoseidonRO<Base, Scalar> +pub struct PoseidonHashFunc<Base, Scalar> where Base: PrimeField + PrimeFieldBits, Scalar: PrimeField + PrimeFieldBits,

@@ -54,11 +54,11 @@ {

// Internal State state: Vec<Base>, // Constants for Poseidon - constants: ROConstantsCircuit<Base>, + constants: PoseidonConstantsCircuit<Base>, _p: PhantomData<Scalar>, } -impl<Base, Scalar> PoseidonRO<Base, Scalar> +impl<Base, Scalar> PoseidonHashFunc<Base, Scalar> where Base: PrimeField + PrimeFieldBits, Scalar: PrimeField + PrimeFieldBits,

@@ -81,14 +81,14 @@ }

} } -impl<Base, Scalar> HashFuncTrait<Base, Scalar> for PoseidonRO<Base, Scalar> +impl<Base, Scalar> HashFuncTrait<Base, Scalar> for PoseidonHashFunc<Base, Scalar> where Base: PrimeField + PrimeFieldBits, Scalar: PrimeField + PrimeFieldBits, { - type Constants = ROConstantsCircuit<Base>; + type Constants = PoseidonConstantsCircuit<Base>; - fn new(constants: ROConstantsCircuit<Base>) -> Self { + fn new(constants: PoseidonConstantsCircuit<Base>) -> Self { Self { state: Vec::new(), constants,

@@ -134,32 +134,19 @@ }

} /// A Poseidon-based RO gadget to use inside the verifier circuit. -pub struct PoseidonROGadget<Scalar> +pub struct PoseidonHashFuncCircuit<Scalar> where Scalar: PrimeField + PrimeFieldBits, { // Internal state state: Vec<AllocatedNum<Scalar>>, - constants: ROConstantsCircuit<Scalar>, + constants: PoseidonConstantsCircuit<Scalar>, } -impl<Scalar> PoseidonROGadget<Scalar> +impl<Scalar> PoseidonHashFuncCircuit<Scalar> where Scalar: PrimeField + PrimeFieldBits, { - /// Initialize the internal state and set the poseidon constants - pub fn new(constants: ROConstantsCircuit<Scalar>) -> Self { - Self { - state: Vec::new(), - constants, - } - } - - /// Absorb a new number into the state of the oracle - pub fn absorb(&mut self, e: AllocatedNum<Scalar>) { - self.state.push(e); - } - fn hash_inner<CS>(&mut self, mut cs: CS) -> Result<Vec<AllocatedBit>, SynthesisError> where CS: ConstraintSystem<Scalar>,

@@ -195,9 +182,29 @@ })

.collect(), ) } +} + +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 - pub fn get_challenge<CS>(&mut self, mut cs: CS) -> Result<Vec<AllocatedBit>, SynthesisError> + fn get_challenge<CS>(&mut self, mut cs: CS) -> Result<Vec<AllocatedBit>, SynthesisError> where CS: ConstraintSystem<Scalar>, {

@@ -205,7 +212,7 @@ let bits = self.hash_inner(cs.namespace(|| "hash"))?;

Ok(bits[..NUM_CHALLENGE_BITS].into()) } - pub fn get_hash<CS>(&mut self, mut cs: CS) -> Result<Vec<AllocatedBit>, SynthesisError> + fn get_hash<CS>(&mut self, mut cs: CS) -> Result<Vec<AllocatedBit>, SynthesisError> where CS: ConstraintSystem<Scalar>, {

@@ -228,9 +235,9 @@ #[test]

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 = ROConstantsCircuit::new(); - let mut ro: PoseidonRO<S, B> = PoseidonRO::new(constants.clone()); - let mut ro_gadget: PoseidonROGadget<S> = PoseidonROGadget::new(constants); + 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 cs: SatisfyingAssignment<G> = SatisfyingAssignment::new(); for i in 0..27 { let num = S::random(&mut csprng);
M src/traits.rs → src/traits.rs

@@ -1,5 +1,8 @@

//! This module defines various traits required by the users of the library to implement. -use bellperson::{gadgets::num::AllocatedNum, ConstraintSystem, SynthesisError}; +use bellperson::{ + gadgets::{boolean::AllocatedBit, num::AllocatedNum}, + ConstraintSystem, SynthesisError, +}; use core::{ fmt::Debug, ops::{Add, AddAssign, Mul, MulAssign, Sub, SubAssign},

@@ -37,6 +40,9 @@

/// 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>; + + /// An alternate implementation of Self::HashFunc in the circuit model + type HashFuncCircuit: HashFuncCircuitTrait<Self::Base>; /// A method to compute a multiexponentation fn vartime_multiscalar_mul(

@@ -108,11 +114,41 @@ /// Returns a hash of the internal state

fn get_hash(&self) -> 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> { + /// A type representing constants/parameters associated with the hash function + type Constants: HashFuncConstantsTrait<Base> + Clone + Send + Sync; + + /// Initializes the hash function + fn new(constants: Self::Constants) -> Self; + + /// 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> + where + CS: ConstraintSystem<Base>; +} + /// A helper trait that defines the constants associated with a hash function pub trait HashFuncConstantsTrait<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::HashFuncCircuit +pub type HashFuncConstantsCircuit<G> = + <<G as Group>::HashFuncCircuit as HashFuncCircuitTrait<<G as Group>::Base>>::Constants; /// A helper trait for types with a group operation. pub trait GroupOps<Rhs = Self, Output = Self>: