Browse Source

Feature/f circuit multiple in outs (#35)

* extend FCircuit to work with multiple ins & outs

* refactor FCircuit trait to work with io for multiple frontends support
main
arnaucube 10 months ago
committed by GitHub
parent
commit
6d919d7a5b
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 90 additions and 74 deletions
  1. +84
    -71
      src/folding/nova/circuits.rs
  2. +6
    -3
      src/folding/nova/mod.rs

+ 84
- 71
src/folding/nova/circuits.rs

@ -15,6 +15,7 @@ use ark_r1cs_std::{
ToConstraintFieldGadget,
};
use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystemRef, Namespace, SynthesisError};
use ark_std::fmt::Debug;
use ark_std::Zero;
use core::{borrow::Borrow, marker::PhantomData};
@ -85,11 +86,14 @@ where
self,
crh_params: &CRHParametersVar<CF1<C>>,
i: FpVar<CF1<C>>,
z_0: FpVar<CF1<C>>,
z_i: FpVar<CF1<C>>,
z_0: Vec<FpVar<CF1<C>>>,
z_i: Vec<FpVar<CF1<C>>>,
) -> Result<FpVar<CF1<C>>, SynthesisError> {
let input = vec![
vec![i, z_0, z_i, self.u],
vec![i],
z_0,
z_i,
vec![self.u],
self.x,
self.cmE.x.to_constraint_field()?,
self.cmE.y.to_constraint_field()?,
@ -206,18 +210,24 @@ where
/// FCircuit defines the trait of the circuit of the F function, which is the one being executed
/// inside the agmented F' function.
pub trait FCircuit<F: PrimeField>: Copy {
/// method that returns z_i (input), z_{i+1} (output)
fn public(self) -> (F, F);
/// method that computes the next state values in place, assigning z_{i+1} into z_i, and
pub trait FCircuit<F: PrimeField>: Clone + Copy + Debug {
/// computes the next state values in place, assigning z_{i+1} into z_i, and
/// computing the new z_i
fn step_native(&mut self);
fn step_circuit(
fn step_native(
// this method uses self, so that each FCircuit implementation (and different frontends)
// can hold a state if needed to store data to compute the next state.
self,
z_i: Vec<F>,
) -> Vec<F>;
/// generates the constraints for the step of F for the given z_i
fn generate_step_constraints(
// this method uses self, so that each FCircuit implementation (and different frontends)
// can hold a state if needed to store data to generate the constraints.
self,
cs: ConstraintSystemRef<F>,
z_i: FpVar<F>,
) -> Result<FpVar<F>, SynthesisError>;
z_i: Vec<FpVar<F>>,
) -> Result<Vec<FpVar<F>>, SynthesisError>;
}
/// AugmentedFCircuit implements the F' circuit (augmented F) defined in
@ -226,8 +236,8 @@ pub trait FCircuit: Copy {
pub struct AugmentedFCircuit<C: CurveGroup, FC: FCircuit<CF1<C>>> {
pub poseidon_config: PoseidonConfig<CF1<C>>,
pub i: Option<CF1<C>>,
pub z_0: Option<C::ScalarField>,
pub z_i: Option<C::ScalarField>,
pub z_0: Option<Vec<C::ScalarField>>,
pub z_i: Option<Vec<C::ScalarField>>,
pub u_i: Option<CommittedInstance<C>>,
pub U_i: Option<CommittedInstance<C>>,
pub U_i1: Option<CommittedInstance<C>>,
@ -237,6 +247,25 @@ pub struct AugmentedFCircuit>> {
pub x: Option<CF1<C>>, // public inputs (u_{i+1}.x)
}
impl<C: CurveGroup, FC: FCircuit<CF1<C>>> AugmentedFCircuit<C, FC> {
#[allow(dead_code)] // TMP while IVC does not use this method
fn empty(poseidon_config: &PoseidonConfig<CF1<C>>, F_circuit: FC) -> Self {
Self {
poseidon_config: poseidon_config.clone(),
i: None,
z_0: None,
z_i: None,
u_i: None,
U_i: None,
U_i1: None,
cmT: None,
r: None,
F: F_circuit,
x: None,
}
}
}
impl<C: CurveGroup, FC: FCircuit<CF1<C>>> ConstraintSynthesizer<CF1<C>> for AugmentedFCircuit<C, FC>
where
C: CurveGroup,
@ -246,15 +275,15 @@ where
fn generate_constraints(self, cs: ConstraintSystemRef<CF1<C>>) -> Result<(), SynthesisError> {
let i =
FpVar::<CF1<C>>::new_witness(cs.clone(), || Ok(self.i.unwrap_or_else(CF1::<C>::zero)))?;
let z_0 = FpVar::<CF1<C>>::new_witness(cs.clone(), || {
Ok(self.z_0.unwrap_or_else(CF1::<C>::zero))
let z_0 = Vec::<FpVar<CF1<C>>>::new_witness(cs.clone(), || {
Ok(self.z_0.unwrap_or_else(|| vec![CF1::<C>::zero()]))
})?;
let z_i = FpVar::<CF1<C>>::new_witness(cs.clone(), || {
Ok(self.z_i.unwrap_or_else(CF1::<C>::zero))
let z_i = Vec::<FpVar<CF1<C>>>::new_witness(cs.clone(), || {
Ok(self.z_i.unwrap_or_else(|| vec![CF1::<C>::zero()]))
})?;
// get z_{i+1} from the F circuit
let z_i1 = self.F.step_circuit(cs.clone(), z_i.clone())?;
let z_i1 = self.F.generate_step_constraints(cs.clone(), z_i.clone())?;
let u_dummy_native = CommittedInstance {
cmE: C::zero(),
@ -359,25 +388,21 @@ mod tests {
/// used as the state. `z_i` is used as `x`, and `z_{i+1}` is used as `y`, and at the next
/// step, `z_{i+1}` will be assigned to `z_i`, and a new `z+{i+1}` will be computted.
pub struct TestFCircuit<F: PrimeField> {
z_i: F, // z_i
z_i1: F, // z_{i+1}
_f: PhantomData<F>,
}
impl<F: PrimeField> FCircuit<F> for TestFCircuit<F> {
fn public(self) -> (F, F) {
(self.z_i, self.z_i1)
fn step_native(self, z_i: Vec<F>) -> Vec<F> {
vec![z_i[0] * z_i[0] * z_i[0] + z_i[0] + F::from(5_u32)]
}
fn step_native(&mut self) {
self.z_i = self.z_i1;
self.z_i1 = self.z_i * self.z_i * self.z_i + self.z_i + F::from(5_u32);
}
fn step_circuit(
fn generate_step_constraints(
self,
cs: ConstraintSystemRef<F>,
z_i: FpVar<F>,
) -> Result<FpVar<F>, SynthesisError> {
z_i: Vec<FpVar<F>>,
) -> Result<Vec<FpVar<F>>, SynthesisError> {
let five = FpVar::<F>::new_constant(cs.clone(), F::from(5u32))?;
let z_i = z_i[0].clone();
Ok(&z_i * &z_i * &z_i + &z_i + &five)
Ok(vec![&z_i * &z_i * &z_i + &z_i + &five])
}
}
@ -499,8 +524,8 @@ mod tests {
let poseidon_config = poseidon_test_config::<Fr>();
let i = Fr::from(3_u32);
let z_0 = Fr::from(3_u32);
let z_i = Fr::from(3_u32);
let z_0 = vec![Fr::from(3_u32)];
let z_i = vec![Fr::from(3_u32)];
let ci = CommittedInstance::<Projective> {
cmE: Projective::rand(&mut rng),
u: Fr::rand(&mut rng),
@ -509,13 +534,15 @@ mod tests {
};
// compute the CommittedInstance hash natively
let h = ci.hash(&poseidon_config, i, z_0, z_i).unwrap();
let h = ci
.hash(&poseidon_config, i, z_0.clone(), z_i.clone())
.unwrap();
let cs = ConstraintSystem::<Fr>::new_ref();
let iVar = FpVar::<Fr>::new_witness(cs.clone(), || Ok(i)).unwrap();
let z_0Var = FpVar::<Fr>::new_witness(cs.clone(), || Ok(z_0)).unwrap();
let z_iVar = FpVar::<Fr>::new_witness(cs.clone(), || Ok(z_i)).unwrap();
let z_0Var = Vec::<FpVar<Fr>>::new_witness(cs.clone(), || Ok(z_0.clone())).unwrap();
let z_iVar = Vec::<FpVar<Fr>>::new_witness(cs.clone(), || Ok(z_i.clone())).unwrap();
let ciVar =
CommittedInstanceVar::<Projective>::new_witness(cs.clone(), || Ok(ci.clone())).unwrap();
@ -545,23 +572,9 @@ mod tests {
let cs = ConstraintSystem::<Fr>::new_ref();
// prepare the circuit to obtain its R1CS
let test_F_circuit_dummy = TestFCircuit::<Fr> {
z_i: Fr::zero(),
z_i1: Fr::zero(),
};
let mut augmented_F_circuit = AugmentedFCircuit::<Projective, TestFCircuit<Fr>> {
poseidon_config: poseidon_config.clone(),
i: None,
z_0: None,
z_i: None,
u_i: None,
U_i: None,
U_i1: None,
cmT: None,
r: None,
F: test_F_circuit_dummy,
x: None,
};
let F_circuit = TestFCircuit::<Fr> { _f: PhantomData };
let mut augmented_F_circuit =
AugmentedFCircuit::<Projective, TestFCircuit<Fr>>::empty(&poseidon_config, F_circuit);
augmented_F_circuit
.generate_constraints(cs.clone())
.unwrap();
@ -576,13 +589,10 @@ mod tests {
let pedersen_params = Pedersen::<Projective>::new_params(&mut rng, r1cs.A.n_rows);
// first step
let z_0 = Fr::from(3_u32);
let mut z_i = z_0;
let mut z_i1 = Fr::from(35_u32);
// set the circuit to be folded with z_i=z_0=3 and z_{i+1}=35 (initial values)
let mut test_F_circuit = TestFCircuit::<Fr> { z_i, z_i1 };
// first step, set z_i=z_0=3 and z_{i+1}=35 (initial values)
let z_0 = vec![Fr::from(3_u32)];
let mut z_i = z_0.clone();
let mut z_i1 = vec![Fr::from(35_u32)];
let w_dummy = Witness::<Projective>::new(vec![Fr::zero(); F_witness_len], r1cs.A.n_rows);
let u_dummy = CommittedInstance::<Projective>::dummy(x.len());
@ -612,20 +622,22 @@ mod tests {
if i == Fr::zero() {
// base case: i=0, z_i=z_0, U_i = U_d := dummy instance
// u_1.x = H(1, z_0, z_i, U_i)
u_i1_x = U_i.hash(&poseidon_config, Fr::one(), z_0, z_i1).unwrap();
u_i1_x = U_i
.hash(&poseidon_config, Fr::one(), z_0.clone(), z_i1.clone())
.unwrap();
// base case
augmented_F_circuit = AugmentedFCircuit::<Projective, TestFCircuit<Fr>> {
poseidon_config: poseidon_config.clone(),
i: Some(i), // = 0
z_0: Some(z_0), // = z_i=3
z_i: Some(z_i),
i: Some(i), // = 0
z_0: Some(z_0.clone()), // = z_i=3
z_i: Some(z_i.clone()),
u_i: Some(u_i.clone()), // = dummy
U_i: Some(U_i.clone()), // = dummy
U_i1: Some(U_i1.clone()), // = dummy
cmT: Some(cmT),
r: Some(Fr::one()),
F: test_F_circuit,
F: F_circuit,
x: Some(u_i1_x),
};
} else {
@ -654,20 +666,20 @@ mod tests {
// folded instance output (public input, x)
// u_{i+1}.x = H(i+1, z_0, z_{i+1}, U_{i+1})
u_i1_x = U_i1
.hash(&poseidon_config, i + Fr::one(), z_0, z_i1)
.hash(&poseidon_config, i + Fr::one(), z_0.clone(), z_i1.clone())
.unwrap();
augmented_F_circuit = AugmentedFCircuit::<Projective, TestFCircuit<Fr>> {
poseidon_config: poseidon_config.clone(),
i: Some(i),
z_0: Some(z_0),
z_i: Some(z_i),
z_0: Some(z_0.clone()),
z_i: Some(z_i.clone()),
u_i: Some(u_i),
U_i: Some(U_i.clone()),
U_i1: Some(U_i1.clone()),
cmT: Some(cmT),
r: Some(r_Fr),
F: test_F_circuit,
F: F_circuit,
x: Some(u_i1_x),
};
}
@ -702,8 +714,9 @@ mod tests {
// set values for next iteration
i += Fr::one();
test_F_circuit.step_native(); // advance the F circuit state
(z_i, z_i1) = test_F_circuit.public();
// advance the F circuit state
z_i = z_i1.clone();
z_i1 = F_circuit.step_native(z_i.clone());
U_i = U_i1.clone();
W_i = W_i1.clone();
}

+ 6
- 3
src/folding/nova/mod.rs

@ -46,8 +46,8 @@ where
&self,
poseidon_config: &PoseidonConfig<C::ScalarField>,
i: C::ScalarField,
z_0: C::ScalarField,
z_i: C::ScalarField,
z_0: Vec<C::ScalarField>,
z_i: Vec<C::ScalarField>,
) -> Result<C::ScalarField, Error> {
let (cmE_x, cmE_y) = point_to_nonnative_limbs::<C>(self.cmE)?;
let (cmW_x, cmW_y) = point_to_nonnative_limbs::<C>(self.cmW)?;
@ -55,7 +55,10 @@ where
Ok(CRH::<C::ScalarField>::evaluate(
poseidon_config,
vec![
vec![i, z_0, z_i, self.u],
vec![i],
z_0,
z_i,
vec![self.u],
self.x.clone(),
cmE_x,
cmE_y,

Loading…
Cancel
Save