arnaucube
arnaucube/NovaPublic Self-hosted repository
Base case for second circuit (#60)

* output the incoming instance as the running instance in one of the circuits

* Make some verifier circuit inputs optional (for base case)
iontzialla iontzialla@gmail.com
Sat, 14 May 2022 10:56:57 -0400
commit

9f7c12dbc564edd302e0add657bf8e5de80aa2e3

parent

3193d67bce38a05fc928245faefc5554e81fa8bc

4 files changed, 165 insertions(+), 80 deletions(-)

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

@@ -12,7 +12,9 @@ commitments::Commitment,

gadgets::{ ecc::AllocatedPoint, r1cs::{AllocatedR1CSInstance, AllocatedRelaxedR1CSInstance}, - utils::{alloc_num_equals, alloc_zero, conditionally_select, le_bits_to_num}, + utils::{ + alloc_num_equals, alloc_scalar_as_base, alloc_zero, conditionally_select, le_bits_to_num, + }, }, poseidon::{NovaPoseidonConstants, PoseidonROGadget}, r1cs::{R1CSInstance, RelaxedR1CSInstance},

@@ -32,26 +34,29 @@ #[derive(Debug, Clone)]

pub struct NIFSVerifierCircuitParams { limb_width: usize, n_limbs: usize, + is_primary_circuit: bool, // A boolean indicating if this is the primary circuit } impl NIFSVerifierCircuitParams { #[allow(dead_code)] - pub fn new(limb_width: usize, n_limbs: usize) -> Self { + pub fn new(limb_width: usize, n_limbs: usize, is_primary_circuit: bool) -> Self { Self { limb_width, n_limbs, + is_primary_circuit, } } } +#[derive(Debug)] pub struct NIFSVerifierCircuitInputs<G: Group> { - params: G::Base, // Hash(Shape of u2, Gens for u2). Needed for computing the challenge. + params: G::Scalar, // Hash(Shape of u2, Gens for u2). Needed for computing the challenge. i: G::Base, z0: G::Base, - zi: G::Base, - U: RelaxedR1CSInstance<G>, - u: R1CSInstance<G>, - T: Commitment<G>, + zi: Option<G::Base>, + U: Option<RelaxedR1CSInstance<G>>, + u: Option<R1CSInstance<G>>, + T: Option<Commitment<G>>, } impl<G> NIFSVerifierCircuitInputs<G>

@@ -61,13 +66,13 @@ {

/// Create new inputs/witness for the verification circuit #[allow(dead_code, clippy::too_many_arguments)] pub fn new( - params: G::Base, + params: G::Scalar, i: G::Base, z0: G::Base, - zi: G::Base, - U: RelaxedR1CSInstance<G>, - u: R1CSInstance<G>, - T: Commitment<G>, + zi: Option<G::Base>, + U: Option<RelaxedR1CSInstance<G>>, + u: Option<R1CSInstance<G>>, + T: Option<Commitment<G>>, ) -> Self { Self { params,

@@ -131,46 +136,46 @@ ),

SynthesisError, > { // Allocate the params - let params = AllocatedNum::alloc(cs.namespace(|| "params"), || Ok(self.inputs.get()?.params))?; + let params = alloc_scalar_as_base::<G, _>( + cs.namespace(|| "params"), + self.inputs.get().map_or(None, |inputs| Some(inputs.params)), + )?; // Allocate i let i = AllocatedNum::alloc(cs.namespace(|| "i"), || Ok(self.inputs.get()?.i))?; - // Allocate z0 let z_0 = AllocatedNum::alloc(cs.namespace(|| "z0"), || Ok(self.inputs.get()?.z0))?; - - // Allocate zi - let z_i = AllocatedNum::alloc(cs.namespace(|| "zi"), || Ok(self.inputs.get()?.zi))?; - + // Allocate zi. If inputs.zi is not provided (base case) allocate default value 0 + let z_i = AllocatedNum::alloc(cs.namespace(|| "zi"), || { + Ok(self.inputs.get()?.zi.unwrap_or_else(G::Base::zero)) + })?; // Allocate the running instance let U: AllocatedRelaxedR1CSInstance<G> = AllocatedRelaxedR1CSInstance::alloc( cs.namespace(|| "Allocate U"), - self - .inputs - .get() - .map_or(None, |inputs| Some(inputs.U.clone())), + self.inputs.get().map_or(None, |inputs| { + inputs.U.get().map_or(None, |U| Some(U.clone())) + }), self.params.limb_width, self.params.n_limbs, )?; - // Allocate the instance to be folded in let u = AllocatedR1CSInstance::alloc( cs.namespace(|| "allocate instance u to fold"), - self - .inputs - .get() - .map_or(None, |inputs| Some(inputs.u.clone())), + self.inputs.get().map_or(None, |inputs| { + inputs.u.get().map_or(None, |u| Some(u.clone())) + }), )?; // Allocate T let T = AllocatedPoint::alloc( cs.namespace(|| "allocate T"), - self - .inputs - .get() - .map_or(None, |inputs| Some(inputs.T.comm.to_coordinates())), + self.inputs.get().map_or(None, |inputs| { + inputs + .T + .get() + .map_or(None, |T| Some(T.comm.to_coordinates())) + }), )?; - Ok((params, i, z_0, z_i, U, u, T)) }

@@ -178,12 +183,24 @@ /// Synthesizes base case and returns the new relaxed R1CSInstance

fn synthesize_base_case<CS: ConstraintSystem<<G as Group>::Base>>( &self, mut cs: CS, + u: AllocatedR1CSInstance<G>, ) -> Result<AllocatedRelaxedR1CSInstance<G>, SynthesisError> { - let U_default: AllocatedRelaxedR1CSInstance<G> = AllocatedRelaxedR1CSInstance::default( - cs.namespace(|| "Allocate U_default"), - self.params.limb_width, - self.params.n_limbs, - )?; + let U_default: AllocatedRelaxedR1CSInstance<G> = if self.params.is_primary_circuit { + // The primary circuit just returns the default R1CS instance + AllocatedRelaxedR1CSInstance::default( + cs.namespace(|| "Allocate U_default"), + self.params.limb_width, + self.params.n_limbs, + )? + } else { + // The secondary circuit returns the incoming R1CS instance + AllocatedRelaxedR1CSInstance::from_r1cs_instance( + cs.namespace(|| "Allocate U_default"), + u, + self.params.limb_width, + self.params.n_limbs, + )? + }; Ok(U_default) }

@@ -250,7 +267,7 @@ let zero = alloc_zero(cs.namespace(|| "zero"))?;

let is_base_case = alloc_num_equals(cs.namespace(|| "Check if base case"), &i.clone(), &zero)?; //TODO: maybe optimize this? // Synthesize the circuit for the base case and get the new running instance - let Unew_base = self.synthesize_base_case(cs.namespace(|| "base case"))?; + let Unew_base = self.synthesize_base_case(cs.namespace(|| "base case"), u.clone())?; // Synthesize the circuit for the non-base case and get the new running // instance along with a boolean indicating if all checks have passed

@@ -336,7 +353,6 @@ type G2 = pasta_curves::vesta::Point;

use crate::constants::{BN_LIMB_WIDTH, BN_N_LIMBS}; use crate::{ bellperson::r1cs::{NovaShape, NovaWitness}, - commitments::CommitTrait, traits::HashFuncConstantsTrait, }; use ff::PrimeField;

@@ -361,22 +377,24 @@ }

#[test] fn test_verification_circuit() { - // We experiment with 8 limbs of 32 bits each - let params = NIFSVerifierCircuitParams::new(BN_LIMB_WIDTH, BN_N_LIMBS); - // The first circuit that verifies G2 + // 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 poseidon_constants1: NovaPoseidonConstants<<G2 as Group>::Base> = NovaPoseidonConstants::new(); + let poseidon_constants2: NovaPoseidonConstants<<G1 as Group>::Base> = + NovaPoseidonConstants::new(); + + // Initialize the shape and gens for the primary let circuit1: NIFSVerifierCircuit<G2, TestCircuit<<G2 as Group>::Base>> = NIFSVerifierCircuit::new( - params.clone(), + params1.clone(), None, TestCircuit { _p: Default::default(), }, poseidon_constants1.clone(), ); - - // First create the shape let mut cs: ShapeCS<G1> = ShapeCS::new(); let _ = circuit1.synthesize(&mut cs); let (shape1, gens1) = (cs.r1cs_shape(), cs.r1cs_gens());

@@ -385,19 +403,16 @@ "Circuit1 -> Number of constraints: {}",

cs.num_constraints() ); - // The second circuit that verifies G1 - let poseidon_constants2: NovaPoseidonConstants<<G1 as Group>::Base> = - NovaPoseidonConstants::new(); + // Initialize the shape and gens for the secondary let circuit2: NIFSVerifierCircuit<G1, TestCircuit<<G1 as Group>::Base>> = NIFSVerifierCircuit::new( - params.clone(), + params2.clone(), None, TestCircuit { _p: Default::default(), }, - poseidon_constants2, + poseidon_constants2.clone(), ); - // First create the shape let mut cs: ShapeCS<G2> = ShapeCS::new(); let _ = circuit2.synthesize(&mut cs); let (shape2, gens2) = (cs.r1cs_shape(), cs.r1cs_gens());

@@ -406,35 +421,56 @@ "Circuit2 -> Number of constraints: {}",

cs.num_constraints() ); - let zero = <<G2 as Group>::Base as Field>::zero(); - let zero_fq = <<G2 as Group>::Scalar as Field>::zero(); - let T = vec![<G2 as Group>::Scalar::zero()].commit(&gens2.gens_E); - let w = vec![<G2 as Group>::Scalar::zero()].commit(&gens2.gens_E); - // Now get an assignment - let mut cs: SatisfyingAssignment<G1> = SatisfyingAssignment::new(); - let inputs: NIFSVerifierCircuitInputs<G2> = NIFSVerifierCircuitInputs::new( - <<G2 as Group>::Base as Field>::zero(), // TODO: provide real inputs - zero, // TODO: provide real inputs - zero, // TODO: provide real inputs - zero, // TODO: provide real inputs - RelaxedR1CSInstance::default(&gens2, &shape2), - R1CSInstance::new(&shape2, &w, &[zero_fq, zero_fq]).unwrap(), - T, // TODO: provide real inputs + // Execute the base case for the primary + let zero1 = <<G2 as Group>::Base as Field>::zero(); + let mut cs1: SatisfyingAssignment<G1> = SatisfyingAssignment::new(); + let inputs1: NIFSVerifierCircuitInputs<G2> = NIFSVerifierCircuitInputs::new( + shape2.get_digest(), + zero1, + zero1, // TODO: Provide real input for z0 + None, + None, + None, + None, ); - - let circuit: NIFSVerifierCircuit<G2, TestCircuit<<G2 as Group>::Base>> = + let circuit1: NIFSVerifierCircuit<G2, TestCircuit<<G2 as Group>::Base>> = NIFSVerifierCircuit::new( - params, - Some(inputs), + params1, + Some(inputs1), TestCircuit { _p: Default::default(), }, poseidon_constants1, ); - let _ = circuit.synthesize(&mut cs); - let (inst, witness) = cs.r1cs_instance_and_witness(&shape1, &gens1).unwrap(); + let _ = circuit1.synthesize(&mut cs1); + let (inst1, witness1) = cs1.r1cs_instance_and_witness(&shape1, &gens1).unwrap(); + // Make sure that this is satisfiable + assert!(shape1.is_sat(&gens1, &inst1, &witness1).is_ok()); - // Make sure that this is satisfiable - assert!(shape1.is_sat(&gens1, &inst, &witness).is_ok()); + // Execute the base case for the secondary + let zero2 = <<G1 as Group>::Base as Field>::zero(); + let mut cs2: SatisfyingAssignment<G2> = SatisfyingAssignment::new(); + let inputs2: NIFSVerifierCircuitInputs<G1> = NIFSVerifierCircuitInputs::new( + shape1.get_digest(), + zero2, + zero2, + None, + None, + Some(inst1), + None, + ); + let circuit: NIFSVerifierCircuit<G1, TestCircuit<<G1 as Group>::Base>> = + NIFSVerifierCircuit::new( + params2, + Some(inputs2), + TestCircuit { + _p: Default::default(), + }, + poseidon_constants2, + ); + let _ = circuit.synthesize(&mut cs2); + let (inst2, witness2) = cs2.r1cs_instance_and_witness(&shape2, &gens2).unwrap(); + // Make sure that it is satisfiable + assert!(shape2.is_sat(&gens2, &inst2, &witness2).is_ok()); } }
M src/gadgets/ecc.rs → src/gadgets/ecc.rs

@@ -29,14 +29,19 @@ where

Fp: PrimeField, { /// Allocates a new point on the curve using coordinates provided by `coords`. + /// If coords = None, it allocates the default infinity point pub fn alloc<CS>(mut cs: CS, coords: Option<(Fp, Fp, bool)>) -> Result<Self, SynthesisError> where CS: ConstraintSystem<Fp>, { - let x = AllocatedNum::alloc(cs.namespace(|| "x"), || Ok(coords.unwrap().0))?; - let y = AllocatedNum::alloc(cs.namespace(|| "y"), || Ok(coords.unwrap().1))?; + let x = AllocatedNum::alloc(cs.namespace(|| "x"), || { + Ok(coords.map_or(Fp::zero(), |c| c.0)) + })?; + let y = AllocatedNum::alloc(cs.namespace(|| "y"), || { + Ok(coords.map_or(Fp::zero(), |c| c.0)) + })?; let is_infinity = AllocatedNum::alloc(cs.namespace(|| "is_infinity"), || { - Ok(if coords.unwrap().2 { + Ok(if coords.map_or(true, |c| c.2) { Fp::one() } else { Fp::zero()
M src/gadgets/r1cs.rs → src/gadgets/r1cs.rs

@@ -3,7 +3,7 @@ use crate::{

gadgets::{ ecc::AllocatedPoint, utils::{ - alloc_bignat_constant, alloc_scalar_as_base, conditionally_select, + alloc_bignat_constant, alloc_one, alloc_scalar_as_base, conditionally_select, conditionally_select_bignat, le_bits_to_num, }, },

@@ -114,16 +114,25 @@ cs.namespace(|| "allocate u"),

inst.get().map_or(None, |inst| Some(inst.u)), )?; + // Allocate X0 and X1. If the input instance is None, then allocate default values 0. let X0 = BigNat::alloc_from_nat( cs.namespace(|| "allocate X[0]"), - || Ok(f_to_nat(&inst.get()?.X[0])), + || { + Ok(f_to_nat( + &inst.clone().map_or(G::Scalar::zero(), |inst| inst.X[0]), + )) + }, limb_width, n_limbs, )?; let X1 = BigNat::alloc_from_nat( cs.namespace(|| "allocate X[1]"), - || Ok(f_to_nat(&inst.get()?.X[1])), + || { + Ok(f_to_nat( + &inst.clone().map_or(G::Scalar::zero(), |inst| inst.X[1]), + )) + }, limb_width, n_limbs, )?;

@@ -158,6 +167,41 @@ n_limbs,

)?; Ok(AllocatedRelaxedR1CSInstance { W, E, u, X0, X1 }) + } + + /// Allocates the R1CS Instance as a RelaxedR1CSInstance in the circuit. + /// E = 0, u = 1 + pub fn from_r1cs_instance<CS: ConstraintSystem<<G as Group>::Base>>( + mut cs: CS, + inst: AllocatedR1CSInstance<G>, + limb_width: usize, + n_limbs: usize, + ) -> Result<Self, SynthesisError> { + let E = AllocatedPoint::default(cs.namespace(|| "allocate W"))?; + + let u = alloc_one(cs.namespace(|| "one"))?; + + let X0 = BigNat::from_num( + cs.namespace(|| "allocate X0 from relaxed r1cs"), + Num::from(inst.X0.clone()), + limb_width, + n_limbs, + )?; + + let X1 = BigNat::from_num( + cs.namespace(|| "allocate X1 from relaxed r1cs"), + Num::from(inst.X1.clone()), + limb_width, + n_limbs, + )?; + + Ok(AllocatedRelaxedR1CSInstance { + W: inst.W, + E, + u, + X0, + X1, + }) } /// Absorb the provided instance in the RO
M src/gadgets/utils.rs → src/gadgets/utils.rs

@@ -86,7 +86,7 @@ <G as Group>::Scalar: PrimeFieldBits,

CS: ConstraintSystem<<G as Group>::Base>, { AllocatedNum::alloc(cs.namespace(|| "allocate scalar as base"), || { - let input_bits = input.get()?.clone().to_le_bits(); + let input_bits = input.unwrap_or_else(G::Scalar::zero).clone().to_le_bits(); let mut mult = G::Base::one(); let mut val = G::Base::zero(); for bit in input_bits {