mirror of
https://github.com/arnaucube/sonobe.git
synced 2026-01-07 14:31:31 +01:00
Protogalaxy based IVC (#123)
* Parallelize vector and matrix operations * Implement convenient methods for `NonNativeAffineVar` * Return `L_X_evals` and intermediate `phi_star`s from ProtoGalaxy prover. These values will be used as hints to the augmented circuit * Correctly use number of variables, number of constraints, and `t` * Fix the size of `F_coeffs` and `K_coeffs` for in-circuit consistency * Improve prover's performance * Make `prepare_inputs` generic * Remove redundant parameters in verifier * Move `eval_f` to arith * `u` is unnecessary in ProtoGalaxy * Convert `RelaxedR1CS` to a trait that can be used in both Nova and ProtoGalaxy * Implement several traits for ProtoGalaxy * Move `FCircuit` impls to `utils.rs` and add `DummyCircuit` * `AugmentedFCircuit` and ProtoGalaxy-based IVC * Add explanations about IVC prover and in-circuit operations * Avoid using unstable features * Rename `PROTOGALAXY` to `PG` to make clippy happy * Fix merge conflicts in `RelaxedR1CS::sample` * Fix merge conflicts in `CycleFoldCircuit` * Swap `m` and `n` for protogalaxy * Add `#[cfg(test)]` to test-only util circuits * Prefer unit struct over empty struct * Add documents to `AugmentedFCircuit` for ProtoGalaxy * Fix the names for CycleFold cricuits in ProtoGalaxy * Fix usize conversion when targeting wasm * Restrict the visibility of fields in `AugmentedFCircuit` to `pub(super)` * Make CycleFold circuits and configs public * Add docs for `ProverParams` and `VerifierParams` * Refactor `pow_i` * Fix imports * Remove lint reasons * Fix type inference
This commit is contained in:
@@ -36,8 +36,7 @@ pub struct CCS<F: PrimeField> {
|
||||
}
|
||||
|
||||
impl<F: PrimeField> Arith<F> for CCS<F> {
|
||||
/// check that a CCS structure is satisfied by a z vector. Only for testing.
|
||||
fn check_relation(&self, z: &[F]) -> Result<(), Error> {
|
||||
fn eval_relation(&self, z: &[F]) -> Result<Vec<F>, Error> {
|
||||
let mut result = vec![F::zero(); self.m];
|
||||
|
||||
for i in 0..self.q {
|
||||
@@ -57,14 +56,7 @@ impl<F: PrimeField> Arith<F> for CCS<F> {
|
||||
result = vec_add(&result, &c_M_j_z)?;
|
||||
}
|
||||
|
||||
// make sure the final vector is all zeroes
|
||||
for e in result {
|
||||
if !e.is_zero() {
|
||||
return Err(Error::NotSatisfied);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
fn params_to_le_bytes(&self) -> Vec<u8> {
|
||||
@@ -113,7 +105,10 @@ impl<F: PrimeField> CCS<F> {
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use super::*;
|
||||
use crate::arith::r1cs::tests::{get_test_r1cs, get_test_z as r1cs_get_test_z};
|
||||
use crate::{
|
||||
arith::r1cs::tests::{get_test_r1cs, get_test_z as r1cs_get_test_z},
|
||||
utils::vec::is_zero_vec,
|
||||
};
|
||||
use ark_pallas::Fr;
|
||||
|
||||
pub fn get_test_ccs<F: PrimeField>() -> CCS<F> {
|
||||
@@ -124,9 +119,22 @@ pub mod tests {
|
||||
r1cs_get_test_z(input)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_eval_ccs_relation() {
|
||||
let ccs = get_test_ccs::<Fr>();
|
||||
let mut z = get_test_z(3);
|
||||
|
||||
let f_w = ccs.eval_relation(&z).unwrap();
|
||||
assert!(is_zero_vec(&f_w));
|
||||
|
||||
z[1] = Fr::from(111);
|
||||
let f_w = ccs.eval_relation(&z).unwrap();
|
||||
assert!(!is_zero_vec(&f_w));
|
||||
}
|
||||
|
||||
/// Test that a basic CCS relation can be satisfied
|
||||
#[test]
|
||||
fn test_ccs_relation() {
|
||||
fn test_check_ccs_relation() {
|
||||
let ccs = get_test_ccs::<Fr>();
|
||||
let z = get_test_z(3);
|
||||
|
||||
|
||||
@@ -6,8 +6,21 @@ pub mod ccs;
|
||||
pub mod r1cs;
|
||||
|
||||
pub trait Arith<F: PrimeField> {
|
||||
/// Checks that the given Arith structure is satisfied by a z vector. Used only for testing.
|
||||
fn check_relation(&self, z: &[F]) -> Result<(), Error>;
|
||||
/// Evaluate the given Arith structure at `z`, a vector of assignments, and
|
||||
/// return the evaluation.
|
||||
fn eval_relation(&self, z: &[F]) -> Result<Vec<F>, Error>;
|
||||
|
||||
/// Checks that the given Arith structure is satisfied by a z vector, i.e.,
|
||||
/// if the evaluation is a zero vector
|
||||
///
|
||||
/// Used only for testing.
|
||||
fn check_relation(&self, z: &[F]) -> Result<(), Error> {
|
||||
if self.eval_relation(z)?.iter().all(|f| f.is_zero()) {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::NotSatisfied)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the bytes that represent the parameters, that is, the matrices sizes, the amount of
|
||||
/// public inputs, etc, without the matrices/polynomials values.
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
use crate::commitment::CommitmentScheme;
|
||||
use crate::folding::nova::{CommittedInstance, Witness};
|
||||
use crate::RngCore;
|
||||
use ark_crypto_primitives::sponge::Absorb;
|
||||
use ark_ec::{CurveGroup, Group};
|
||||
use ark_ec::CurveGroup;
|
||||
use ark_ff::PrimeField;
|
||||
use ark_relations::r1cs::ConstraintSystem;
|
||||
use ark_serialize::{CanonicalDeserialize, CanonicalSerialize};
|
||||
use ark_std::rand::Rng;
|
||||
|
||||
use super::Arith;
|
||||
use crate::utils::vec::{hadamard, mat_vec_mul, vec_add, vec_scalar_mul, vec_sub, SparseMatrix};
|
||||
use crate::utils::vec::{hadamard, mat_vec_mul, vec_scalar_mul, vec_sub, SparseMatrix};
|
||||
use crate::Error;
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)]
|
||||
@@ -21,16 +19,24 @@ pub struct R1CS<F: PrimeField> {
|
||||
}
|
||||
|
||||
impl<F: PrimeField> Arith<F> for R1CS<F> {
|
||||
/// check that a R1CS structure is satisfied by a z vector. Only for testing.
|
||||
fn check_relation(&self, z: &[F]) -> Result<(), Error> {
|
||||
fn eval_relation(&self, z: &[F]) -> Result<Vec<F>, Error> {
|
||||
if z.len() != self.A.n_cols {
|
||||
return Err(Error::NotSameLength(
|
||||
"z.len()".to_string(),
|
||||
z.len(),
|
||||
"number of variables in R1CS".to_string(),
|
||||
self.A.n_cols,
|
||||
));
|
||||
}
|
||||
|
||||
let Az = mat_vec_mul(&self.A, z)?;
|
||||
let Bz = mat_vec_mul(&self.B, z)?;
|
||||
let Cz = mat_vec_mul(&self.C, z)?;
|
||||
// Multiply Cz by z[0] (u) here, allowing this method to be reused for
|
||||
// both relaxed and unrelaxed R1CS.
|
||||
let uCz = vec_scalar_mul(&Cz, &z[0]);
|
||||
let AzBz = hadamard(&Az, &Bz)?;
|
||||
if AzBz != Cz {
|
||||
return Err(Error::NotSatisfied);
|
||||
}
|
||||
Ok(())
|
||||
vec_sub(&AzBz, &uCz)
|
||||
}
|
||||
|
||||
fn params_to_le_bytes(&self) -> Vec<u8> {
|
||||
@@ -65,55 +71,50 @@ impl<F: PrimeField> R1CS<F> {
|
||||
pub fn split_z(&self, z: &[F]) -> (Vec<F>, Vec<F>) {
|
||||
(z[self.l + 1..].to_vec(), z[1..self.l + 1].to_vec())
|
||||
}
|
||||
}
|
||||
|
||||
/// converts the R1CS instance into a RelaxedR1CS as described in
|
||||
/// [Nova](https://eprint.iacr.org/2021/370.pdf) section 4.1.
|
||||
pub fn relax(self) -> RelaxedR1CS<F> {
|
||||
RelaxedR1CS::<F> {
|
||||
l: self.l,
|
||||
E: vec![F::zero(); self.A.n_rows],
|
||||
A: self.A,
|
||||
B: self.B,
|
||||
C: self.C,
|
||||
u: F::one(),
|
||||
pub trait RelaxedR1CS<C: CurveGroup, W, U>: Arith<C::ScalarField> {
|
||||
/// returns a dummy running instance (Witness and CommittedInstance) for the current R1CS structure
|
||||
fn dummy_running_instance(&self) -> (W, U);
|
||||
|
||||
/// returns a dummy incoming instance (Witness and CommittedInstance) for the current R1CS structure
|
||||
fn dummy_incoming_instance(&self) -> (W, U);
|
||||
|
||||
/// checks if the given instance is relaxed
|
||||
fn is_relaxed(w: &W, u: &U) -> bool;
|
||||
|
||||
/// extracts `z`, the vector of variables, from the given Witness and CommittedInstance
|
||||
fn extract_z(w: &W, u: &U) -> Vec<C::ScalarField>;
|
||||
|
||||
/// checks if the computed error terms correspond to the actual one in `w`
|
||||
/// or `u`
|
||||
fn check_error_terms(w: &W, u: &U, e: Vec<C::ScalarField>) -> Result<(), Error>;
|
||||
|
||||
/// checks the tight (unrelaxed) R1CS relation
|
||||
fn check_tight_relation(&self, w: &W, u: &U) -> Result<(), Error> {
|
||||
if Self::is_relaxed(w, u) {
|
||||
return Err(Error::R1CSUnrelaxedFail);
|
||||
}
|
||||
|
||||
let z = Self::extract_z(w, u);
|
||||
self.check_relation(&z)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub struct RelaxedR1CS<F: PrimeField> {
|
||||
pub l: usize, // io len
|
||||
pub A: SparseMatrix<F>,
|
||||
pub B: SparseMatrix<F>,
|
||||
pub C: SparseMatrix<F>,
|
||||
pub u: F,
|
||||
pub E: Vec<F>,
|
||||
}
|
||||
|
||||
impl<F: PrimeField> RelaxedR1CS<F> {
|
||||
/// check that a RelaxedR1CS structure is satisfied by a z vector. Only for testing.
|
||||
pub fn check_relation(&self, z: &[F]) -> Result<(), Error> {
|
||||
let Az = mat_vec_mul(&self.A, z)?;
|
||||
let Bz = mat_vec_mul(&self.B, z)?;
|
||||
let Cz = mat_vec_mul(&self.C, z)?;
|
||||
let uCz = vec_scalar_mul(&Cz, &self.u);
|
||||
let uCzE = vec_add(&uCz, &self.E)?;
|
||||
let AzBz = hadamard(&Az, &Bz)?;
|
||||
if AzBz != uCzE {
|
||||
return Err(Error::NotSatisfied);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
/// checks the relaxed R1CS relation
|
||||
fn check_relaxed_relation(&self, w: &W, u: &U) -> Result<(), Error> {
|
||||
let z = Self::extract_z(w, u);
|
||||
let e = self.eval_relation(&z)?;
|
||||
Self::check_error_terms(w, u, e)
|
||||
}
|
||||
|
||||
// Computes the E term, given A, B, C, z, u
|
||||
fn compute_E(
|
||||
A: &SparseMatrix<F>,
|
||||
B: &SparseMatrix<F>,
|
||||
C: &SparseMatrix<F>,
|
||||
z: &[F],
|
||||
u: &F,
|
||||
) -> Result<Vec<F>, Error> {
|
||||
A: &SparseMatrix<C::ScalarField>,
|
||||
B: &SparseMatrix<C::ScalarField>,
|
||||
C: &SparseMatrix<C::ScalarField>,
|
||||
z: &[C::ScalarField],
|
||||
u: &C::ScalarField,
|
||||
) -> Result<Vec<C::ScalarField>, Error> {
|
||||
let Az = mat_vec_mul(A, z)?;
|
||||
let Bz = mat_vec_mul(B, z)?;
|
||||
let AzBz = hadamard(&Az, &Bz)?;
|
||||
@@ -123,66 +124,9 @@ impl<F: PrimeField> RelaxedR1CS<F> {
|
||||
vec_sub(&AzBz, &uCz)
|
||||
}
|
||||
|
||||
pub fn check_sampled_relaxed_r1cs(&self, u: F, E: &[F], z: &[F]) -> bool {
|
||||
let sampled = RelaxedR1CS {
|
||||
l: self.l,
|
||||
A: self.A.clone(),
|
||||
B: self.B.clone(),
|
||||
C: self.C.clone(),
|
||||
u,
|
||||
E: E.to_vec(),
|
||||
};
|
||||
sampled.check_relation(z).is_ok()
|
||||
}
|
||||
|
||||
// Implements sampling a (committed) RelaxedR1CS
|
||||
// See construction 5 in https://eprint.iacr.org/2023/573.pdf
|
||||
pub fn sample<C, CS>(
|
||||
&self,
|
||||
params: &CS::ProverParams,
|
||||
mut rng: impl RngCore,
|
||||
) -> Result<(CommittedInstance<C>, Witness<C>), Error>
|
||||
fn sample<CS>(&self, params: &CS::ProverParams, rng: impl RngCore) -> Result<(W, U), Error>
|
||||
where
|
||||
C: CurveGroup,
|
||||
C: CurveGroup<ScalarField = F>,
|
||||
<C as Group>::ScalarField: Absorb,
|
||||
CS: CommitmentScheme<C, true>,
|
||||
{
|
||||
let u = C::ScalarField::rand(&mut rng);
|
||||
let rE = C::ScalarField::rand(&mut rng);
|
||||
let rW = C::ScalarField::rand(&mut rng);
|
||||
|
||||
let W = (0..self.A.n_cols - self.l - 1)
|
||||
.map(|_| F::rand(&mut rng))
|
||||
.collect();
|
||||
let x = (0..self.l).map(|_| F::rand(&mut rng)).collect::<Vec<F>>();
|
||||
let mut z = vec![u];
|
||||
z.extend(&x);
|
||||
z.extend(&W);
|
||||
|
||||
let E = RelaxedR1CS::compute_E(&self.A, &self.B, &self.C, &z, &u)?;
|
||||
|
||||
debug_assert!(
|
||||
z.len() == self.A.n_cols,
|
||||
"Length of z is {}, while A has {} columns.",
|
||||
z.len(),
|
||||
self.A.n_cols
|
||||
);
|
||||
|
||||
debug_assert!(
|
||||
self.check_sampled_relaxed_r1cs(u, &E, &z),
|
||||
"Sampled a non satisfiable relaxed R1CS, sampled u: {}, computed E: {:?}",
|
||||
u,
|
||||
E
|
||||
);
|
||||
|
||||
let witness = Witness { E, rE, W, rW };
|
||||
let mut cm_witness = witness.commit::<CS, true>(params, x)?;
|
||||
|
||||
// witness.commit() sets u to 1, we set it to the sampled u value
|
||||
cm_witness.u = u;
|
||||
Ok((cm_witness, witness))
|
||||
}
|
||||
CS: CommitmentScheme<C, true>;
|
||||
}
|
||||
|
||||
/// extracts arkworks ConstraintSystem matrices into crate::utils::vec::SparseMatrix format as R1CS
|
||||
@@ -229,9 +173,13 @@ pub fn extract_w_x<F: PrimeField>(cs: &ConstraintSystem<F>) -> (Vec<F>, Vec<F>)
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use super::*;
|
||||
use crate::folding::nova::{CommittedInstance, Witness};
|
||||
use crate::{
|
||||
commitment::pedersen::Pedersen,
|
||||
utils::vec::tests::{to_F_matrix, to_F_vec},
|
||||
utils::vec::{
|
||||
is_zero_vec,
|
||||
tests::{to_F_matrix, to_F_vec},
|
||||
},
|
||||
};
|
||||
|
||||
use ark_pallas::{Fr, Projective};
|
||||
@@ -242,9 +190,8 @@ pub mod tests {
|
||||
let r1cs = get_test_r1cs::<Fr>();
|
||||
let (prover_params, _) = Pedersen::<Projective>::setup(rng, r1cs.A.n_rows).unwrap();
|
||||
|
||||
let relaxed_r1cs = r1cs.relax();
|
||||
let sampled =
|
||||
relaxed_r1cs.sample::<Projective, Pedersen<Projective, true>>(&prover_params, rng);
|
||||
let sampled: Result<(Witness<Projective>, CommittedInstance<Projective>), _> =
|
||||
r1cs.sample::<Pedersen<Projective, true>>(&prover_params, rng);
|
||||
assert!(sampled.is_ok());
|
||||
}
|
||||
|
||||
@@ -302,10 +249,23 @@ pub mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_check_relation() {
|
||||
fn test_eval_r1cs_relation() {
|
||||
let mut rng = ark_std::test_rng();
|
||||
let r1cs = get_test_r1cs::<Fr>();
|
||||
let mut z = get_test_z::<Fr>(rng.gen::<u16>() as usize);
|
||||
|
||||
let f_w = r1cs.eval_relation(&z).unwrap();
|
||||
assert!(is_zero_vec(&f_w));
|
||||
|
||||
z[1] = Fr::from(111);
|
||||
let f_w = r1cs.eval_relation(&z).unwrap();
|
||||
assert!(!is_zero_vec(&f_w));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_check_r1cs_relation() {
|
||||
let r1cs = get_test_r1cs::<Fr>();
|
||||
let z = get_test_z(5);
|
||||
r1cs.check_relation(&z).unwrap();
|
||||
r1cs.relax().check_relation(&z).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -360,6 +360,8 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// `CycleFoldConfig` allows us to customize the behavior of CycleFold circuit
|
||||
/// according to the folding scheme we are working with.
|
||||
pub trait CycleFoldConfig {
|
||||
/// `N_INPUT_POINTS` specifies the number of input points that are folded in
|
||||
/// [`CycleFoldCircuit`] via random linear combinations.
|
||||
@@ -465,7 +467,10 @@ where
|
||||
// In multifolding schemes such as HyperNova, this is:
|
||||
// computed_x = [r, p_0, p_1, p_2, ..., p_n, p_folded],
|
||||
// where each p_i is in fact p_i.to_constraint_field()
|
||||
let r_fp = Boolean::le_bits_to_fp_var(&r_bits)?;
|
||||
let r_fp = r_bits
|
||||
.chunks(CFG::F::MODULUS_BIT_SIZE as usize - 1)
|
||||
.map(Boolean::le_bits_to_fp_var)
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
let points_aux: Vec<FpVar<CFG::F>> = points
|
||||
.iter()
|
||||
.map(|p_i| Ok(p_i.to_constraint_field()?[..2].to_vec()))
|
||||
@@ -475,7 +480,7 @@ where
|
||||
.collect();
|
||||
|
||||
let computed_x: Vec<FpVar<CFG::F>> = [
|
||||
vec![r_fp],
|
||||
r_fp,
|
||||
points_aux,
|
||||
p_folded.to_constraint_field()?[..2].to_vec(),
|
||||
]
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
use ark_ec::{AffineRepr, CurveGroup};
|
||||
use ark_ec::{short_weierstrass::SWFlags, AffineRepr, CurveGroup};
|
||||
use ark_ff::{Field, PrimeField};
|
||||
use ark_r1cs_std::{
|
||||
alloc::{AllocVar, AllocationMode},
|
||||
fields::fp::FpVar,
|
||||
ToConstraintFieldGadget,
|
||||
R1CSVar, ToConstraintFieldGadget,
|
||||
};
|
||||
use ark_relations::r1cs::{Namespace, SynthesisError};
|
||||
use ark_relations::r1cs::{ConstraintSystemRef, Namespace, SynthesisError};
|
||||
use ark_serialize::{CanonicalSerialize, CanonicalSerializeWithFlags};
|
||||
use ark_std::Zero;
|
||||
use core::borrow::Borrow;
|
||||
|
||||
@@ -45,6 +47,39 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: CurveGroup> R1CSVar<C::ScalarField> for NonNativeAffineVar<C> {
|
||||
type Value = C;
|
||||
|
||||
fn cs(&self) -> ConstraintSystemRef<C::ScalarField> {
|
||||
self.x.cs().or(self.y.cs())
|
||||
}
|
||||
|
||||
fn value(&self) -> Result<Self::Value, SynthesisError> {
|
||||
debug_assert_eq!(C::BaseField::extension_degree(), 1);
|
||||
|
||||
let x = <C::BaseField as Field>::BasePrimeField::from_le_bytes_mod_order(
|
||||
&self.x.value()?.to_bytes_le(),
|
||||
);
|
||||
let y = <C::BaseField as Field>::BasePrimeField::from_le_bytes_mod_order(
|
||||
&self.y.value()?.to_bytes_le(),
|
||||
);
|
||||
let mut bytes = vec![];
|
||||
x.serialize_uncompressed(&mut bytes).unwrap();
|
||||
y.serialize_with_flags(
|
||||
&mut bytes,
|
||||
if x.is_zero() && y.is_zero() {
|
||||
SWFlags::PointAtInfinity
|
||||
} else if y <= -y {
|
||||
SWFlags::YIsPositive
|
||||
} else {
|
||||
SWFlags::YIsNegative
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
Ok(C::deserialize_uncompressed_unchecked(&bytes[..]).unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: CurveGroup> ToConstraintFieldGadget<C::ScalarField> for NonNativeAffineVar<C> {
|
||||
// Used for converting `NonNativeAffineVar` to a vector of `FpVar` with minimum length in
|
||||
// the circuit.
|
||||
@@ -83,6 +118,10 @@ impl<C: CurveGroup> NonNativeAffineVar<C> {
|
||||
let y = NonNativeUintVar::inputize(*y);
|
||||
Ok((x, y))
|
||||
}
|
||||
|
||||
pub fn zero() -> Self {
|
||||
Self::new_constant(ConstraintSystemRef::None, C::zero()).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: CurveGroup> AbsorbNonNative<C::ScalarField> for C {
|
||||
@@ -105,7 +144,6 @@ impl<C: CurveGroup> AbsorbNonNativeGadget<C::ScalarField> for NonNativeAffineVar
|
||||
mod tests {
|
||||
use super::*;
|
||||
use ark_pallas::{Fr, Projective};
|
||||
use ark_r1cs_std::R1CSVar;
|
||||
use ark_relations::r1cs::ConstraintSystem;
|
||||
use ark_std::UniformRand;
|
||||
|
||||
|
||||
@@ -475,30 +475,30 @@ pub struct AugmentedFCircuit<
|
||||
> where
|
||||
for<'a> &'a GC2: GroupOpsBounds<'a, C2, GC2>,
|
||||
{
|
||||
pub _c2: PhantomData<C2>,
|
||||
pub _gc2: PhantomData<GC2>,
|
||||
pub poseidon_config: PoseidonConfig<CF1<C1>>,
|
||||
pub ccs: CCS<C1::ScalarField>, // CCS of the AugmentedFCircuit
|
||||
pub pp_hash: Option<CF1<C1>>,
|
||||
pub i: Option<CF1<C1>>,
|
||||
pub i_usize: Option<usize>,
|
||||
pub z_0: Option<Vec<C1::ScalarField>>,
|
||||
pub z_i: Option<Vec<C1::ScalarField>>,
|
||||
pub external_inputs: Option<Vec<C1::ScalarField>>,
|
||||
pub U_i: Option<LCCCS<C1>>,
|
||||
pub Us: Option<Vec<LCCCS<C1>>>, // other U_i's to be folded that are not the main running instance
|
||||
pub u_i_C: Option<C1>, // u_i.C
|
||||
pub us: Option<Vec<CCCS<C1>>>, // other u_i's to be folded that are not the main incoming instance
|
||||
pub U_i1_C: Option<C1>, // U_{i+1}.C
|
||||
pub F: FC, // F circuit
|
||||
pub x: Option<CF1<C1>>, // public input (u_{i+1}.x[0])
|
||||
pub nimfs_proof: Option<NIMFSProof<C1>>,
|
||||
pub(super) _c2: PhantomData<C2>,
|
||||
pub(super) _gc2: PhantomData<GC2>,
|
||||
pub(super) poseidon_config: PoseidonConfig<CF1<C1>>,
|
||||
pub(super) ccs: CCS<C1::ScalarField>, // CCS of the AugmentedFCircuit
|
||||
pub(super) pp_hash: Option<CF1<C1>>,
|
||||
pub(super) i: Option<CF1<C1>>,
|
||||
pub(super) i_usize: Option<usize>,
|
||||
pub(super) z_0: Option<Vec<C1::ScalarField>>,
|
||||
pub(super) z_i: Option<Vec<C1::ScalarField>>,
|
||||
pub(super) external_inputs: Option<Vec<C1::ScalarField>>,
|
||||
pub(super) U_i: Option<LCCCS<C1>>,
|
||||
pub(super) Us: Option<Vec<LCCCS<C1>>>, // other U_i's to be folded that are not the main running instance
|
||||
pub(super) u_i_C: Option<C1>, // u_i.C
|
||||
pub(super) us: Option<Vec<CCCS<C1>>>, // other u_i's to be folded that are not the main incoming instance
|
||||
pub(super) U_i1_C: Option<C1>, // U_{i+1}.C
|
||||
pub(super) F: FC, // F circuit
|
||||
pub(super) x: Option<CF1<C1>>, // public input (u_{i+1}.x[0])
|
||||
pub(super) nimfs_proof: Option<NIMFSProof<C1>>,
|
||||
|
||||
// cyclefold verifier on C1
|
||||
pub cf_u_i_cmW: Option<C2>, // input, cf_u_i.cmW
|
||||
pub cf_U_i: Option<CycleFoldCommittedInstance<C2>>, // input, RelaxedR1CS CycleFold instance
|
||||
pub cf_x: Option<CF1<C1>>, // public input (cf_u_{i+1}.x[1])
|
||||
pub cf_cmT: Option<C2>,
|
||||
pub(super) cf_u_i_cmW: Option<C2>, // input, cf_u_i.cmW
|
||||
pub(super) cf_U_i: Option<CycleFoldCommittedInstance<C2>>, // input, RelaxedR1CS CycleFold instance
|
||||
pub(super) cf_x: Option<CF1<C1>>, // public input (cf_u_{i+1}.x[1])
|
||||
pub(super) cf_cmT: Option<C2>,
|
||||
}
|
||||
|
||||
impl<C1, C2, GC2, FC, const MU: usize, const NU: usize> AugmentedFCircuit<C1, C2, GC2, FC, MU, NU>
|
||||
@@ -891,7 +891,7 @@ mod tests {
|
||||
use crate::{
|
||||
arith::{
|
||||
ccs::tests::{get_test_ccs, get_test_z},
|
||||
r1cs::extract_w_x,
|
||||
r1cs::{extract_w_x, RelaxedR1CS},
|
||||
},
|
||||
commitment::{pedersen::Pedersen, CommitmentScheme},
|
||||
folding::{
|
||||
@@ -900,9 +900,8 @@ mod tests {
|
||||
utils::{compute_c, compute_sigmas_thetas},
|
||||
HyperNovaCycleFoldCircuit,
|
||||
},
|
||||
nova::traits::NovaR1CS,
|
||||
},
|
||||
frontend::tests::CubicFCircuit,
|
||||
frontend::utils::CubicFCircuit,
|
||||
transcript::poseidon::poseidon_canonical_config,
|
||||
utils::get_cm_coordinates,
|
||||
};
|
||||
@@ -1216,7 +1215,7 @@ mod tests {
|
||||
let (cf_W_dummy, cf_U_dummy): (
|
||||
CycleFoldWitness<Projective2>,
|
||||
CycleFoldCommittedInstance<Projective2>,
|
||||
) = cf_r1cs.dummy_instance();
|
||||
) = cf_r1cs.dummy_running_instance();
|
||||
|
||||
// set the initial dummy instances
|
||||
let mut W_i = W_dummy.clone();
|
||||
@@ -1455,9 +1454,7 @@ mod tests {
|
||||
u_i.check_relation(&ccs, &w_i).unwrap();
|
||||
|
||||
// check the CycleFold instance relation
|
||||
cf_r1cs
|
||||
.check_relaxed_instance_relation(&cf_W_i, &cf_U_i)
|
||||
.unwrap();
|
||||
cf_r1cs.check_relaxed_relation(&cf_W_i, &cf_U_i).unwrap();
|
||||
|
||||
println!("augmented_f_circuit step {}: {:?}", i, start.elapsed());
|
||||
}
|
||||
|
||||
@@ -228,7 +228,7 @@ pub mod tests {
|
||||
use super::*;
|
||||
use crate::commitment::{kzg::KZG, pedersen::Pedersen};
|
||||
use crate::folding::hypernova::PreprocessorParam;
|
||||
use crate::frontend::tests::CubicFCircuit;
|
||||
use crate::frontend::utils::CubicFCircuit;
|
||||
use crate::transcript::poseidon::poseidon_canonical_config;
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -517,7 +517,7 @@ pub mod tests {
|
||||
use super::*;
|
||||
use crate::commitment::pedersen::Pedersen;
|
||||
use crate::folding::nova::PreprocessorParam;
|
||||
use crate::frontend::tests::CubicFCircuit;
|
||||
use crate::frontend::utils::CubicFCircuit;
|
||||
use crate::transcript::poseidon::poseidon_canonical_config;
|
||||
use crate::FoldingScheme;
|
||||
|
||||
|
||||
@@ -21,7 +21,6 @@ use circuits::AugmentedFCircuit;
|
||||
use lcccs::LCCCS;
|
||||
use nimfs::NIMFS;
|
||||
|
||||
use crate::commitment::CommitmentScheme;
|
||||
use crate::constants::NOVA_N_BITS_RO;
|
||||
use crate::folding::circuits::{
|
||||
cyclefold::{
|
||||
@@ -30,10 +29,11 @@ use crate::folding::circuits::{
|
||||
},
|
||||
CF2,
|
||||
};
|
||||
use crate::folding::nova::{get_r1cs_from_cs, traits::NovaR1CS, PreprocessorParam};
|
||||
use crate::folding::nova::{get_r1cs_from_cs, PreprocessorParam};
|
||||
use crate::frontend::FCircuit;
|
||||
use crate::utils::{get_cm_coordinates, pp_hash};
|
||||
use crate::Error;
|
||||
use crate::{arith::r1cs::RelaxedR1CS, commitment::CommitmentScheme};
|
||||
use crate::{
|
||||
arith::{
|
||||
ccs::CCS,
|
||||
@@ -42,7 +42,8 @@ use crate::{
|
||||
FoldingScheme, MultiFolding,
|
||||
};
|
||||
|
||||
struct HyperNovaCycleFoldConfig<C: CurveGroup, const MU: usize, const NU: usize> {
|
||||
/// Configuration for HyperNova's CycleFold circuit
|
||||
pub struct HyperNovaCycleFoldConfig<C: CurveGroup, const MU: usize, const NU: usize> {
|
||||
_c: PhantomData<C>,
|
||||
}
|
||||
|
||||
@@ -55,7 +56,9 @@ impl<C: CurveGroup, const MU: usize, const NU: usize> CycleFoldConfig
|
||||
type F = C::BaseField;
|
||||
}
|
||||
|
||||
type HyperNovaCycleFoldCircuit<C, GC, const MU: usize, const NU: usize> =
|
||||
/// CycleFold circuit for computing random linear combinations of group elements
|
||||
/// in HyperNova instances.
|
||||
pub type HyperNovaCycleFoldCircuit<C, GC, const MU: usize, const NU: usize> =
|
||||
CycleFoldCircuit<HyperNovaCycleFoldConfig<C, MU, NU>, GC>;
|
||||
|
||||
/// Witness for the LCCCS & CCCS, containing the w vector, and the r_w used as randomness in the Pedersen commitment.
|
||||
@@ -76,6 +79,7 @@ impl<F: PrimeField> Witness<F> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Proving parameters for HyperNova-based IVC
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ProverParams<C1, C2, CS1, CS2, const H: bool>
|
||||
where
|
||||
@@ -84,13 +88,18 @@ where
|
||||
CS1: CommitmentScheme<C1, H>,
|
||||
CS2: CommitmentScheme<C2, H>,
|
||||
{
|
||||
/// Poseidon sponge configuration
|
||||
pub poseidon_config: PoseidonConfig<C1::ScalarField>,
|
||||
/// Proving parameters of the underlying commitment scheme over C1
|
||||
pub cs_pp: CS1::ProverParams,
|
||||
/// Proving parameters of the underlying commitment scheme over C2
|
||||
pub cf_cs_pp: CS2::ProverParams,
|
||||
// if ccs is set, it will be used, if not, it will be computed at runtime
|
||||
/// CCS of the Augmented Function circuit
|
||||
/// If ccs is set, it will be used, if not, it will be computed at runtime
|
||||
pub ccs: Option<CCS<C1::ScalarField>>,
|
||||
}
|
||||
|
||||
/// Verification parameters for HyperNova-based IVC
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct VerifierParams<
|
||||
C1: CurveGroup,
|
||||
@@ -99,10 +108,15 @@ pub struct VerifierParams<
|
||||
CS2: CommitmentScheme<C2, H>,
|
||||
const H: bool,
|
||||
> {
|
||||
/// Poseidon sponge configuration
|
||||
pub poseidon_config: PoseidonConfig<C1::ScalarField>,
|
||||
/// CCS of the Augmented step circuit
|
||||
pub ccs: CCS<C1::ScalarField>,
|
||||
/// R1CS of the CycleFold circuit
|
||||
pub cf_r1cs: R1CS<C2::ScalarField>,
|
||||
/// Verification parameters of the underlying commitment scheme over C1
|
||||
pub cs_vp: CS1::VerifierParams,
|
||||
/// Verification parameters of the underlying commitment scheme over C2
|
||||
pub cf_cs_vp: CS2::VerifierParams,
|
||||
}
|
||||
|
||||
@@ -282,7 +296,7 @@ where
|
||||
let U_i = LCCCS::<C1>::dummy(self.ccs.l, self.ccs.t, self.ccs.s);
|
||||
let mut u_i = CCCS::<C1>::dummy(self.ccs.l);
|
||||
let (_, cf_U_i): (CycleFoldWitness<C2>, CycleFoldCommittedInstance<C2>) =
|
||||
self.cf_r1cs.dummy_instance();
|
||||
self.cf_r1cs.dummy_running_instance();
|
||||
|
||||
let sponge = PoseidonSponge::<C1::ScalarField>::new(&self.poseidon_config);
|
||||
|
||||
@@ -476,7 +490,7 @@ where
|
||||
let w_dummy = W_dummy.clone();
|
||||
let mut u_dummy = CCCS::<C1>::dummy(ccs.l);
|
||||
let (cf_W_dummy, cf_U_dummy): (CycleFoldWitness<C2>, CycleFoldCommittedInstance<C2>) =
|
||||
cf_r1cs.dummy_instance();
|
||||
cf_r1cs.dummy_running_instance();
|
||||
u_dummy.x = vec![
|
||||
U_dummy.hash(
|
||||
&sponge,
|
||||
@@ -884,8 +898,7 @@ where
|
||||
u_i.check_relation(&vp.ccs, &w_i)?;
|
||||
|
||||
// check CycleFold's RelaxedR1CS satisfiability
|
||||
vp.cf_r1cs
|
||||
.check_relaxed_instance_relation(&cf_W_i, &cf_U_i)?;
|
||||
vp.cf_r1cs.check_relaxed_relation(&cf_W_i, &cf_U_i)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -900,7 +913,7 @@ mod tests {
|
||||
|
||||
use super::*;
|
||||
use crate::commitment::pedersen::Pedersen;
|
||||
use crate::frontend::tests::CubicFCircuit;
|
||||
use crate::frontend::utils::CubicFCircuit;
|
||||
use crate::transcript::poseidon::poseidon_canonical_config;
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -237,31 +237,31 @@ pub struct AugmentedFCircuit<
|
||||
> where
|
||||
for<'a> &'a GC2: GroupOpsBounds<'a, C2, GC2>,
|
||||
{
|
||||
pub _gc2: PhantomData<GC2>,
|
||||
pub poseidon_config: PoseidonConfig<CF1<C1>>,
|
||||
pub pp_hash: Option<CF1<C1>>,
|
||||
pub i: Option<CF1<C1>>,
|
||||
pub i_usize: Option<usize>,
|
||||
pub z_0: Option<Vec<C1::ScalarField>>,
|
||||
pub z_i: Option<Vec<C1::ScalarField>>,
|
||||
pub external_inputs: Option<Vec<C1::ScalarField>>,
|
||||
pub u_i_cmW: Option<C1>,
|
||||
pub U_i: Option<CommittedInstance<C1>>,
|
||||
pub U_i1_cmE: Option<C1>,
|
||||
pub U_i1_cmW: Option<C1>,
|
||||
pub cmT: Option<C1>,
|
||||
pub F: FC, // F circuit
|
||||
pub x: Option<CF1<C1>>, // public input (u_{i+1}.x[0])
|
||||
pub(super) _gc2: PhantomData<GC2>,
|
||||
pub(super) poseidon_config: PoseidonConfig<CF1<C1>>,
|
||||
pub(super) pp_hash: Option<CF1<C1>>,
|
||||
pub(super) i: Option<CF1<C1>>,
|
||||
pub(super) i_usize: Option<usize>,
|
||||
pub(super) z_0: Option<Vec<C1::ScalarField>>,
|
||||
pub(super) z_i: Option<Vec<C1::ScalarField>>,
|
||||
pub(super) external_inputs: Option<Vec<C1::ScalarField>>,
|
||||
pub(super) u_i_cmW: Option<C1>,
|
||||
pub(super) U_i: Option<CommittedInstance<C1>>,
|
||||
pub(super) U_i1_cmE: Option<C1>,
|
||||
pub(super) U_i1_cmW: Option<C1>,
|
||||
pub(super) cmT: Option<C1>,
|
||||
pub(super) F: FC, // F circuit
|
||||
pub(super) x: Option<CF1<C1>>, // public input (u_{i+1}.x[0])
|
||||
|
||||
// cyclefold verifier on C1
|
||||
// Here 'cf1, cf2' are for each of the CycleFold circuits, corresponding to the fold of cmW and
|
||||
// cmE respectively
|
||||
pub cf1_u_i_cmW: Option<C2>, // input
|
||||
pub cf2_u_i_cmW: Option<C2>, // input
|
||||
pub cf_U_i: Option<CycleFoldCommittedInstance<C2>>, // input
|
||||
pub cf1_cmT: Option<C2>,
|
||||
pub cf2_cmT: Option<C2>,
|
||||
pub cf_x: Option<CF1<C1>>, // public input (u_{i+1}.x[1])
|
||||
pub(super) cf1_u_i_cmW: Option<C2>, // input
|
||||
pub(super) cf2_u_i_cmW: Option<C2>, // input
|
||||
pub(super) cf_U_i: Option<CycleFoldCommittedInstance<C2>>, // input
|
||||
pub(super) cf1_cmT: Option<C2>,
|
||||
pub(super) cf2_cmT: Option<C2>,
|
||||
pub(super) cf_x: Option<CF1<C1>>, // public input (u_{i+1}.x[1])
|
||||
}
|
||||
|
||||
impl<C1: CurveGroup, C2: CurveGroup, GC2: CurveVar<C2, CF2<C2>>, FC: FCircuit<CF1<C1>>>
|
||||
|
||||
@@ -342,7 +342,7 @@ pub mod tests {
|
||||
use crate::folding::nova::{
|
||||
PreprocessorParam, ProverParams as NovaProverParams, VerifierParams as NovaVerifierParams,
|
||||
};
|
||||
use crate::frontend::tests::CubicFCircuit;
|
||||
use crate::frontend::utils::CubicFCircuit;
|
||||
use crate::transcript::poseidon::poseidon_canonical_config;
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -577,6 +577,8 @@ where
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use std::cmp::max;
|
||||
|
||||
use ark_crypto_primitives::crh::{
|
||||
sha256::{
|
||||
constraints::{Sha256Gadget, UnitVar},
|
||||
@@ -587,34 +589,61 @@ pub mod tests {
|
||||
use ark_pallas::{constraints::GVar, Fq, Fr, Projective};
|
||||
use ark_r1cs_std::bits::uint8::UInt8;
|
||||
use ark_relations::r1cs::ConstraintSystem;
|
||||
use ark_std::{One, UniformRand};
|
||||
use ark_std::{
|
||||
rand::{thread_rng, Rng},
|
||||
One, UniformRand,
|
||||
};
|
||||
use ark_vesta::{constraints::GVar as GVar2, Projective as Projective2};
|
||||
|
||||
use super::*;
|
||||
use crate::arith::{
|
||||
r1cs::{
|
||||
extract_r1cs, extract_w_x,
|
||||
tests::{get_test_r1cs, get_test_z},
|
||||
{extract_r1cs, extract_w_x},
|
||||
RelaxedR1CS,
|
||||
},
|
||||
Arith,
|
||||
};
|
||||
use crate::commitment::pedersen::Pedersen;
|
||||
use crate::folding::nova::PreprocessorParam;
|
||||
use crate::frontend::tests::{CubicFCircuit, CustomFCircuit, WrapperCircuit};
|
||||
use crate::frontend::utils::{CubicFCircuit, CustomFCircuit, WrapperCircuit};
|
||||
use crate::transcript::poseidon::poseidon_canonical_config;
|
||||
use crate::FoldingScheme;
|
||||
|
||||
fn prepare_instances<C: CurveGroup, CS: CommitmentScheme<C>, R: Rng>(
|
||||
mut rng: R,
|
||||
r1cs: &R1CS<C::ScalarField>,
|
||||
z: &[C::ScalarField],
|
||||
) -> (Witness<C>, CommittedInstance<C>)
|
||||
where
|
||||
C::ScalarField: Absorb,
|
||||
{
|
||||
let (w, x) = r1cs.split_z(z);
|
||||
|
||||
let (cs_pp, _) = CS::setup(&mut rng, max(w.len(), r1cs.A.n_rows)).unwrap();
|
||||
|
||||
let mut w = Witness::new::<false>(w, r1cs.A.n_rows, &mut rng);
|
||||
w.E = r1cs.eval_relation(z).unwrap();
|
||||
let mut u = w.commit::<CS, false>(&cs_pp, x).unwrap();
|
||||
u.u = z[0];
|
||||
|
||||
(w, u)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_relaxed_r1cs_small_gadget_handcrafted() {
|
||||
let rng = &mut thread_rng();
|
||||
|
||||
let r1cs: R1CS<Fr> = get_test_r1cs();
|
||||
let rel_r1cs = r1cs.clone().relax();
|
||||
let z = get_test_z(3);
|
||||
let mut z = get_test_z(3);
|
||||
z[0] = Fr::rand(rng);
|
||||
let (w, u) = prepare_instances::<_, Pedersen<Projective>, _>(rng, &r1cs, &z);
|
||||
|
||||
let cs = ConstraintSystem::<Fr>::new_ref();
|
||||
|
||||
let zVar = Vec::<FpVar<Fr>>::new_witness(cs.clone(), || Ok(z)).unwrap();
|
||||
let EVar = Vec::<FpVar<Fr>>::new_witness(cs.clone(), || Ok(rel_r1cs.E)).unwrap();
|
||||
let uVar = FpVar::<Fr>::new_witness(cs.clone(), || Ok(rel_r1cs.u)).unwrap();
|
||||
let EVar = Vec::<FpVar<Fr>>::new_witness(cs.clone(), || Ok(w.E)).unwrap();
|
||||
let uVar = FpVar::<Fr>::new_witness(cs.clone(), || Ok(u.u)).unwrap();
|
||||
let r1csVar = R1CSVar::<Fr, Fr, FpVar<Fr>>::new_witness(cs.clone(), || Ok(r1cs)).unwrap();
|
||||
|
||||
RelaxedR1CSGadget::check_native(r1csVar, EVar, uVar, zVar).unwrap();
|
||||
@@ -624,6 +653,8 @@ pub mod tests {
|
||||
// gets as input a circuit that implements the ConstraintSynthesizer trait, and that has been
|
||||
// initialized.
|
||||
fn test_relaxed_r1cs_gadget<CS: ConstraintSynthesizer<Fr>>(circuit: CS) {
|
||||
let rng = &mut thread_rng();
|
||||
|
||||
let cs = ConstraintSystem::<Fr>::new_ref();
|
||||
|
||||
circuit.generate_constraints(cs.clone()).unwrap();
|
||||
@@ -634,18 +665,19 @@ pub mod tests {
|
||||
|
||||
let r1cs = extract_r1cs::<Fr>(&cs);
|
||||
let (w, x) = extract_w_x::<Fr>(&cs);
|
||||
let z = [vec![Fr::one()], x, w].concat();
|
||||
let mut z = [vec![Fr::one()], x, w].concat();
|
||||
r1cs.check_relation(&z).unwrap();
|
||||
|
||||
let relaxed_r1cs = r1cs.clone().relax();
|
||||
relaxed_r1cs.check_relation(&z).unwrap();
|
||||
z[0] = Fr::rand(rng);
|
||||
let (w, u) = prepare_instances::<_, Pedersen<Projective>, _>(rng, &r1cs, &z);
|
||||
r1cs.check_relaxed_relation(&w, &u).unwrap();
|
||||
|
||||
// set new CS for the circuit that checks the RelaxedR1CS of our original circuit
|
||||
let cs = ConstraintSystem::<Fr>::new_ref();
|
||||
// prepare the inputs for our circuit
|
||||
let zVar = Vec::<FpVar<Fr>>::new_witness(cs.clone(), || Ok(z)).unwrap();
|
||||
let EVar = Vec::<FpVar<Fr>>::new_witness(cs.clone(), || Ok(relaxed_r1cs.E)).unwrap();
|
||||
let uVar = FpVar::<Fr>::new_witness(cs.clone(), || Ok(relaxed_r1cs.u)).unwrap();
|
||||
let EVar = Vec::<FpVar<Fr>>::new_witness(cs.clone(), || Ok(w.E)).unwrap();
|
||||
let uVar = FpVar::<Fr>::new_witness(cs.clone(), || Ok(u.u)).unwrap();
|
||||
let r1csVar = R1CSVar::<Fr, Fr, FpVar<Fr>>::new_witness(cs.clone(), || Ok(r1cs)).unwrap();
|
||||
|
||||
RelaxedR1CSGadget::check_native(r1csVar, EVar, uVar, zVar).unwrap();
|
||||
@@ -709,6 +741,8 @@ pub mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_relaxed_r1cs_nonnative_circuit() {
|
||||
let rng = &mut thread_rng();
|
||||
|
||||
let cs = ConstraintSystem::<Fq>::new_ref();
|
||||
// in practice we would use CycleFoldCircuit, but is a very big circuit (when computed
|
||||
// non-natively inside the RelaxedR1CS circuit), so in order to have a short test we use a
|
||||
@@ -725,16 +759,15 @@ pub mod tests {
|
||||
let cs = cs.into_inner().unwrap();
|
||||
let r1cs = extract_r1cs::<Fq>(&cs);
|
||||
let (w, x) = extract_w_x::<Fq>(&cs);
|
||||
let z = [vec![Fq::one()], x, w].concat();
|
||||
let z = [vec![Fq::rand(rng)], x, w].concat();
|
||||
|
||||
let relaxed_r1cs = r1cs.clone().relax();
|
||||
let (w, u) = prepare_instances::<_, Pedersen<Projective2>, _>(rng, &r1cs, &z);
|
||||
|
||||
// natively
|
||||
let cs = ConstraintSystem::<Fq>::new_ref();
|
||||
let zVar = Vec::<FpVar<Fq>>::new_witness(cs.clone(), || Ok(z.clone())).unwrap();
|
||||
let EVar =
|
||||
Vec::<FpVar<Fq>>::new_witness(cs.clone(), || Ok(relaxed_r1cs.clone().E)).unwrap();
|
||||
let uVar = FpVar::<Fq>::new_witness(cs.clone(), || Ok(relaxed_r1cs.u)).unwrap();
|
||||
let EVar = Vec::<FpVar<Fq>>::new_witness(cs.clone(), || Ok(w.E.clone())).unwrap();
|
||||
let uVar = FpVar::<Fq>::new_witness(cs.clone(), || Ok(u.u)).unwrap();
|
||||
let r1csVar =
|
||||
R1CSVar::<Fq, Fq, FpVar<Fq>>::new_witness(cs.clone(), || Ok(r1cs.clone())).unwrap();
|
||||
RelaxedR1CSGadget::check_native(r1csVar, EVar, uVar, zVar).unwrap();
|
||||
@@ -742,8 +775,8 @@ pub mod tests {
|
||||
// non-natively
|
||||
let cs = ConstraintSystem::<Fr>::new_ref();
|
||||
let zVar = Vec::new_witness(cs.clone(), || Ok(z)).unwrap();
|
||||
let EVar = Vec::new_witness(cs.clone(), || Ok(relaxed_r1cs.E)).unwrap();
|
||||
let uVar = NonNativeUintVar::<Fr>::new_witness(cs.clone(), || Ok(relaxed_r1cs.u)).unwrap();
|
||||
let EVar = Vec::new_witness(cs.clone(), || Ok(w.E)).unwrap();
|
||||
let uVar = NonNativeUintVar::<Fr>::new_witness(cs.clone(), || Ok(u.u)).unwrap();
|
||||
let r1csVar =
|
||||
R1CSVar::<Fq, Fr, NonNativeUintVar<Fr>>::new_witness(cs.clone(), || Ok(r1cs)).unwrap();
|
||||
RelaxedR1CSGadget::check_nonnative(r1csVar, EVar, uVar, zVar).unwrap();
|
||||
|
||||
@@ -14,7 +14,6 @@ use ark_std::rand::RngCore;
|
||||
use ark_std::{One, UniformRand, Zero};
|
||||
use core::marker::PhantomData;
|
||||
|
||||
use crate::commitment::CommitmentScheme;
|
||||
use crate::folding::circuits::cyclefold::{
|
||||
fold_cyclefold_circuit, CycleFoldCircuit, CycleFoldCommittedInstance, CycleFoldConfig,
|
||||
CycleFoldWitness,
|
||||
@@ -25,6 +24,7 @@ use crate::transcript::{poseidon::poseidon_canonical_config, AbsorbNonNative, Tr
|
||||
use crate::utils::vec::is_zero_vec;
|
||||
use crate::Error;
|
||||
use crate::FoldingScheme;
|
||||
use crate::{arith::r1cs::RelaxedR1CS, commitment::CommitmentScheme};
|
||||
use crate::{
|
||||
arith::r1cs::{extract_r1cs, extract_w_x, R1CS},
|
||||
constants::NOVA_N_BITS_RO,
|
||||
@@ -40,8 +40,8 @@ pub mod traits;
|
||||
pub mod zk;
|
||||
use circuits::{AugmentedFCircuit, ChallengeGadget};
|
||||
use nifs::NIFS;
|
||||
use traits::NovaR1CS;
|
||||
|
||||
/// Configuration for Nova's CycleFold circuit
|
||||
pub struct NovaCycleFoldConfig<C: CurveGroup> {
|
||||
_c: PhantomData<C>,
|
||||
}
|
||||
@@ -56,7 +56,9 @@ impl<C: CurveGroup> CycleFoldConfig for NovaCycleFoldConfig<C> {
|
||||
type F = C::BaseField;
|
||||
}
|
||||
|
||||
type NovaCycleFoldCircuit<C, GC> = CycleFoldCircuit<NovaCycleFoldConfig<C>, GC>;
|
||||
/// CycleFold circuit for computing random linear combinations of group elements
|
||||
/// in Nova instances.
|
||||
pub type NovaCycleFoldCircuit<C, GC> = CycleFoldCircuit<NovaCycleFoldConfig<C>, GC>;
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)]
|
||||
pub struct CommittedInstance<C: CurveGroup> {
|
||||
@@ -136,10 +138,7 @@ pub struct Witness<C: CurveGroup> {
|
||||
pub rW: C::ScalarField,
|
||||
}
|
||||
|
||||
impl<C: CurveGroup> Witness<C>
|
||||
where
|
||||
<C as Group>::ScalarField: Absorb,
|
||||
{
|
||||
impl<C: CurveGroup> Witness<C> {
|
||||
pub fn new<const H: bool>(w: Vec<C::ScalarField>, e_len: usize, mut rng: impl RngCore) -> Self {
|
||||
let (rW, rE) = if H {
|
||||
(
|
||||
@@ -227,6 +226,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Proving parameters for Nova-based IVC
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ProverParams<C1, C2, CS1, CS2, const H: bool = false>
|
||||
where
|
||||
@@ -235,8 +235,11 @@ where
|
||||
CS1: CommitmentScheme<C1, H>,
|
||||
CS2: CommitmentScheme<C2, H>,
|
||||
{
|
||||
/// Poseidon sponge configuration
|
||||
pub poseidon_config: PoseidonConfig<C1::ScalarField>,
|
||||
/// Proving parameters of the underlying commitment scheme over C1
|
||||
pub cs_pp: CS1::ProverParams,
|
||||
/// Proving parameters of the underlying commitment scheme over C2
|
||||
pub cf_cs_pp: CS2::ProverParams,
|
||||
}
|
||||
|
||||
@@ -302,6 +305,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Verification parameters for Nova-based IVC
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct VerifierParams<C1, C2, CS1, CS2, const H: bool = false>
|
||||
where
|
||||
@@ -310,10 +314,15 @@ where
|
||||
CS1: CommitmentScheme<C1, H>,
|
||||
CS2: CommitmentScheme<C2, H>,
|
||||
{
|
||||
/// Poseidon sponge configuration
|
||||
pub poseidon_config: PoseidonConfig<C1::ScalarField>,
|
||||
/// R1CS of the Augmented step circuit
|
||||
pub r1cs: R1CS<C1::ScalarField>,
|
||||
/// R1CS of the CycleFold circuit
|
||||
pub cf_r1cs: R1CS<C2::ScalarField>,
|
||||
/// Verification parameters of the underlying commitment scheme over C1
|
||||
pub cs_vp: CS1::VerifierParams,
|
||||
/// Verification parameters of the underlying commitment scheme over C2
|
||||
pub cf_cs_vp: CS2::VerifierParams,
|
||||
}
|
||||
|
||||
@@ -553,8 +562,9 @@ where
|
||||
let pp_hash = vp.pp_hash()?;
|
||||
|
||||
// setup the dummy instances
|
||||
let (w_dummy, u_dummy) = r1cs.dummy_instance();
|
||||
let (cf_w_dummy, cf_u_dummy) = cf_r1cs.dummy_instance();
|
||||
let (W_dummy, U_dummy) = r1cs.dummy_running_instance();
|
||||
let (w_dummy, u_dummy) = r1cs.dummy_incoming_instance();
|
||||
let (cf_W_dummy, cf_U_dummy) = cf_r1cs.dummy_running_instance();
|
||||
|
||||
// W_dummy=W_0 is a 'dummy witness', all zeroes, but with the size corresponding to the
|
||||
// R1CS that we're working with.
|
||||
@@ -572,13 +582,13 @@ where
|
||||
i: C1::ScalarField::zero(),
|
||||
z_0: z_0.clone(),
|
||||
z_i: z_0,
|
||||
w_i: w_dummy.clone(),
|
||||
u_i: u_dummy.clone(),
|
||||
W_i: w_dummy,
|
||||
U_i: u_dummy,
|
||||
w_i: w_dummy,
|
||||
u_i: u_dummy,
|
||||
W_i: W_dummy,
|
||||
U_i: U_dummy,
|
||||
// cyclefold running instance
|
||||
cf_W_i: cf_w_dummy.clone(),
|
||||
cf_U_i: cf_u_dummy.clone(),
|
||||
cf_W_i: cf_W_dummy,
|
||||
cf_U_i: cf_U_dummy,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -805,10 +815,10 @@ where
|
||||
|
||||
#[cfg(test)]
|
||||
{
|
||||
self.cf_r1cs.check_instance_relation(&_cfW_w_i, &cfW_u_i)?;
|
||||
self.cf_r1cs.check_instance_relation(&_cfE_w_i, &cfE_u_i)?;
|
||||
self.cf_r1cs.check_tight_relation(&_cfW_w_i, &cfW_u_i)?;
|
||||
self.cf_r1cs.check_tight_relation(&_cfE_w_i, &cfE_u_i)?;
|
||||
self.cf_r1cs
|
||||
.check_relaxed_instance_relation(&self.cf_W_i, &self.cf_U_i)?;
|
||||
.check_relaxed_relation(&self.cf_W_i, &self.cf_U_i)?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -840,9 +850,8 @@ where
|
||||
|
||||
#[cfg(test)]
|
||||
{
|
||||
self.r1cs.check_instance_relation(&self.w_i, &self.u_i)?;
|
||||
self.r1cs
|
||||
.check_relaxed_instance_relation(&self.W_i, &self.U_i)?;
|
||||
self.r1cs.check_tight_relation(&self.w_i, &self.u_i)?;
|
||||
self.r1cs.check_relaxed_relation(&self.W_i, &self.U_i)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -908,19 +917,13 @@ where
|
||||
return Err(Error::IVCVerificationFail);
|
||||
}
|
||||
|
||||
// check u_i.cmE==0, u_i.u==1 (=u_i is a un-relaxed instance)
|
||||
if !u_i.cmE.is_zero() || !u_i.u.is_one() {
|
||||
return Err(Error::IVCVerificationFail);
|
||||
}
|
||||
|
||||
// check R1CS satisfiability
|
||||
vp.r1cs.check_instance_relation(&w_i, &u_i)?;
|
||||
// check R1CS satisfiability, which also enforces u_i.cmE==0, u_i.u==1
|
||||
vp.r1cs.check_tight_relation(&w_i, &u_i)?;
|
||||
// check RelaxedR1CS satisfiability
|
||||
vp.r1cs.check_relaxed_instance_relation(&W_i, &U_i)?;
|
||||
vp.r1cs.check_relaxed_relation(&W_i, &U_i)?;
|
||||
|
||||
// check CycleFold RelaxedR1CS satisfiability
|
||||
vp.cf_r1cs
|
||||
.check_relaxed_instance_relation(&cf_W_i, &cf_U_i)?;
|
||||
vp.cf_r1cs.check_relaxed_relation(&cf_W_i, &cf_U_i)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1077,7 +1080,7 @@ pub mod tests {
|
||||
|
||||
use super::*;
|
||||
use crate::commitment::pedersen::Pedersen;
|
||||
use crate::frontend::tests::CubicFCircuit;
|
||||
use crate::frontend::utils::CubicFCircuit;
|
||||
use crate::transcript::poseidon::poseidon_canonical_config;
|
||||
|
||||
/// This test tests the Nova+CycleFold IVC, and by consequence it is also testing the
|
||||
|
||||
@@ -210,10 +210,12 @@ pub mod tests {
|
||||
use ark_pallas::{Fr, Projective};
|
||||
use ark_std::{ops::Mul, test_rng, UniformRand};
|
||||
|
||||
use crate::arith::r1cs::tests::{get_test_r1cs, get_test_z};
|
||||
use crate::arith::r1cs::{
|
||||
tests::{get_test_r1cs, get_test_z},
|
||||
RelaxedR1CS,
|
||||
};
|
||||
use crate::commitment::pedersen::{Params as PedersenParams, Pedersen};
|
||||
use crate::folding::nova::circuits::ChallengeGadget;
|
||||
use crate::folding::nova::traits::NovaR1CS;
|
||||
use crate::transcript::poseidon::poseidon_canonical_config;
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
@@ -316,8 +318,8 @@ pub mod tests {
|
||||
let u_i = u_dummy.clone();
|
||||
let W_i = w_dummy.clone();
|
||||
let U_i = u_dummy.clone();
|
||||
r1cs.check_relaxed_instance_relation(&w_i, &u_i).unwrap();
|
||||
r1cs.check_relaxed_instance_relation(&W_i, &U_i).unwrap();
|
||||
r1cs.check_relaxed_relation(&w_i, &u_i).unwrap();
|
||||
r1cs.check_relaxed_relation(&W_i, &U_i).unwrap();
|
||||
|
||||
let r_Fr = Fr::from(3_u32);
|
||||
|
||||
@@ -334,7 +336,7 @@ pub mod tests {
|
||||
r_Fr, &w_i, &u_i, &W_i, &U_i, &T, cmT,
|
||||
)
|
||||
.unwrap();
|
||||
r1cs.check_relaxed_instance_relation(&W_i1, &U_i1).unwrap();
|
||||
r1cs.check_relaxed_relation(&W_i1, &U_i1).unwrap();
|
||||
}
|
||||
|
||||
// fold 2 instances into one
|
||||
@@ -348,9 +350,9 @@ pub mod tests {
|
||||
assert_eq!(ci3_v, ci3);
|
||||
|
||||
// check that relations hold for the 2 inputted instances and the folded one
|
||||
r1cs.check_relaxed_instance_relation(&w1, &ci1).unwrap();
|
||||
r1cs.check_relaxed_instance_relation(&w2, &ci2).unwrap();
|
||||
r1cs.check_relaxed_instance_relation(&w3, &ci3).unwrap();
|
||||
r1cs.check_relaxed_relation(&w1, &ci1).unwrap();
|
||||
r1cs.check_relaxed_relation(&w2, &ci2).unwrap();
|
||||
r1cs.check_relaxed_relation(&w3, &ci3).unwrap();
|
||||
|
||||
// check that folded commitments from folded instance (ci) are equal to folding the
|
||||
// use folded rE, rW to commit w3
|
||||
@@ -425,7 +427,7 @@ pub mod tests {
|
||||
.commit::<Pedersen<Projective>, false>(&pedersen_params, x)
|
||||
.unwrap();
|
||||
|
||||
r1cs.check_relaxed_instance_relation(&running_instance_w, &running_committed_instance)
|
||||
r1cs.check_relaxed_relation(&running_instance_w, &running_committed_instance)
|
||||
.unwrap();
|
||||
|
||||
let num_iters = 10;
|
||||
@@ -438,11 +440,8 @@ pub mod tests {
|
||||
let incoming_committed_instance = incoming_instance_w
|
||||
.commit::<Pedersen<Projective>, false>(&pedersen_params, x)
|
||||
.unwrap();
|
||||
r1cs.check_relaxed_instance_relation(
|
||||
&incoming_instance_w,
|
||||
&incoming_committed_instance,
|
||||
)
|
||||
.unwrap();
|
||||
r1cs.check_relaxed_relation(&incoming_instance_w, &incoming_committed_instance)
|
||||
.unwrap();
|
||||
|
||||
let r = Fr::rand(&mut rng); // folding challenge would come from the RO
|
||||
|
||||
@@ -475,7 +474,7 @@ pub mod tests {
|
||||
&cmT,
|
||||
);
|
||||
|
||||
r1cs.check_relaxed_instance_relation(&folded_w, &folded_committed_instance)
|
||||
r1cs.check_relaxed_relation(&folded_w, &folded_committed_instance)
|
||||
.unwrap();
|
||||
|
||||
// set running_instance for next loop iteration
|
||||
|
||||
@@ -187,7 +187,7 @@ pub mod tests {
|
||||
use crate::{
|
||||
commitment::{kzg::KZG, pedersen::Pedersen},
|
||||
folding::nova::{Nova, PreprocessorParam},
|
||||
frontend::{tests::CubicFCircuit, FCircuit},
|
||||
frontend::{utils::CubicFCircuit, FCircuit},
|
||||
transcript::poseidon::poseidon_canonical_config,
|
||||
FoldingScheme,
|
||||
};
|
||||
|
||||
@@ -1,69 +1,90 @@
|
||||
use ark_crypto_primitives::sponge::Absorb;
|
||||
use ark_ec::{CurveGroup, Group};
|
||||
use ark_std::One;
|
||||
use ark_ec::CurveGroup;
|
||||
use ark_std::{rand::RngCore, One, UniformRand};
|
||||
|
||||
use super::{CommittedInstance, Witness};
|
||||
use crate::arith::{r1cs::R1CS, Arith};
|
||||
use crate::arith::r1cs::{RelaxedR1CS, R1CS};
|
||||
use crate::Error;
|
||||
|
||||
/// NovaR1CS extends R1CS methods with Nova specific methods
|
||||
pub trait NovaR1CS<C: CurveGroup> {
|
||||
/// returns a dummy instance (Witness and CommittedInstance) for the current R1CS structure
|
||||
fn dummy_instance(&self) -> (Witness<C>, CommittedInstance<C>);
|
||||
|
||||
/// checks the R1CS relation (un-relaxed) for the given Witness and CommittedInstance.
|
||||
fn check_instance_relation(
|
||||
&self,
|
||||
W: &Witness<C>,
|
||||
U: &CommittedInstance<C>,
|
||||
) -> Result<(), Error>;
|
||||
|
||||
/// checks the Relaxed R1CS relation (corresponding to the current R1CS) for the given Witness
|
||||
/// and CommittedInstance.
|
||||
fn check_relaxed_instance_relation(
|
||||
&self,
|
||||
W: &Witness<C>,
|
||||
U: &CommittedInstance<C>,
|
||||
) -> Result<(), Error>;
|
||||
}
|
||||
|
||||
impl<C: CurveGroup> NovaR1CS<C> for R1CS<C::ScalarField>
|
||||
where
|
||||
<C as Group>::ScalarField: Absorb,
|
||||
<C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField,
|
||||
{
|
||||
fn dummy_instance(&self) -> (Witness<C>, CommittedInstance<C>) {
|
||||
impl<C: CurveGroup> RelaxedR1CS<C, Witness<C>, CommittedInstance<C>> for R1CS<C::ScalarField> {
|
||||
fn dummy_running_instance(&self) -> (Witness<C>, CommittedInstance<C>) {
|
||||
let w_len = self.A.n_cols - 1 - self.l;
|
||||
let w_dummy = Witness::<C>::dummy(w_len, self.A.n_rows);
|
||||
let u_dummy = CommittedInstance::<C>::dummy(self.l);
|
||||
(w_dummy, u_dummy)
|
||||
}
|
||||
|
||||
// notice that this method does not check the commitment correctness
|
||||
fn check_instance_relation(
|
||||
&self,
|
||||
W: &Witness<C>,
|
||||
U: &CommittedInstance<C>,
|
||||
) -> Result<(), Error> {
|
||||
if U.cmE != C::zero() || U.u != C::ScalarField::one() {
|
||||
return Err(Error::R1CSUnrelaxedFail);
|
||||
}
|
||||
|
||||
let Z: Vec<C::ScalarField> = [vec![U.u], U.x.to_vec(), W.W.to_vec()].concat();
|
||||
self.check_relation(&Z)
|
||||
fn dummy_incoming_instance(&self) -> (Witness<C>, CommittedInstance<C>) {
|
||||
self.dummy_running_instance()
|
||||
}
|
||||
|
||||
// notice that this method does not check the commitment correctness
|
||||
fn check_relaxed_instance_relation(
|
||||
&self,
|
||||
W: &Witness<C>,
|
||||
U: &CommittedInstance<C>,
|
||||
) -> Result<(), Error> {
|
||||
let mut rel_r1cs = self.clone().relax();
|
||||
rel_r1cs.u = U.u;
|
||||
rel_r1cs.E = W.E.clone();
|
||||
fn is_relaxed(_w: &Witness<C>, u: &CommittedInstance<C>) -> bool {
|
||||
u.cmE != C::zero() || u.u != C::ScalarField::one()
|
||||
}
|
||||
|
||||
let Z: Vec<C::ScalarField> = [vec![U.u], U.x.to_vec(), W.W.to_vec()].concat();
|
||||
rel_r1cs.check_relation(&Z)
|
||||
fn extract_z(w: &Witness<C>, u: &CommittedInstance<C>) -> Vec<C::ScalarField> {
|
||||
[&[u.u][..], &u.x, &w.W].concat()
|
||||
}
|
||||
|
||||
fn check_error_terms(
|
||||
w: &Witness<C>,
|
||||
_u: &CommittedInstance<C>,
|
||||
e: Vec<C::ScalarField>,
|
||||
) -> Result<(), Error> {
|
||||
if w.E == e {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::NotSatisfied)
|
||||
}
|
||||
}
|
||||
|
||||
fn sample<CS>(
|
||||
&self,
|
||||
params: &CS::ProverParams,
|
||||
mut rng: impl RngCore,
|
||||
) -> Result<(Witness<C>, CommittedInstance<C>), Error>
|
||||
where
|
||||
CS: crate::commitment::CommitmentScheme<C, true>,
|
||||
{
|
||||
// Implements sampling a (committed) RelaxedR1CS
|
||||
// See construction 5 in https://eprint.iacr.org/2023/573.pdf
|
||||
let u = C::ScalarField::rand(&mut rng);
|
||||
let rE = C::ScalarField::rand(&mut rng);
|
||||
let rW = C::ScalarField::rand(&mut rng);
|
||||
|
||||
let W = (0..self.A.n_cols - self.l - 1)
|
||||
.map(|_| C::ScalarField::rand(&mut rng))
|
||||
.collect();
|
||||
let x = (0..self.l)
|
||||
.map(|_| C::ScalarField::rand(&mut rng))
|
||||
.collect::<Vec<C::ScalarField>>();
|
||||
let mut z = vec![u];
|
||||
z.extend(&x);
|
||||
z.extend(&W);
|
||||
|
||||
let E = <Self as RelaxedR1CS<C, Witness<C>, CommittedInstance<C>>>::compute_E(
|
||||
&self.A, &self.B, &self.C, &z, &u,
|
||||
)?;
|
||||
|
||||
debug_assert!(
|
||||
z.len() == self.A.n_cols,
|
||||
"Length of z is {}, while A has {} columns.",
|
||||
z.len(),
|
||||
self.A.n_cols
|
||||
);
|
||||
|
||||
let witness = Witness { E, rE, W, rW };
|
||||
let mut cm_witness = witness.commit::<CS, true>(params, x)?;
|
||||
|
||||
// witness.commit() sets u to 1, we set it to the sampled u value
|
||||
cm_witness.u = u;
|
||||
|
||||
debug_assert!(
|
||||
self.check_relaxed_relation(&witness, &cm_witness).is_ok(),
|
||||
"Sampled a non satisfiable relaxed R1CS, sampled u: {}, computed E: {:?}",
|
||||
u,
|
||||
witness.E
|
||||
);
|
||||
|
||||
Ok((witness, cm_witness))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,6 @@
|
||||
/// paper).
|
||||
/// And the Use-case-2 would require a modified version of the Decider circuits.
|
||||
///
|
||||
use crate::folding::nova::traits::NovaR1CS;
|
||||
use ark_crypto_primitives::sponge::CryptographicSponge;
|
||||
use ark_ff::{BigInteger, PrimeField};
|
||||
use ark_std::{One, Zero};
|
||||
@@ -141,9 +140,8 @@ where
|
||||
// d. Store folding proof
|
||||
let pi = FoldingProof { cmT };
|
||||
|
||||
// 2. Sample a satisfying relaxed R1CS instance-witness pair (U_r, W_r)
|
||||
let relaxed_instance = nova.r1cs.clone().relax();
|
||||
let (U_r, W_r) = relaxed_instance.sample::<C1, CS1>(&nova.cs_pp, &mut rng)?;
|
||||
// 2. Sample a satisfying relaxed R1CS instance-witness pair (W_r, U_r)
|
||||
let (W_r, U_r) = nova.r1cs.sample::<CS1>(&nova.cs_pp, &mut rng)?;
|
||||
|
||||
// 3. Fold the instance-witness pair (U_f, W_f) with (U_r, W_r)
|
||||
// a. Compute T
|
||||
@@ -280,21 +278,10 @@ where
|
||||
);
|
||||
|
||||
// 5. Check that W^{\prime}_i is a satisfying witness
|
||||
let mut z = vec![U_i_prime.u];
|
||||
z.extend(&U_i_prime.x);
|
||||
z.extend(&proof.W_i_prime.W);
|
||||
let relaxed_r1cs = RelaxedR1CS {
|
||||
l: r1cs.l,
|
||||
A: r1cs.A.clone(),
|
||||
B: r1cs.B.clone(),
|
||||
C: r1cs.C.clone(),
|
||||
u: U_i_prime.u,
|
||||
E: proof.W_i_prime.E.clone(),
|
||||
};
|
||||
relaxed_r1cs.check_relation(&z)?;
|
||||
r1cs.check_relaxed_relation(&proof.W_i_prime, &U_i_prime)?;
|
||||
|
||||
// 6. Check that the cyclefold instance-witness pair satisfies the cyclefold relaxed r1cs
|
||||
cf_r1cs.check_relaxed_instance_relation(&proof.cf_W_i, &proof.cf_U_i)?;
|
||||
cf_r1cs.check_relaxed_relation(&proof.cf_W_i, &proof.cf_U_i)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -305,7 +292,7 @@ pub mod tests {
|
||||
use super::*;
|
||||
use crate::commitment::pedersen::Pedersen;
|
||||
use crate::folding::nova::tests::test_ivc_opt;
|
||||
use crate::frontend::tests::CubicFCircuit;
|
||||
use crate::frontend::utils::CubicFCircuit;
|
||||
use crate::transcript::poseidon::poseidon_canonical_config;
|
||||
use ark_bn254::{Fr, G1Projective as Projective};
|
||||
use ark_grumpkin::{constraints::GVar as GVar2, Projective as Projective2};
|
||||
@@ -380,11 +367,9 @@ pub mod tests {
|
||||
F_circuit,
|
||||
3,
|
||||
);
|
||||
let (sampled_committed_instance, _) = nova
|
||||
let (_, sampled_committed_instance) = nova
|
||||
.r1cs
|
||||
.clone()
|
||||
.relax()
|
||||
.sample::<Projective, Pedersen<Projective, true>>(&nova.cs_pp, rng)
|
||||
.sample::<Pedersen<Projective, true>>(&nova.cs_pp, rng)
|
||||
.unwrap();
|
||||
|
||||
// proof verification fails with incorrect running instance
|
||||
@@ -419,11 +404,9 @@ pub mod tests {
|
||||
F_circuit,
|
||||
3,
|
||||
);
|
||||
let (_, sampled_committed_witness) = nova
|
||||
let (sampled_committed_witness, _) = nova
|
||||
.r1cs
|
||||
.clone()
|
||||
.relax()
|
||||
.sample::<Projective, Pedersen<Projective, true>>(&nova.cs_pp, rng)
|
||||
.sample::<Pedersen<Projective, true>>(&nova.cs_pp, rng)
|
||||
.unwrap();
|
||||
|
||||
// proof generation fails with incorrect running witness
|
||||
|
||||
@@ -1,26 +1,46 @@
|
||||
use ark_crypto_primitives::sponge::CryptographicSponge;
|
||||
use ark_crypto_primitives::sponge::{
|
||||
constraints::CryptographicSpongeVar,
|
||||
poseidon::{constraints::PoseidonSpongeVar, PoseidonConfig, PoseidonSponge},
|
||||
Absorb, CryptographicSponge,
|
||||
};
|
||||
use ark_ec::CurveGroup;
|
||||
use ark_ff::PrimeField;
|
||||
use ark_poly::{univariate::DensePolynomial, EvaluationDomain, GeneralEvaluationDomain};
|
||||
use ark_r1cs_std::{
|
||||
alloc::AllocVar,
|
||||
boolean::Boolean,
|
||||
eq::EqGadget,
|
||||
fields::{fp::FpVar, FieldVar},
|
||||
groups::{CurveVar, GroupOpsBounds},
|
||||
poly::polynomial::univariate::dense::DensePolynomialVar,
|
||||
R1CSVar, ToBitsGadget, ToConstraintFieldGadget,
|
||||
};
|
||||
use ark_relations::r1cs::{ConstraintSystemRef, SynthesisError};
|
||||
use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystemRef, SynthesisError};
|
||||
use ark_std::{fmt::Debug, marker::PhantomData, One, Zero};
|
||||
|
||||
use super::{
|
||||
folding::lagrange_polys,
|
||||
utils::{all_powers_var, betas_star_var, exponential_powers_var},
|
||||
CommittedInstanceVar,
|
||||
CommittedInstance, CommittedInstanceVar, ProtoGalaxyCycleFoldConfig,
|
||||
};
|
||||
use crate::{
|
||||
folding::circuits::nonnative::affine::NonNativeAffineVar, transcript::TranscriptVar,
|
||||
folding::circuits::{
|
||||
cyclefold::{
|
||||
CycleFoldChallengeGadget, CycleFoldCommittedInstance, CycleFoldCommittedInstanceVar,
|
||||
CycleFoldConfig, NIFSFullGadget,
|
||||
},
|
||||
nonnative::{affine::NonNativeAffineVar, uint::NonNativeUintVar},
|
||||
CF1, CF2,
|
||||
},
|
||||
frontend::FCircuit,
|
||||
transcript::{AbsorbNonNativeGadget, TranscriptVar},
|
||||
utils::gadgets::VectorGadget,
|
||||
};
|
||||
|
||||
pub struct FoldingGadget {}
|
||||
|
||||
impl FoldingGadget {
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub fn fold_committed_instance<C: CurveGroup, S: CryptographicSponge>(
|
||||
transcript: &mut impl TranscriptVar<C::ScalarField, S>,
|
||||
// running instance
|
||||
@@ -30,9 +50,8 @@ impl FoldingGadget {
|
||||
// polys from P
|
||||
F_coeffs: Vec<FpVar<C::ScalarField>>,
|
||||
K_coeffs: Vec<FpVar<C::ScalarField>>,
|
||||
) -> Result<CommittedInstanceVar<C>, SynthesisError> {
|
||||
) -> Result<(CommittedInstanceVar<C>, Vec<FpVar<C::ScalarField>>), SynthesisError> {
|
||||
let t = instance.betas.len();
|
||||
let n = F_coeffs.len();
|
||||
|
||||
// absorb the committed instances
|
||||
transcript.absorb(instance)?;
|
||||
@@ -44,7 +63,7 @@ impl FoldingGadget {
|
||||
transcript.absorb(&F_coeffs)?;
|
||||
|
||||
let alpha = transcript.get_challenge()?;
|
||||
let alphas = all_powers_var(alpha.clone(), n);
|
||||
let alphas = all_powers_var(alpha.clone(), t);
|
||||
|
||||
// F(alpha) = e + \sum_t F_i * alpha^i
|
||||
let mut F_alpha = instance.e.clone();
|
||||
@@ -88,34 +107,377 @@ impl FoldingGadget {
|
||||
|
||||
let e_star = F_alpha * &L_X_evals[0] + Z_X.evaluate(&gamma)? * K_X.evaluate(&gamma)?;
|
||||
|
||||
let mut u_star = &instance.u * &L_X_evals[0];
|
||||
let mut x_star = instance.x.mul_scalar(&L_X_evals[0])?;
|
||||
for i in 0..k {
|
||||
u_star += &vec_instances[i].u * &L_X_evals[i + 1];
|
||||
x_star = x_star.add(&vec_instances[i].x.mul_scalar(&L_X_evals[i + 1])?)?;
|
||||
}
|
||||
|
||||
// return the folded instance
|
||||
Ok(CommittedInstanceVar {
|
||||
betas: betas_star,
|
||||
// phi will be computed in CycleFold
|
||||
phi: NonNativeAffineVar::new_constant(ConstraintSystemRef::None, C::zero())?,
|
||||
e: e_star,
|
||||
u: u_star,
|
||||
x: x_star,
|
||||
})
|
||||
Ok((
|
||||
CommittedInstanceVar {
|
||||
betas: betas_star,
|
||||
// phi will be computed in CycleFold
|
||||
phi: NonNativeAffineVar::new_constant(ConstraintSystemRef::None, C::zero())?,
|
||||
e: e_star,
|
||||
x: x_star,
|
||||
},
|
||||
L_X_evals,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AugmentationGadget;
|
||||
|
||||
impl AugmentationGadget {
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub fn prepare_and_fold_primary<C: CurveGroup, S: CryptographicSponge>(
|
||||
transcript: &mut impl TranscriptVar<CF1<C>, S>,
|
||||
U: CommittedInstanceVar<C>,
|
||||
u_phis: Vec<NonNativeAffineVar<C>>,
|
||||
u_xs: Vec<Vec<FpVar<CF1<C>>>>,
|
||||
new_U_phi: NonNativeAffineVar<C>,
|
||||
F_coeffs: Vec<FpVar<CF1<C>>>,
|
||||
K_coeffs: Vec<FpVar<CF1<C>>>,
|
||||
) -> Result<(CommittedInstanceVar<C>, Vec<FpVar<CF1<C>>>), SynthesisError> {
|
||||
assert_eq!(u_phis.len(), u_xs.len());
|
||||
|
||||
// Prepare the incoming instances.
|
||||
// For each instance `u`, we have `u.betas = []`, `u.e = 0`.
|
||||
let us = u_phis
|
||||
.into_iter()
|
||||
.zip(u_xs)
|
||||
.map(|(phi, x)| CommittedInstanceVar {
|
||||
phi,
|
||||
betas: vec![],
|
||||
e: FpVar::zero(),
|
||||
x,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Fold the incoming instances `us` into the running instance `U`.
|
||||
let (mut U, L_X_evals) =
|
||||
FoldingGadget::fold_committed_instance(transcript, &U, &us, F_coeffs, K_coeffs)?;
|
||||
// Notice that FoldingGadget::fold_committed_instance does not fold phi.
|
||||
// We set `U.phi` to unconstrained witnesses `U_phi` here, whose
|
||||
// correctness will be checked on the other curve.
|
||||
U.phi = new_U_phi;
|
||||
|
||||
Ok((U, L_X_evals))
|
||||
}
|
||||
|
||||
pub fn prepare_and_fold_cyclefold<
|
||||
C1: CurveGroup<BaseField = C2::ScalarField, ScalarField = C2::BaseField>,
|
||||
C2: CurveGroup,
|
||||
GC2: CurveVar<C2, CF2<C2>> + ToConstraintFieldGadget<CF2<C2>>,
|
||||
S: CryptographicSponge,
|
||||
>(
|
||||
transcript: &mut PoseidonSpongeVar<CF1<C1>>,
|
||||
pp_hash: FpVar<CF1<C1>>,
|
||||
mut cf_U: CycleFoldCommittedInstanceVar<C2, GC2>,
|
||||
cf_u_cmWs: Vec<GC2>,
|
||||
cf_u_xs: Vec<Vec<NonNativeUintVar<CF1<C1>>>>,
|
||||
cf_cmTs: Vec<GC2>,
|
||||
) -> Result<CycleFoldCommittedInstanceVar<C2, GC2>, SynthesisError>
|
||||
where
|
||||
C2::BaseField: PrimeField + Absorb,
|
||||
for<'a> &'a GC2: GroupOpsBounds<'a, C2, GC2>,
|
||||
{
|
||||
assert_eq!(cf_u_cmWs.len(), cf_u_xs.len());
|
||||
assert_eq!(cf_u_xs.len(), cf_cmTs.len());
|
||||
|
||||
// Fold the incoming CycleFold instances into the running CycleFold
|
||||
// instance in a iterative way, since `NIFSFullGadget` only supports
|
||||
// folding one incoming instance at a time.
|
||||
for ((cmW, x), cmT) in cf_u_cmWs.into_iter().zip(cf_u_xs).zip(cf_cmTs) {
|
||||
// Prepare the incoming CycleFold instance `cf_u` for the current
|
||||
// iteration.
|
||||
// For each CycleFold instance `cf_u`, we have `cf_u.cmE = 0`, and
|
||||
// `cf_u.u = 1`.
|
||||
let cf_u = CycleFoldCommittedInstanceVar {
|
||||
cmE: GC2::zero(),
|
||||
u: NonNativeUintVar::new_constant(ConstraintSystemRef::None, C1::BaseField::one())?,
|
||||
cmW,
|
||||
x,
|
||||
};
|
||||
|
||||
let cf_r_bits = CycleFoldChallengeGadget::get_challenge_gadget(
|
||||
transcript,
|
||||
pp_hash.clone(),
|
||||
cf_U.to_native_sponge_field_elements()?,
|
||||
cf_u.clone(),
|
||||
cmT.clone(),
|
||||
)?;
|
||||
// Fold the current incoming CycleFold instance `cf_u` into the
|
||||
// running CycleFold instance `cf_U`.
|
||||
cf_U = NIFSFullGadget::fold_committed_instance(cf_r_bits, cmT, cf_U, cf_u)?;
|
||||
}
|
||||
|
||||
Ok(cf_U)
|
||||
}
|
||||
}
|
||||
|
||||
/// `AugmentedFCircuit` enhances the original step function `F`, so that it can
|
||||
/// be used in recursive arguments such as IVC.
|
||||
///
|
||||
/// The method for converting `F` to `AugmentedFCircuit` (`F'`) is defined in
|
||||
/// [Nova](https://eprint.iacr.org/2021/370.pdf), where `AugmentedFCircuit` not
|
||||
/// only invokes `F`, but also adds additional constraints for verifying the
|
||||
/// correct folding of primary instances (i.e., the instances over `C1`).
|
||||
/// In the paper, the primary instances are Nova's `CommittedInstance`, but we
|
||||
/// extend this method to support using ProtoGalaxy's `CommittedInstance` as
|
||||
/// primary instances.
|
||||
///
|
||||
/// Furthermore, to reduce circuit size over `C2`, we implement the constraints
|
||||
/// defined in [CycleFold](https://eprint.iacr.org/2023/1192.pdf). These extra
|
||||
/// constraints verify the correct folding of CycleFold instances.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AugmentedFCircuit<
|
||||
C1: CurveGroup,
|
||||
C2: CurveGroup,
|
||||
GC2: CurveVar<C2, CF2<C2>>,
|
||||
FC: FCircuit<CF1<C1>>,
|
||||
> {
|
||||
pub(super) _gc2: PhantomData<GC2>,
|
||||
pub(super) poseidon_config: PoseidonConfig<CF1<C1>>,
|
||||
pub(super) pp_hash: CF1<C1>,
|
||||
pub(super) i: CF1<C1>,
|
||||
pub(super) i_usize: usize,
|
||||
pub(super) z_0: Vec<CF1<C1>>,
|
||||
pub(super) z_i: Vec<CF1<C1>>,
|
||||
pub(super) external_inputs: Vec<CF1<C1>>,
|
||||
pub(super) F: FC, // F circuit
|
||||
pub(super) u_i_phi: C1,
|
||||
pub(super) U_i: CommittedInstance<C1>,
|
||||
pub(super) U_i1_phi: C1,
|
||||
pub(super) F_coeffs: Vec<CF1<C1>>,
|
||||
pub(super) K_coeffs: Vec<CF1<C1>>,
|
||||
pub(super) x: Option<CF1<C1>>, // public input (u_{i+1}.x[0])
|
||||
|
||||
pub(super) phi_stars: Vec<C1>,
|
||||
|
||||
pub(super) cf1_u_i_cmW: C2, // input
|
||||
pub(super) cf2_u_i_cmW: C2, // input
|
||||
pub(super) cf_U_i: CycleFoldCommittedInstance<C2>, // input
|
||||
pub(super) cf1_cmT: C2,
|
||||
pub(super) cf2_cmT: C2,
|
||||
pub(super) cf_x: Option<CF1<C1>>, // public input (u_{i+1}.x[1])
|
||||
}
|
||||
|
||||
impl<C1: CurveGroup, C2: CurveGroup, GC2: CurveVar<C2, CF2<C2>>, FC: FCircuit<CF1<C1>>>
|
||||
AugmentedFCircuit<C1, C2, GC2, FC>
|
||||
where
|
||||
for<'a> &'a GC2: GroupOpsBounds<'a, C2, GC2>,
|
||||
{
|
||||
pub fn empty(
|
||||
poseidon_config: &PoseidonConfig<CF1<C1>>,
|
||||
F_circuit: FC,
|
||||
t: usize,
|
||||
d: usize,
|
||||
k: usize,
|
||||
) -> Self {
|
||||
let u_dummy = CommittedInstance::dummy_running(2, t);
|
||||
let cf_u_dummy =
|
||||
CycleFoldCommittedInstance::dummy(ProtoGalaxyCycleFoldConfig::<C1>::IO_LEN);
|
||||
|
||||
Self {
|
||||
_gc2: PhantomData,
|
||||
poseidon_config: poseidon_config.clone(),
|
||||
pp_hash: CF1::<C1>::zero(),
|
||||
i: CF1::<C1>::zero(),
|
||||
i_usize: 0,
|
||||
z_0: vec![CF1::<C1>::zero(); F_circuit.state_len()],
|
||||
z_i: vec![CF1::<C1>::zero(); F_circuit.state_len()],
|
||||
external_inputs: vec![CF1::<C1>::zero(); F_circuit.external_inputs_len()],
|
||||
u_i_phi: C1::zero(),
|
||||
U_i: u_dummy,
|
||||
U_i1_phi: C1::zero(),
|
||||
F_coeffs: vec![CF1::<C1>::zero(); t],
|
||||
K_coeffs: vec![CF1::<C1>::zero(); d * k + 1],
|
||||
phi_stars: vec![C1::zero(); k],
|
||||
F: F_circuit,
|
||||
x: None,
|
||||
// cyclefold values
|
||||
cf1_u_i_cmW: C2::zero(),
|
||||
cf2_u_i_cmW: C2::zero(),
|
||||
cf_U_i: cf_u_dummy,
|
||||
cf1_cmT: C2::zero(),
|
||||
cf2_cmT: C2::zero(),
|
||||
cf_x: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C1, C2, GC2, FC> ConstraintSynthesizer<CF1<C1>> for AugmentedFCircuit<C1, C2, GC2, FC>
|
||||
where
|
||||
C1: CurveGroup<BaseField = C2::ScalarField, ScalarField = C2::BaseField>,
|
||||
C2: CurveGroup,
|
||||
GC2: CurveVar<C2, CF2<C2>> + ToConstraintFieldGadget<CF2<C2>>,
|
||||
FC: FCircuit<CF1<C1>>,
|
||||
C2::BaseField: PrimeField + Absorb,
|
||||
for<'a> &'a GC2: GroupOpsBounds<'a, C2, GC2>,
|
||||
{
|
||||
fn generate_constraints(self, cs: ConstraintSystemRef<CF1<C1>>) -> Result<(), SynthesisError> {
|
||||
let pp_hash = FpVar::<CF1<C1>>::new_witness(cs.clone(), || Ok(self.pp_hash))?;
|
||||
let i = FpVar::<CF1<C1>>::new_witness(cs.clone(), || Ok(self.i))?;
|
||||
let z_0 = Vec::<FpVar<CF1<C1>>>::new_witness(cs.clone(), || Ok(self.z_0))?;
|
||||
let z_i = Vec::<FpVar<CF1<C1>>>::new_witness(cs.clone(), || Ok(self.z_i))?;
|
||||
let external_inputs =
|
||||
Vec::<FpVar<CF1<C1>>>::new_witness(cs.clone(), || Ok(self.external_inputs))?;
|
||||
|
||||
let u_dummy = CommittedInstance::<C1>::dummy_running(2, self.U_i.betas.len());
|
||||
let U_i = CommittedInstanceVar::<C1>::new_witness(cs.clone(), || Ok(self.U_i))?;
|
||||
let u_i_phi = NonNativeAffineVar::new_witness(cs.clone(), || Ok(self.u_i_phi))?;
|
||||
let U_i1_phi = NonNativeAffineVar::new_witness(cs.clone(), || Ok(self.U_i1_phi))?;
|
||||
let phi_stars =
|
||||
Vec::<NonNativeAffineVar<C1>>::new_witness(cs.clone(), || Ok(self.phi_stars))?;
|
||||
|
||||
let cf_u_dummy =
|
||||
CycleFoldCommittedInstance::dummy(ProtoGalaxyCycleFoldConfig::<C1>::IO_LEN);
|
||||
let cf_U_i =
|
||||
CycleFoldCommittedInstanceVar::<C2, GC2>::new_witness(cs.clone(), || Ok(self.cf_U_i))?;
|
||||
let cf1_cmT = GC2::new_witness(cs.clone(), || Ok(self.cf1_cmT))?;
|
||||
let cf2_cmT = GC2::new_witness(cs.clone(), || Ok(self.cf2_cmT))?;
|
||||
|
||||
let F_coeffs = Vec::new_witness(cs.clone(), || Ok(self.F_coeffs))?;
|
||||
let K_coeffs = Vec::new_witness(cs.clone(), || Ok(self.K_coeffs))?;
|
||||
|
||||
// `sponge` is for digest computation.
|
||||
let sponge = PoseidonSpongeVar::<C1::ScalarField>::new(cs.clone(), &self.poseidon_config);
|
||||
// `transcript` is for challenge generation.
|
||||
let mut transcript = sponge.clone();
|
||||
|
||||
// get z_{i+1} from the F circuit
|
||||
let i_usize = self.i_usize;
|
||||
let z_i1 =
|
||||
self.F
|
||||
.generate_step_constraints(cs.clone(), i_usize, z_i.clone(), external_inputs)?;
|
||||
|
||||
let is_basecase = i.is_zero()?;
|
||||
|
||||
// Primary Part
|
||||
// P.1. Compute u_i.x
|
||||
// u_i.x[0] = H(i, z_0, z_i, U_i)
|
||||
let (u_i_x, _) = U_i.clone().hash(
|
||||
&sponge,
|
||||
pp_hash.clone(),
|
||||
i.clone(),
|
||||
z_0.clone(),
|
||||
z_i.clone(),
|
||||
)?;
|
||||
// u_i.x[1] = H(cf_U_i)
|
||||
let (cf_u_i_x, _) = cf_U_i.clone().hash(&sponge, pp_hash.clone())?;
|
||||
|
||||
// P.2. Prepare incoming primary instances
|
||||
// P.3. Fold incoming primary instances into the running instance
|
||||
let (U_i1, r) = AugmentationGadget::prepare_and_fold_primary(
|
||||
&mut transcript,
|
||||
U_i.clone(),
|
||||
vec![u_i_phi.clone()],
|
||||
vec![vec![u_i_x, cf_u_i_x]],
|
||||
U_i1_phi,
|
||||
F_coeffs,
|
||||
K_coeffs,
|
||||
)?;
|
||||
|
||||
// P.4.a compute and check the first output of F'
|
||||
// Base case: u_{i+1}.x[0] == H((i+1, z_0, z_{i+1}, U_{\bot})
|
||||
// Non-base case: u_{i+1}.x[0] == H((i+1, z_0, z_{i+1}, U_{i+1})
|
||||
let (u_i1_x, _) = U_i1.clone().hash(
|
||||
&sponge,
|
||||
pp_hash.clone(),
|
||||
i + FpVar::<CF1<C1>>::one(),
|
||||
z_0.clone(),
|
||||
z_i1.clone(),
|
||||
)?;
|
||||
let (u_i1_x_base, _) = CommittedInstanceVar::new_constant(cs.clone(), u_dummy)?.hash(
|
||||
&sponge,
|
||||
pp_hash.clone(),
|
||||
FpVar::<CF1<C1>>::one(),
|
||||
z_0.clone(),
|
||||
z_i1.clone(),
|
||||
)?;
|
||||
let x = FpVar::new_input(cs.clone(), || Ok(self.x.unwrap_or(u_i1_x_base.value()?)))?;
|
||||
x.enforce_equal(&is_basecase.select(&u_i1_x_base, &u_i1_x)?)?;
|
||||
|
||||
// CycleFold part
|
||||
// C.1. Compute cf1_u_i.x and cf2_u_i.x
|
||||
let mut r0_bits = r[0].to_bits_le()?;
|
||||
let mut r1_bits = r[1].to_bits_le()?;
|
||||
r0_bits.resize(C1::ScalarField::MODULUS_BIT_SIZE as usize, Boolean::FALSE);
|
||||
r1_bits.resize(C1::ScalarField::MODULUS_BIT_SIZE as usize, Boolean::FALSE);
|
||||
let cf1_x = [
|
||||
r0_bits
|
||||
.chunks(C1::BaseField::MODULUS_BIT_SIZE as usize - 1)
|
||||
.map(|bits| {
|
||||
let mut bits = bits.to_vec();
|
||||
bits.resize(C1::BaseField::MODULUS_BIT_SIZE as usize, Boolean::FALSE);
|
||||
NonNativeUintVar::from(&bits)
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
vec![
|
||||
NonNativeUintVar::new_constant(cs.clone(), C1::BaseField::zero())?,
|
||||
NonNativeUintVar::new_constant(cs.clone(), C1::BaseField::zero())?,
|
||||
U_i.phi.x.clone(),
|
||||
U_i.phi.y.clone(),
|
||||
phi_stars[0].x.clone(),
|
||||
phi_stars[0].y.clone(),
|
||||
],
|
||||
]
|
||||
.concat();
|
||||
let cf2_x = [
|
||||
r1_bits
|
||||
.chunks(C1::BaseField::MODULUS_BIT_SIZE as usize - 1)
|
||||
.map(|bits| {
|
||||
let mut bits = bits.to_vec();
|
||||
bits.resize(C1::BaseField::MODULUS_BIT_SIZE as usize, Boolean::FALSE);
|
||||
NonNativeUintVar::from(&bits)
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
vec![
|
||||
phi_stars[0].x.clone(),
|
||||
phi_stars[0].y.clone(),
|
||||
u_i_phi.x.clone(),
|
||||
u_i_phi.y.clone(),
|
||||
U_i1.phi.x.clone(),
|
||||
U_i1.phi.y.clone(),
|
||||
],
|
||||
]
|
||||
.concat();
|
||||
|
||||
// C.2. Prepare incoming CycleFold instances
|
||||
// C.3. Fold incoming CycleFold instances into the running instance
|
||||
let cf_U_i1 =
|
||||
AugmentationGadget::prepare_and_fold_cyclefold::<C1, C2, GC2, PoseidonSponge<CF1<C1>>>(
|
||||
&mut transcript,
|
||||
pp_hash.clone(),
|
||||
cf_U_i,
|
||||
vec![
|
||||
GC2::new_witness(cs.clone(), || Ok(self.cf1_u_i_cmW))?,
|
||||
GC2::new_witness(cs.clone(), || Ok(self.cf2_u_i_cmW))?,
|
||||
],
|
||||
vec![cf1_x, cf2_x],
|
||||
vec![cf1_cmT, cf2_cmT],
|
||||
)?;
|
||||
|
||||
// Back to Primary Part
|
||||
// P.4.b compute and check the second output of F'
|
||||
// Base case: u_{i+1}.x[1] == H(cf_U_{\bot})
|
||||
// Non-base case: u_{i+1}.x[1] == H(cf_U_{i+1})
|
||||
let (cf_u_i1_x, _) = cf_U_i1.clone().hash(&sponge, pp_hash.clone())?;
|
||||
let (cf_u_i1_x_base, _) =
|
||||
CycleFoldCommittedInstanceVar::<C2, GC2>::new_constant(cs.clone(), cf_u_dummy)?
|
||||
.hash(&sponge, pp_hash.clone())?;
|
||||
let cf_x = FpVar::new_input(cs.clone(), || {
|
||||
Ok(self.cf_x.unwrap_or(cf_u_i1_x_base.value()?))
|
||||
})?;
|
||||
cf_x.enforce_equal(&is_basecase.select(&cf_u_i1_x_base, &cf_u_i1_x)?)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use ark_crypto_primitives::sponge::{
|
||||
constraints::CryptographicSpongeVar,
|
||||
poseidon::{constraints::PoseidonSpongeVar, PoseidonSponge},
|
||||
};
|
||||
use ark_pallas::{Fr, Projective};
|
||||
use ark_r1cs_std::R1CSVar;
|
||||
use ark_relations::r1cs::ConstraintSystem;
|
||||
use std::error::Error;
|
||||
|
||||
use super::*;
|
||||
@@ -125,8 +487,11 @@ mod tests {
|
||||
transcript::poseidon::poseidon_canonical_config,
|
||||
};
|
||||
|
||||
use ark_bn254::{Fr, G1Projective as Projective};
|
||||
use ark_relations::r1cs::ConstraintSystem;
|
||||
|
||||
#[test]
|
||||
fn test_fold_gadget() -> Result<(), Box<dyn Error>> {
|
||||
fn test_folding_gadget() -> Result<(), Box<dyn Error>> {
|
||||
let k = 7;
|
||||
let (witness, instance, witnesses, instances) = prepare_inputs(k);
|
||||
let r1cs = get_test_r1cs::<Fr>();
|
||||
@@ -136,7 +501,7 @@ mod tests {
|
||||
let mut transcript_p = PoseidonSponge::new(&poseidon_config);
|
||||
let mut transcript_v = PoseidonSponge::new(&poseidon_config);
|
||||
|
||||
let (_, _, F_coeffs, K_coeffs) = Folding::<Projective>::prove(
|
||||
let (_, _, F_coeffs, K_coeffs, _, _) = Folding::<Projective>::prove(
|
||||
&mut transcript_p,
|
||||
&r1cs,
|
||||
&instance,
|
||||
@@ -147,7 +512,6 @@ mod tests {
|
||||
|
||||
let folded_instance = Folding::<Projective>::verify(
|
||||
&mut transcript_v,
|
||||
&r1cs,
|
||||
&instance,
|
||||
&instances,
|
||||
F_coeffs.clone(),
|
||||
@@ -161,7 +525,7 @@ mod tests {
|
||||
let F_coeffs_var = Vec::new_witness(cs.clone(), || Ok(F_coeffs))?;
|
||||
let K_coeffs_var = Vec::new_witness(cs.clone(), || Ok(K_coeffs))?;
|
||||
|
||||
let folded_instance_var = FoldingGadget::fold_committed_instance(
|
||||
let (folded_instance_var, _) = FoldingGadget::fold_committed_instance(
|
||||
&mut transcript_var,
|
||||
&instance_var,
|
||||
&instances_var,
|
||||
@@ -170,7 +534,6 @@ mod tests {
|
||||
)?;
|
||||
assert_eq!(folded_instance.betas, folded_instance_var.betas.value()?);
|
||||
assert_eq!(folded_instance.e, folded_instance_var.e.value()?);
|
||||
assert_eq!(folded_instance.u, folded_instance_var.u.value()?);
|
||||
assert_eq!(folded_instance.x, folded_instance_var.x.value()?);
|
||||
assert!(cs.is_satisfied()?);
|
||||
|
||||
|
||||
@@ -6,18 +6,19 @@ use ark_poly::{
|
||||
univariate::{DensePolynomial, SparsePolynomial},
|
||||
DenseUVPolynomial, EvaluationDomain, Evaluations, GeneralEvaluationDomain, Polynomial,
|
||||
};
|
||||
use ark_std::{cfg_into_iter, log2, Zero};
|
||||
use rayon::iter::{IntoParallelIterator, ParallelIterator};
|
||||
use ark_std::{cfg_into_iter, log2, One, Zero};
|
||||
use rayon::prelude::*;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use super::utils::{all_powers, betas_star, exponential_powers};
|
||||
use super::utils::{all_powers, betas_star, exponential_powers, pow_i};
|
||||
use super::ProtoGalaxyError;
|
||||
use super::{CommittedInstance, Witness};
|
||||
|
||||
use crate::arith::r1cs::R1CS;
|
||||
#[cfg(test)]
|
||||
use crate::arith::r1cs::RelaxedR1CS;
|
||||
use crate::arith::{r1cs::R1CS, Arith};
|
||||
use crate::transcript::Transcript;
|
||||
use crate::utils::vec::*;
|
||||
use crate::utils::virtual_polynomial::bit_decompose;
|
||||
use crate::Error;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
@@ -48,6 +49,8 @@ where
|
||||
Witness<C::ScalarField>,
|
||||
Vec<C::ScalarField>, // F_X coeffs
|
||||
Vec<C::ScalarField>, // K_X coeffs
|
||||
Vec<C::ScalarField>, // L_X evals
|
||||
Vec<C>, // phi_stars
|
||||
),
|
||||
Error,
|
||||
> {
|
||||
@@ -63,19 +66,25 @@ where
|
||||
let k = vec_instances.len();
|
||||
let t = instance.betas.len();
|
||||
let n = r1cs.A.n_cols;
|
||||
let m = r1cs.A.n_rows;
|
||||
|
||||
let z = [vec![instance.u], instance.x.clone(), w.w.clone()].concat();
|
||||
let z = [vec![C::ScalarField::one()], instance.x.clone(), w.w.clone()].concat();
|
||||
|
||||
if z.len() != n {
|
||||
return Err(Error::NotSameLength(
|
||||
"z.len()".to_string(),
|
||||
z.len(),
|
||||
"n".to_string(),
|
||||
"number of variables in R1CS".to_string(), // hardcoded to R1CS
|
||||
n,
|
||||
));
|
||||
}
|
||||
if log2(n) as usize != t {
|
||||
return Err(Error::NotEqual);
|
||||
if log2(m) as usize != t {
|
||||
return Err(Error::NotSameLength(
|
||||
"log2(number of constraints in R1CS)".to_string(),
|
||||
log2(m) as usize,
|
||||
"instance.betas.len()".to_string(),
|
||||
t,
|
||||
));
|
||||
}
|
||||
if !(k + 1).is_power_of_two() {
|
||||
return Err(Error::ProtoGalaxy(ProtoGalaxyError::WrongNumInstances(k)));
|
||||
@@ -88,13 +97,24 @@ where
|
||||
let delta = transcript.get_challenge();
|
||||
let deltas = exponential_powers(delta, t);
|
||||
|
||||
let f_z = eval_f(r1cs, &z)?;
|
||||
let mut f_z = r1cs.eval_relation(&z)?;
|
||||
if f_z.len() != m {
|
||||
return Err(Error::NotSameLength(
|
||||
"number of constraints in R1CS".to_string(),
|
||||
m,
|
||||
"f_z.len()".to_string(),
|
||||
f_z.len(),
|
||||
));
|
||||
}
|
||||
f_z.resize(1 << t, C::ScalarField::zero());
|
||||
|
||||
// F(X)
|
||||
let F_X: SparsePolynomial<C::ScalarField> =
|
||||
calc_f_from_btree(&f_z, &instance.betas, &deltas).expect("Error calculating F[x]");
|
||||
let F_X_dense = DensePolynomial::from(F_X.clone());
|
||||
transcript.absorb(&F_X_dense.coeffs);
|
||||
let mut F_coeffs = F_X_dense.coeffs;
|
||||
F_coeffs.resize(t, C::ScalarField::zero());
|
||||
transcript.absorb(&F_coeffs);
|
||||
|
||||
let alpha = transcript.get_challenge();
|
||||
|
||||
@@ -107,16 +127,14 @@ where
|
||||
// sanity check: check that the new randomized instance (the original instance but with
|
||||
// 'refreshed' randomness) satisfies the relation.
|
||||
#[cfg(test)]
|
||||
tests::check_instance(
|
||||
r1cs,
|
||||
r1cs.check_relaxed_relation(
|
||||
w,
|
||||
&CommittedInstance {
|
||||
phi: instance.phi,
|
||||
betas: betas_star.clone(),
|
||||
e: F_alpha,
|
||||
u: instance.u,
|
||||
x: instance.x.clone(),
|
||||
},
|
||||
w,
|
||||
)?;
|
||||
|
||||
let zs: Vec<Vec<C::ScalarField>> = std::iter::once(z.clone())
|
||||
@@ -125,12 +143,12 @@ where
|
||||
.iter()
|
||||
.zip(vec_instances)
|
||||
.map(|(wj, uj)| {
|
||||
let zj = [vec![uj.u], uj.x.clone(), wj.w.clone()].concat();
|
||||
let zj = [vec![C::ScalarField::one()], uj.x.clone(), wj.w.clone()].concat();
|
||||
if zj.len() != n {
|
||||
return Err(Error::NotSameLength(
|
||||
"zj.len()".to_string(),
|
||||
zj.len(),
|
||||
"n".to_string(),
|
||||
"number of variables in R1CS".to_string(),
|
||||
n,
|
||||
));
|
||||
}
|
||||
@@ -153,26 +171,19 @@ where
|
||||
// each iteration evaluates G(h)
|
||||
// inner = L_0(x) * z + \sum_k L_i(x) * z_j
|
||||
let mut inner: Vec<C::ScalarField> = vec![C::ScalarField::zero(); zs[0].len()];
|
||||
for (i, z) in zs.iter().enumerate() {
|
||||
for (z, L) in zs.iter().zip(&L_X) {
|
||||
// Li_z_h = (Li(X)*zj)(h) = Li(h) * zj
|
||||
let mut Liz_h: Vec<C::ScalarField> = vec![C::ScalarField::zero(); z.len()];
|
||||
let Lh = L.evaluate(&h);
|
||||
for (j, zj) in z.iter().enumerate() {
|
||||
Liz_h[j] = (&L_X[i] * *zj).evaluate(&h);
|
||||
}
|
||||
|
||||
for j in 0..inner.len() {
|
||||
inner[j] += Liz_h[j];
|
||||
inner[j] += Lh * zj;
|
||||
}
|
||||
}
|
||||
let f_ev = eval_f(r1cs, &inner)?;
|
||||
let f_ev = r1cs.eval_relation(&inner)?;
|
||||
|
||||
let mut Gsum = C::ScalarField::zero();
|
||||
for (i, f_ev_i) in f_ev.iter().enumerate() {
|
||||
let pow_i_betas = pow_i(i, &betas_star);
|
||||
let curr = pow_i_betas * f_ev_i;
|
||||
Gsum += curr;
|
||||
}
|
||||
G_evals[hi] = Gsum;
|
||||
G_evals[hi] = cfg_into_iter!(f_ev)
|
||||
.enumerate()
|
||||
.map(|(i, f_ev_i)| pow_i(i, &betas_star) * f_ev_i)
|
||||
.sum();
|
||||
}
|
||||
let G_X: DensePolynomial<C::ScalarField> =
|
||||
Evaluations::<C::ScalarField>::from_vec_and_domain(G_evals, G_domain).interpolate();
|
||||
@@ -190,7 +201,9 @@ where
|
||||
return Err(Error::ProtoGalaxy(ProtoGalaxyError::RemainderNotZero));
|
||||
}
|
||||
|
||||
transcript.absorb(&K_X.coeffs);
|
||||
let mut K_coeffs = K_X.coeffs.clone();
|
||||
K_coeffs.resize(d * k + 1, C::ScalarField::zero());
|
||||
transcript.absorb(&K_coeffs);
|
||||
|
||||
let gamma = transcript.get_challenge();
|
||||
|
||||
@@ -200,17 +213,18 @@ where
|
||||
.map(|L| L.evaluate(&gamma))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut phi_stars = vec![];
|
||||
|
||||
let e_star = F_alpha * L_X_evals[0] + Z_X.evaluate(&gamma) * K_X.evaluate(&gamma);
|
||||
let mut w_star = vec_scalar_mul(&w.w, &L_X_evals[0]);
|
||||
let mut r_w_star = w.r_w * L_X_evals[0];
|
||||
let mut phi_star = instance.phi * L_X_evals[0];
|
||||
let mut u_star = instance.u * L_X_evals[0];
|
||||
let mut x_star = vec_scalar_mul(&instance.x, &L_X_evals[0]);
|
||||
for i in 0..k {
|
||||
w_star = vec_add(&w_star, &vec_scalar_mul(&vec_w[i].w, &L_X_evals[i + 1]))?;
|
||||
r_w_star += vec_w[i].r_w * L_X_evals[i + 1];
|
||||
phi_stars.push(phi_star); // Push before updating. We don't need the last one
|
||||
phi_star += vec_instances[i].phi * L_X_evals[i + 1];
|
||||
u_star += vec_instances[i].u * L_X_evals[i + 1];
|
||||
x_star = vec_add(
|
||||
&x_star,
|
||||
&vec_scalar_mul(&vec_instances[i].x, &L_X_evals[i + 1]),
|
||||
@@ -222,22 +236,22 @@ where
|
||||
betas: betas_star,
|
||||
phi: phi_star,
|
||||
e: e_star,
|
||||
u: u_star,
|
||||
x: x_star,
|
||||
},
|
||||
Witness {
|
||||
w: w_star,
|
||||
r_w: r_w_star,
|
||||
},
|
||||
F_X_dense.coeffs,
|
||||
K_X.coeffs,
|
||||
F_coeffs,
|
||||
K_coeffs,
|
||||
L_X_evals,
|
||||
phi_stars,
|
||||
))
|
||||
}
|
||||
|
||||
/// implements the non-interactive Verifier from the folding scheme described in section 4
|
||||
pub fn verify(
|
||||
transcript: &mut impl Transcript<C::ScalarField>,
|
||||
r1cs: &R1CS<C::ScalarField>,
|
||||
// running instance
|
||||
instance: &CommittedInstance<C>,
|
||||
// incoming instances
|
||||
@@ -247,7 +261,6 @@ where
|
||||
K_coeffs: Vec<C::ScalarField>,
|
||||
) -> Result<CommittedInstance<C>, Error> {
|
||||
let t = instance.betas.len();
|
||||
let n = r1cs.A.n_cols;
|
||||
|
||||
// absorb the committed instances
|
||||
transcript.absorb(instance);
|
||||
@@ -259,7 +272,7 @@ where
|
||||
transcript.absorb(&F_coeffs);
|
||||
|
||||
let alpha = transcript.get_challenge();
|
||||
let alphas = all_powers(alpha, n);
|
||||
let alphas = all_powers(alpha, t);
|
||||
|
||||
// F(alpha) = e + \sum_t F_i * alpha^i
|
||||
let mut F_alpha = instance.e;
|
||||
@@ -269,6 +282,8 @@ where
|
||||
|
||||
let betas_star = betas_star(&instance.betas, &deltas, alpha);
|
||||
|
||||
transcript.absorb(&K_coeffs);
|
||||
|
||||
let k = vec_instances.len();
|
||||
let H =
|
||||
GeneralEvaluationDomain::<C::ScalarField>::new(k + 1).ok_or(Error::NewDomainFail)?;
|
||||
@@ -277,8 +292,6 @@ where
|
||||
let K_X: DensePolynomial<C::ScalarField> =
|
||||
DensePolynomial::<C::ScalarField>::from_coefficients_vec(K_coeffs);
|
||||
|
||||
transcript.absorb(&K_X.coeffs);
|
||||
|
||||
let gamma = transcript.get_challenge();
|
||||
|
||||
let L_X_evals = L_X
|
||||
@@ -290,11 +303,9 @@ where
|
||||
let e_star = F_alpha * L_X_evals[0] + Z_X.evaluate(&gamma) * K_X.evaluate(&gamma);
|
||||
|
||||
let mut phi_star = instance.phi * L_X_evals[0];
|
||||
let mut u_star = instance.u * L_X_evals[0];
|
||||
let mut x_star = vec_scalar_mul(&instance.x, &L_X_evals[0]);
|
||||
for i in 0..k {
|
||||
phi_star += vec_instances[i].phi * L_X_evals[i + 1];
|
||||
u_star += vec_instances[i].u * L_X_evals[i + 1];
|
||||
x_star = vec_add(
|
||||
&x_star,
|
||||
&vec_scalar_mul(&vec_instances[i].x, &L_X_evals[i + 1]),
|
||||
@@ -306,30 +317,11 @@ where
|
||||
betas: betas_star,
|
||||
phi: phi_star,
|
||||
e: e_star,
|
||||
u: u_star,
|
||||
x: x_star,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// naive impl of pow_i for betas, assuming that betas=(b, b^2, b^4, ..., b^{2^{t-1}})
|
||||
fn pow_i<F: PrimeField>(i: usize, betas: &[F]) -> F {
|
||||
// WIP check if makes more sense to do it with ifs instead of arithmetic
|
||||
|
||||
let n = 2_u64.pow(betas.len() as u32);
|
||||
let b = bit_decompose(i as u64, n as usize);
|
||||
|
||||
let mut r: F = F::one();
|
||||
for (j, beta_j) in betas.iter().enumerate() {
|
||||
let mut b_j = F::zero();
|
||||
if b[j] {
|
||||
b_j = F::one();
|
||||
}
|
||||
r *= (F::one() - b_j) + b_j * beta_j;
|
||||
}
|
||||
r
|
||||
}
|
||||
|
||||
/// calculates F[x] using the optimized binary-tree technique
|
||||
/// described in Claim 4.4
|
||||
/// of [ProtoGalaxy](https://eprint.iacr.org/2023/1106.pdf)
|
||||
@@ -394,16 +386,6 @@ pub fn lagrange_polys<F: PrimeField>(
|
||||
lagrange_polynomials
|
||||
}
|
||||
|
||||
// f(w) in R1CS context. For the moment we use R1CS, in the future we will abstract this with a
|
||||
// trait
|
||||
fn eval_f<F: PrimeField>(r1cs: &R1CS<F>, z: &[F]) -> Result<Vec<F>, Error> {
|
||||
let Az = mat_vec_mul(&r1cs.A, z)?;
|
||||
let Bz = mat_vec_mul(&r1cs.B, z)?;
|
||||
let Cz = mat_vec_mul(&r1cs.C, z)?;
|
||||
let AzBz = hadamard(&Az, &Bz)?;
|
||||
vec_sub(&AzBz, &Cz)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use super::*;
|
||||
@@ -412,38 +394,10 @@ pub mod tests {
|
||||
use ark_pallas::{Fr, Projective};
|
||||
use ark_std::{rand::Rng, UniformRand};
|
||||
|
||||
use crate::arith::r1cs::tests::{get_test_r1cs, get_test_z, get_test_z_split};
|
||||
use crate::arith::r1cs::tests::{get_test_r1cs, get_test_z_split};
|
||||
use crate::commitment::{pedersen::Pedersen, CommitmentScheme};
|
||||
use crate::transcript::poseidon::poseidon_canonical_config;
|
||||
|
||||
pub(crate) fn check_instance<C: CurveGroup>(
|
||||
r1cs: &R1CS<C::ScalarField>,
|
||||
instance: &CommittedInstance<C>,
|
||||
w: &Witness<C::ScalarField>,
|
||||
) -> Result<(), Error> {
|
||||
let z = [vec![instance.u], instance.x.clone(), w.w.clone()].concat();
|
||||
|
||||
if instance.betas.len() != log2(z.len()) as usize {
|
||||
return Err(Error::NotSameLength(
|
||||
"instance.betas.len()".to_string(),
|
||||
instance.betas.len(),
|
||||
"log2(z.len())".to_string(),
|
||||
log2(z.len()) as usize,
|
||||
));
|
||||
}
|
||||
|
||||
let f_z = eval_f(r1cs, &z)?; // f(z)
|
||||
|
||||
let mut r = C::ScalarField::zero();
|
||||
for (i, f_z_i) in f_z.iter().enumerate() {
|
||||
r += pow_i(i, &instance.betas) * f_z_i;
|
||||
}
|
||||
if instance.e == r {
|
||||
return Ok(());
|
||||
}
|
||||
Err(Error::NotSatisfied)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pow_i() {
|
||||
let mut rng = ark_std::test_rng();
|
||||
@@ -459,76 +413,54 @@ pub mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_eval_f() {
|
||||
let mut rng = ark_std::test_rng();
|
||||
let r1cs = get_test_r1cs::<Fr>();
|
||||
let mut z = get_test_z::<Fr>(rng.gen::<u16>() as usize);
|
||||
|
||||
let f_w = eval_f(&r1cs, &z).unwrap();
|
||||
assert!(is_zero_vec(&f_w));
|
||||
|
||||
z[1] = Fr::from(111);
|
||||
let f_w = eval_f(&r1cs, &z).unwrap();
|
||||
assert!(!is_zero_vec(&f_w));
|
||||
}
|
||||
|
||||
// k represents the number of instances to be fold, apart from the running instance
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub fn prepare_inputs(
|
||||
pub fn prepare_inputs<C: CurveGroup>(
|
||||
k: usize,
|
||||
) -> (
|
||||
Witness<Fr>,
|
||||
CommittedInstance<Projective>,
|
||||
Vec<Witness<Fr>>,
|
||||
Vec<CommittedInstance<Projective>>,
|
||||
Witness<C::ScalarField>,
|
||||
CommittedInstance<C>,
|
||||
Vec<Witness<C::ScalarField>>,
|
||||
Vec<CommittedInstance<C>>,
|
||||
) {
|
||||
let mut rng = ark_std::test_rng();
|
||||
|
||||
let (u, x, w) = get_test_z_split::<Fr>(rng.gen::<u16>() as usize);
|
||||
let (_, x, w) = get_test_z_split::<C::ScalarField>(rng.gen::<u16>() as usize);
|
||||
|
||||
let (pedersen_params, _) = Pedersen::<Projective>::setup(&mut rng, w.len()).unwrap();
|
||||
let (pedersen_params, _) = Pedersen::<C>::setup(&mut rng, w.len()).unwrap();
|
||||
|
||||
let n = 1 + x.len() + w.len();
|
||||
let t = log2(n) as usize;
|
||||
let t = log2(get_test_r1cs::<C::ScalarField>().A.n_rows) as usize;
|
||||
|
||||
let beta = Fr::rand(&mut rng);
|
||||
let beta = C::ScalarField::rand(&mut rng);
|
||||
let betas = exponential_powers(beta, t);
|
||||
|
||||
let witness = Witness::<Fr> {
|
||||
let witness = Witness::<C::ScalarField> {
|
||||
w,
|
||||
r_w: Fr::rand(&mut rng),
|
||||
r_w: C::ScalarField::zero(),
|
||||
};
|
||||
let phi = Pedersen::<Projective, true>::commit(&pedersen_params, &witness.w, &witness.r_w)
|
||||
.unwrap();
|
||||
let instance = CommittedInstance::<Projective> {
|
||||
let phi = Pedersen::<C>::commit(&pedersen_params, &witness.w, &witness.r_w).unwrap();
|
||||
let instance = CommittedInstance::<C> {
|
||||
phi,
|
||||
betas: betas.clone(),
|
||||
e: Fr::zero(),
|
||||
u,
|
||||
e: C::ScalarField::zero(),
|
||||
x,
|
||||
};
|
||||
// same for the other instances
|
||||
let mut witnesses: Vec<Witness<Fr>> = Vec::new();
|
||||
let mut instances: Vec<CommittedInstance<Projective>> = Vec::new();
|
||||
let mut witnesses: Vec<Witness<C::ScalarField>> = Vec::new();
|
||||
let mut instances: Vec<CommittedInstance<C>> = Vec::new();
|
||||
#[allow(clippy::needless_range_loop)]
|
||||
for _ in 0..k {
|
||||
let (u_i, x_i, w_i) = get_test_z_split::<Fr>(rng.gen::<u16>() as usize);
|
||||
let witness_i = Witness::<Fr> {
|
||||
let (_, x_i, w_i) = get_test_z_split::<C::ScalarField>(rng.gen::<u16>() as usize);
|
||||
let witness_i = Witness::<C::ScalarField> {
|
||||
w: w_i,
|
||||
r_w: Fr::rand(&mut rng),
|
||||
r_w: C::ScalarField::zero(),
|
||||
};
|
||||
let phi_i = Pedersen::<Projective, true>::commit(
|
||||
&pedersen_params,
|
||||
&witness_i.w,
|
||||
&witness_i.r_w,
|
||||
)
|
||||
.unwrap();
|
||||
let instance_i = CommittedInstance::<Projective> {
|
||||
let phi_i =
|
||||
Pedersen::<C>::commit(&pedersen_params, &witness_i.w, &witness_i.r_w).unwrap();
|
||||
let instance_i = CommittedInstance::<C> {
|
||||
phi: phi_i,
|
||||
betas: vec![],
|
||||
e: Fr::zero(),
|
||||
u: u_i,
|
||||
e: C::ScalarField::zero(),
|
||||
x: x_i,
|
||||
};
|
||||
witnesses.push(witness_i);
|
||||
@@ -549,20 +481,20 @@ pub mod tests {
|
||||
let mut transcript_p = PoseidonSponge::<Fr>::new(&poseidon_config);
|
||||
let mut transcript_v = PoseidonSponge::<Fr>::new(&poseidon_config);
|
||||
|
||||
let (folded_instance, folded_witness, F_coeffs, K_coeffs) = Folding::<Projective>::prove(
|
||||
&mut transcript_p,
|
||||
&r1cs,
|
||||
&instance,
|
||||
&witness,
|
||||
&instances,
|
||||
&witnesses,
|
||||
)
|
||||
.unwrap();
|
||||
let (folded_instance, folded_witness, F_coeffs, K_coeffs, _, _) =
|
||||
Folding::<Projective>::prove(
|
||||
&mut transcript_p,
|
||||
&r1cs,
|
||||
&instance,
|
||||
&witness,
|
||||
&instances,
|
||||
&witnesses,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// verifier
|
||||
let folded_instance_v = Folding::<Projective>::verify(
|
||||
&mut transcript_v,
|
||||
&r1cs,
|
||||
&instance,
|
||||
&instances,
|
||||
F_coeffs,
|
||||
@@ -577,7 +509,8 @@ pub mod tests {
|
||||
assert!(!folded_instance.e.is_zero());
|
||||
|
||||
// check that the folded instance satisfies the relation
|
||||
check_instance(&r1cs, &folded_instance, &folded_witness).unwrap();
|
||||
r1cs.check_relaxed_relation(&folded_witness, &folded_instance)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -598,7 +531,7 @@ pub mod tests {
|
||||
// generate the instances to be fold
|
||||
let (_, _, witnesses, instances) = prepare_inputs(k);
|
||||
|
||||
let (folded_instance, folded_witness, F_coeffs, K_coeffs) =
|
||||
let (folded_instance, folded_witness, F_coeffs, K_coeffs, _, _) =
|
||||
Folding::<Projective>::prove(
|
||||
&mut transcript_p,
|
||||
&r1cs,
|
||||
@@ -612,7 +545,6 @@ pub mod tests {
|
||||
// verifier
|
||||
let folded_instance_v = Folding::<Projective>::verify(
|
||||
&mut transcript_v,
|
||||
&r1cs,
|
||||
&running_instance,
|
||||
&instances,
|
||||
F_coeffs,
|
||||
@@ -626,7 +558,8 @@ pub mod tests {
|
||||
assert!(!folded_instance.e.is_zero());
|
||||
|
||||
// check that the folded instance satisfies the relation
|
||||
check_instance(&r1cs, &folded_instance, &folded_witness).unwrap();
|
||||
r1cs.check_relaxed_relation(&folded_witness, &folded_instance)
|
||||
.unwrap();
|
||||
|
||||
running_witness = folded_witness;
|
||||
running_instance = folded_instance;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3,9 +3,15 @@ use ark_ec::CurveGroup;
|
||||
use ark_ff::PrimeField;
|
||||
use ark_r1cs_std::{fields::fp::FpVar, uint8::UInt8, ToConstraintFieldGadget};
|
||||
use ark_relations::r1cs::SynthesisError;
|
||||
use ark_std::{cfg_iter, log2, rand::RngCore, One, Zero};
|
||||
use rayon::prelude::*;
|
||||
|
||||
use super::{CommittedInstance, CommittedInstanceVar};
|
||||
use crate::transcript::AbsorbNonNative;
|
||||
use super::{utils::pow_i, CommittedInstance, CommittedInstanceVar, Witness};
|
||||
use crate::{
|
||||
arith::r1cs::{RelaxedR1CS, R1CS},
|
||||
transcript::AbsorbNonNative,
|
||||
Error,
|
||||
};
|
||||
|
||||
// Implements the trait for absorbing ProtoGalaxy's CommittedInstance.
|
||||
impl<C: CurveGroup> Absorb for CommittedInstance<C>
|
||||
@@ -22,7 +28,6 @@ where
|
||||
.to_sponge_field_elements(dest);
|
||||
self.betas.to_sponge_field_elements(dest);
|
||||
self.e.to_sponge_field_elements(dest);
|
||||
self.u.to_sponge_field_elements(dest);
|
||||
self.x.to_sponge_field_elements(dest);
|
||||
}
|
||||
}
|
||||
@@ -38,9 +43,74 @@ impl<C: CurveGroup> AbsorbGadget<C::ScalarField> for CommittedInstanceVar<C> {
|
||||
self.phi.to_constraint_field()?,
|
||||
self.betas.to_sponge_field_elements()?,
|
||||
self.e.to_sponge_field_elements()?,
|
||||
self.u.to_sponge_field_elements()?,
|
||||
self.x.to_sponge_field_elements()?,
|
||||
]
|
||||
.concat())
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: CurveGroup> RelaxedR1CS<C, Witness<C::ScalarField>, CommittedInstance<C>>
|
||||
for R1CS<C::ScalarField>
|
||||
{
|
||||
fn dummy_running_instance(&self) -> (Witness<C::ScalarField>, CommittedInstance<C>) {
|
||||
let w_len = self.A.n_cols - 1 - self.l;
|
||||
let w_dummy = Witness::new(vec![C::ScalarField::zero(); w_len]);
|
||||
let u_dummy = CommittedInstance::<C>::dummy_running(self.l, log2(self.A.n_rows) as usize);
|
||||
(w_dummy, u_dummy)
|
||||
}
|
||||
|
||||
fn dummy_incoming_instance(&self) -> (Witness<C::ScalarField>, CommittedInstance<C>) {
|
||||
let w_len = self.A.n_cols - 1 - self.l;
|
||||
let w_dummy = Witness::new(vec![C::ScalarField::zero(); w_len]);
|
||||
let u_dummy = CommittedInstance::<C>::dummy_incoming(self.l);
|
||||
(w_dummy, u_dummy)
|
||||
}
|
||||
|
||||
fn is_relaxed(_w: &Witness<C::ScalarField>, u: &CommittedInstance<C>) -> bool {
|
||||
u.e != C::ScalarField::zero() || !u.betas.is_empty()
|
||||
}
|
||||
|
||||
fn extract_z(w: &Witness<C::ScalarField>, u: &CommittedInstance<C>) -> Vec<C::ScalarField> {
|
||||
[&[C::ScalarField::one()][..], &u.x, &w.w].concat()
|
||||
}
|
||||
|
||||
fn check_error_terms(
|
||||
_w: &Witness<C::ScalarField>,
|
||||
u: &CommittedInstance<C>,
|
||||
e: Vec<C::ScalarField>,
|
||||
) -> Result<(), Error> {
|
||||
if u.betas.len() != log2(e.len()) as usize {
|
||||
return Err(Error::NotSameLength(
|
||||
"instance.betas.len()".to_string(),
|
||||
u.betas.len(),
|
||||
"log2(e.len())".to_string(),
|
||||
log2(e.len()) as usize,
|
||||
));
|
||||
}
|
||||
|
||||
let r = cfg_iter!(e)
|
||||
.enumerate()
|
||||
.map(|(i, e_i)| pow_i(i, &u.betas) * e_i)
|
||||
.sum();
|
||||
if u.e == r {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::NotSatisfied)
|
||||
}
|
||||
}
|
||||
|
||||
fn sample<CS>(
|
||||
&self,
|
||||
_params: &CS::ProverParams,
|
||||
_rng: impl RngCore,
|
||||
) -> Result<(Witness<C::ScalarField>, CommittedInstance<C>), Error>
|
||||
where
|
||||
CS: crate::commitment::CommitmentScheme<C, true>,
|
||||
{
|
||||
// Sampling a random pair of witness and committed instance is required
|
||||
// for the zero-knowledge layer for ProtoGalaxy, which is not supported
|
||||
// yet.
|
||||
// Tracking issue: https://github.com/privacy-scaling-explorations/sonobe/issues/82
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use ark_ff::PrimeField;
|
||||
use ark_r1cs_std::fields::{fp::FpVar, FieldVar};
|
||||
use num_integer::Integer;
|
||||
|
||||
/// Returns (b, b^2, b^4, ..., b^{2^{t-1}})
|
||||
pub fn exponential_powers<F: PrimeField>(b: F, t: usize) -> Vec<F> {
|
||||
@@ -70,6 +71,40 @@ pub fn betas_star_var<F: PrimeField>(
|
||||
.collect::<Vec<FpVar<F>>>()
|
||||
}
|
||||
|
||||
/// Returns the product of selected elements in `betas`.
|
||||
/// For every index `j`, whether `betas[j]` is selected is determined by the
|
||||
/// `j`-th bit in the binary (little endian) representation of `i`.
|
||||
///
|
||||
/// If `betas = (β, β^2, β^4, ..., β^{2^{t-1}})`, then the result is equal to
|
||||
/// `β^i`.
|
||||
pub fn pow_i<F: PrimeField>(mut i: usize, betas: &[F]) -> F {
|
||||
let mut j = 0;
|
||||
let mut r = F::one();
|
||||
while i > 0 {
|
||||
if i.is_odd() {
|
||||
r *= betas[j];
|
||||
}
|
||||
i >>= 1;
|
||||
j += 1;
|
||||
}
|
||||
r
|
||||
}
|
||||
|
||||
/// The in-circuit version of `pow_i`
|
||||
#[allow(dead_code)] // Will remove this once we have the decider circuit for Protogalaxy
|
||||
pub fn pow_i_var<F: PrimeField>(mut i: usize, betas: &[FpVar<F>]) -> FpVar<F> {
|
||||
let mut j = 0;
|
||||
let mut r = FieldVar::one();
|
||||
while i > 0 {
|
||||
if i.is_odd() {
|
||||
r *= &betas[j];
|
||||
}
|
||||
i >>= 1;
|
||||
j += 1;
|
||||
}
|
||||
r
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::error::Error;
|
||||
@@ -78,6 +113,7 @@ mod tests {
|
||||
use ark_r1cs_std::{alloc::AllocVar, R1CSVar};
|
||||
use ark_relations::r1cs::ConstraintSystem;
|
||||
use ark_std::{test_rng, UniformRand};
|
||||
use rand::Rng;
|
||||
|
||||
use super::*;
|
||||
|
||||
@@ -144,4 +180,25 @@ mod tests {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pow_i() -> Result<(), Box<dyn Error>> {
|
||||
let rng = &mut test_rng();
|
||||
|
||||
for t in 1..10 {
|
||||
let cs = ConstraintSystem::<Fr>::new_ref();
|
||||
|
||||
let betas = (0..t).map(|_| Fr::rand(rng)).collect::<Vec<_>>();
|
||||
let i = rng.gen_range(0..(1 << t));
|
||||
|
||||
let betas_var = Vec::new_witness(cs.clone(), || Ok(betas.clone()))?;
|
||||
|
||||
let r = pow_i(i, &betas);
|
||||
let r_var = pow_i_var(i, &betas_var);
|
||||
assert_eq!(r, r_var.value()?);
|
||||
assert!(cs.is_satisfied()?);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -258,7 +258,7 @@ pub mod tests {
|
||||
|
||||
// Allocates z_i1 by using step_native function.
|
||||
let z_i = vec![Fr::from(3_u32)];
|
||||
let wrapper_circuit = crate::frontend::tests::WrapperCircuit {
|
||||
let wrapper_circuit = crate::frontend::utils::WrapperCircuit {
|
||||
FC: circom_fcircuit.clone(),
|
||||
z_i: Some(z_i.clone()),
|
||||
z_i1: Some(circom_fcircuit.step_native(0, z_i.clone(), vec![]).unwrap()),
|
||||
@@ -367,7 +367,7 @@ pub mod tests {
|
||||
|
||||
// Allocates z_i1 by using step_native function.
|
||||
let z_i = vec![Fr::from(3_u32)];
|
||||
let wrapper_circuit = crate::frontend::tests::WrapperCircuit {
|
||||
let wrapper_circuit = crate::frontend::utils::WrapperCircuit {
|
||||
FC: circom_fcircuit.clone(),
|
||||
z_i: Some(z_i.clone()),
|
||||
z_i1: Some(circom_fcircuit.step_native(0, z_i.clone(), vec![]).unwrap()),
|
||||
|
||||
@@ -7,6 +7,7 @@ use ark_std::fmt::Debug;
|
||||
pub mod circom;
|
||||
pub mod noir;
|
||||
pub mod noname;
|
||||
pub mod utils;
|
||||
|
||||
/// FCircuit defines the trait of the circuit of the F function, which is the one being folded (ie.
|
||||
/// inside the agmented F' function).
|
||||
@@ -53,131 +54,9 @@ pub trait FCircuit<F: PrimeField>: Clone + Debug {
|
||||
pub mod tests {
|
||||
use super::*;
|
||||
use ark_bn254::Fr;
|
||||
use ark_r1cs_std::{alloc::AllocVar, eq::EqGadget};
|
||||
use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystem};
|
||||
use core::marker::PhantomData;
|
||||
|
||||
/// CubicFCircuit is a struct that implements the FCircuit trait, for the R1CS example circuit
|
||||
/// from https://www.vitalik.ca/general/2016/12/10/qap.html, which checks `x^3 + x + 5 = y`.
|
||||
/// `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.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct CubicFCircuit<F: PrimeField> {
|
||||
_f: PhantomData<F>,
|
||||
}
|
||||
impl<F: PrimeField> FCircuit<F> for CubicFCircuit<F> {
|
||||
type Params = ();
|
||||
fn new(_params: Self::Params) -> Result<Self, Error> {
|
||||
Ok(Self { _f: PhantomData })
|
||||
}
|
||||
fn state_len(&self) -> usize {
|
||||
1
|
||||
}
|
||||
fn external_inputs_len(&self) -> usize {
|
||||
0
|
||||
}
|
||||
fn step_native(
|
||||
&self,
|
||||
_i: usize,
|
||||
z_i: Vec<F>,
|
||||
_external_inputs: Vec<F>,
|
||||
) -> Result<Vec<F>, Error> {
|
||||
Ok(vec![z_i[0] * z_i[0] * z_i[0] + z_i[0] + F::from(5_u32)])
|
||||
}
|
||||
fn generate_step_constraints(
|
||||
&self,
|
||||
cs: ConstraintSystemRef<F>,
|
||||
_i: usize,
|
||||
z_i: Vec<FpVar<F>>,
|
||||
_external_inputs: 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(vec![&z_i * &z_i * &z_i + &z_i + &five])
|
||||
}
|
||||
}
|
||||
|
||||
/// CustomFCircuit is a circuit that has the number of constraints specified in the
|
||||
/// `n_constraints` parameter. Note that the generated circuit will have very sparse matrices.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct CustomFCircuit<F: PrimeField> {
|
||||
_f: PhantomData<F>,
|
||||
pub n_constraints: usize,
|
||||
}
|
||||
impl<F: PrimeField> FCircuit<F> for CustomFCircuit<F> {
|
||||
type Params = usize;
|
||||
|
||||
fn new(params: Self::Params) -> Result<Self, Error> {
|
||||
Ok(Self {
|
||||
_f: PhantomData,
|
||||
n_constraints: params,
|
||||
})
|
||||
}
|
||||
fn state_len(&self) -> usize {
|
||||
1
|
||||
}
|
||||
fn external_inputs_len(&self) -> usize {
|
||||
0
|
||||
}
|
||||
fn step_native(
|
||||
&self,
|
||||
_i: usize,
|
||||
z_i: Vec<F>,
|
||||
_external_inputs: Vec<F>,
|
||||
) -> Result<Vec<F>, Error> {
|
||||
let mut z_i1 = F::one();
|
||||
for _ in 0..self.n_constraints - 1 {
|
||||
z_i1 *= z_i[0];
|
||||
}
|
||||
Ok(vec![z_i1])
|
||||
}
|
||||
fn generate_step_constraints(
|
||||
&self,
|
||||
cs: ConstraintSystemRef<F>,
|
||||
_i: usize,
|
||||
z_i: Vec<FpVar<F>>,
|
||||
_external_inputs: Vec<FpVar<F>>,
|
||||
) -> Result<Vec<FpVar<F>>, SynthesisError> {
|
||||
let mut z_i1 = FpVar::<F>::new_witness(cs.clone(), || Ok(F::one()))?;
|
||||
for _ in 0..self.n_constraints - 1 {
|
||||
z_i1 *= z_i[0].clone();
|
||||
}
|
||||
|
||||
Ok(vec![z_i1])
|
||||
}
|
||||
}
|
||||
|
||||
/// WrapperCircuit is a circuit that wraps any circuit that implements the FCircuit trait. This
|
||||
/// is used to test the `FCircuit.generate_step_constraints` method. This is a similar wrapping
|
||||
/// than the one done in the `AugmentedFCircuit`, but without adding all the extra constraints
|
||||
/// of the AugmentedF circuit logic, in order to run lighter tests when we're not interested in
|
||||
/// the the AugmentedF logic but in the wrapping of the circuits.
|
||||
pub struct WrapperCircuit<F: PrimeField, FC: FCircuit<F>> {
|
||||
pub FC: FC, // F circuit
|
||||
pub z_i: Option<Vec<F>>,
|
||||
pub z_i1: Option<Vec<F>>,
|
||||
}
|
||||
impl<F, FC> ConstraintSynthesizer<F> for WrapperCircuit<F, FC>
|
||||
where
|
||||
F: PrimeField,
|
||||
FC: FCircuit<F>,
|
||||
{
|
||||
fn generate_constraints(self, cs: ConstraintSystemRef<F>) -> Result<(), SynthesisError> {
|
||||
let z_i = Vec::<FpVar<F>>::new_witness(cs.clone(), || {
|
||||
Ok(self.z_i.unwrap_or(vec![F::zero()]))
|
||||
})?;
|
||||
let z_i1 = Vec::<FpVar<F>>::new_input(cs.clone(), || {
|
||||
Ok(self.z_i1.unwrap_or(vec![F::zero()]))
|
||||
})?;
|
||||
let computed_z_i1 =
|
||||
self.FC
|
||||
.generate_step_constraints(cs.clone(), 0, z_i.clone(), vec![])?;
|
||||
|
||||
computed_z_i1.enforce_equal(&z_i1)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
use utils::{CubicFCircuit, CustomFCircuit, WrapperCircuit};
|
||||
|
||||
#[test]
|
||||
fn test_testfcircuit() {
|
||||
|
||||
181
folding-schemes/src/frontend/utils.rs
Normal file
181
folding-schemes/src/frontend/utils.rs
Normal file
@@ -0,0 +1,181 @@
|
||||
use ark_ff::PrimeField;
|
||||
use ark_r1cs_std::{alloc::AllocVar, fields::fp::FpVar};
|
||||
use ark_relations::r1cs::{ConstraintSystemRef, SynthesisError};
|
||||
#[cfg(test)]
|
||||
use ark_std::marker::PhantomData;
|
||||
use ark_std::{fmt::Debug, Zero};
|
||||
|
||||
use super::FCircuit;
|
||||
use crate::Error;
|
||||
|
||||
/// DummyCircuit is a circuit that has dummy state and external inputs whose
|
||||
/// lengths are specified in the `state_len` and `external_inputs_len`
|
||||
/// parameters, without any constraints.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct DummyCircuit {
|
||||
state_len: usize,
|
||||
external_inputs_len: usize,
|
||||
}
|
||||
impl<F: PrimeField> FCircuit<F> for DummyCircuit {
|
||||
type Params = (usize, usize);
|
||||
|
||||
fn new((state_len, external_inputs_len): Self::Params) -> Result<Self, Error> {
|
||||
Ok(Self {
|
||||
state_len,
|
||||
external_inputs_len,
|
||||
})
|
||||
}
|
||||
fn state_len(&self) -> usize {
|
||||
self.state_len
|
||||
}
|
||||
fn external_inputs_len(&self) -> usize {
|
||||
self.external_inputs_len
|
||||
}
|
||||
fn step_native(
|
||||
&self,
|
||||
_i: usize,
|
||||
_z_i: Vec<F>,
|
||||
_external_inputs: Vec<F>,
|
||||
) -> Result<Vec<F>, Error> {
|
||||
Ok(vec![F::zero(); self.state_len])
|
||||
}
|
||||
fn generate_step_constraints(
|
||||
&self,
|
||||
cs: ConstraintSystemRef<F>,
|
||||
_i: usize,
|
||||
_z_i: Vec<FpVar<F>>,
|
||||
_external_inputs: Vec<FpVar<F>>,
|
||||
) -> Result<Vec<FpVar<F>>, SynthesisError> {
|
||||
Vec::new_witness(cs.clone(), || Ok(vec![Zero::zero(); self.state_len]))
|
||||
}
|
||||
}
|
||||
|
||||
/// CubicFCircuit is a struct that implements the FCircuit trait, for the R1CS example circuit
|
||||
/// from https://www.vitalik.ca/general/2016/12/10/qap.html, which checks `x^3 + x + 5 = y`.
|
||||
/// `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.
|
||||
#[cfg(test)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct CubicFCircuit<F: PrimeField> {
|
||||
_f: PhantomData<F>,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
impl<F: PrimeField> FCircuit<F> for CubicFCircuit<F> {
|
||||
type Params = ();
|
||||
fn new(_params: Self::Params) -> Result<Self, Error> {
|
||||
Ok(Self { _f: PhantomData })
|
||||
}
|
||||
fn state_len(&self) -> usize {
|
||||
1
|
||||
}
|
||||
fn external_inputs_len(&self) -> usize {
|
||||
0
|
||||
}
|
||||
fn step_native(
|
||||
&self,
|
||||
_i: usize,
|
||||
z_i: Vec<F>,
|
||||
_external_inputs: Vec<F>,
|
||||
) -> Result<Vec<F>, Error> {
|
||||
Ok(vec![z_i[0] * z_i[0] * z_i[0] + z_i[0] + F::from(5_u32)])
|
||||
}
|
||||
fn generate_step_constraints(
|
||||
&self,
|
||||
cs: ConstraintSystemRef<F>,
|
||||
_i: usize,
|
||||
z_i: Vec<FpVar<F>>,
|
||||
_external_inputs: 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(vec![&z_i * &z_i * &z_i + &z_i + &five])
|
||||
}
|
||||
}
|
||||
|
||||
/// CustomFCircuit is a circuit that has the number of constraints specified in the
|
||||
/// `n_constraints` parameter. Note that the generated circuit will have very sparse matrices.
|
||||
#[cfg(test)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct CustomFCircuit<F: PrimeField> {
|
||||
_f: PhantomData<F>,
|
||||
pub n_constraints: usize,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
impl<F: PrimeField> FCircuit<F> for CustomFCircuit<F> {
|
||||
type Params = usize;
|
||||
|
||||
fn new(params: Self::Params) -> Result<Self, Error> {
|
||||
Ok(Self {
|
||||
_f: PhantomData,
|
||||
n_constraints: params,
|
||||
})
|
||||
}
|
||||
fn state_len(&self) -> usize {
|
||||
1
|
||||
}
|
||||
fn external_inputs_len(&self) -> usize {
|
||||
0
|
||||
}
|
||||
fn step_native(
|
||||
&self,
|
||||
_i: usize,
|
||||
z_i: Vec<F>,
|
||||
_external_inputs: Vec<F>,
|
||||
) -> Result<Vec<F>, Error> {
|
||||
let mut z_i1 = F::one();
|
||||
for _ in 0..self.n_constraints - 1 {
|
||||
z_i1 *= z_i[0];
|
||||
}
|
||||
Ok(vec![z_i1])
|
||||
}
|
||||
fn generate_step_constraints(
|
||||
&self,
|
||||
cs: ConstraintSystemRef<F>,
|
||||
_i: usize,
|
||||
z_i: Vec<FpVar<F>>,
|
||||
_external_inputs: Vec<FpVar<F>>,
|
||||
) -> Result<Vec<FpVar<F>>, SynthesisError> {
|
||||
let mut z_i1 = FpVar::<F>::new_witness(cs.clone(), || Ok(F::one()))?;
|
||||
for _ in 0..self.n_constraints - 1 {
|
||||
z_i1 *= z_i[0].clone();
|
||||
}
|
||||
|
||||
Ok(vec![z_i1])
|
||||
}
|
||||
}
|
||||
|
||||
/// WrapperCircuit is a circuit that wraps any circuit that implements the FCircuit trait. This
|
||||
/// is used to test the `FCircuit.generate_step_constraints` method. This is a similar wrapping
|
||||
/// than the one done in the `AugmentedFCircuit`, but without adding all the extra constraints
|
||||
/// of the AugmentedF circuit logic, in order to run lighter tests when we're not interested in
|
||||
/// the the AugmentedF logic but in the wrapping of the circuits.
|
||||
#[cfg(test)]
|
||||
pub struct WrapperCircuit<F: PrimeField, FC: FCircuit<F>> {
|
||||
pub FC: FC, // F circuit
|
||||
pub z_i: Option<Vec<F>>,
|
||||
pub z_i1: Option<Vec<F>>,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
impl<F, FC> ark_relations::r1cs::ConstraintSynthesizer<F> for WrapperCircuit<F, FC>
|
||||
where
|
||||
F: PrimeField,
|
||||
FC: FCircuit<F>,
|
||||
{
|
||||
fn generate_constraints(self, cs: ConstraintSystemRef<F>) -> Result<(), SynthesisError> {
|
||||
let z_i =
|
||||
Vec::<FpVar<F>>::new_witness(cs.clone(), || Ok(self.z_i.unwrap_or(vec![F::zero()])))?;
|
||||
let z_i1 =
|
||||
Vec::<FpVar<F>>::new_input(cs.clone(), || Ok(self.z_i1.unwrap_or(vec![F::zero()])))?;
|
||||
let computed_z_i1 =
|
||||
self.FC
|
||||
.generate_step_constraints(cs.clone(), 0, z_i.clone(), vec![])?;
|
||||
|
||||
use ark_r1cs_std::eq::EqGadget;
|
||||
computed_z_i1.enforce_equal(&z_i1)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -82,7 +82,7 @@ pub fn vec_add<F: PrimeField>(a: &[F], b: &[F]) -> Result<Vec<F>, Error> {
|
||||
b.len(),
|
||||
));
|
||||
}
|
||||
Ok(a.iter().zip(b.iter()).map(|(x, y)| *x + y).collect())
|
||||
Ok(cfg_iter!(a).zip(b).map(|(x, y)| *x + y).collect())
|
||||
}
|
||||
|
||||
pub fn vec_sub<F: PrimeField>(a: &[F], b: &[F]) -> Result<Vec<F>, Error> {
|
||||
@@ -94,15 +94,15 @@ pub fn vec_sub<F: PrimeField>(a: &[F], b: &[F]) -> Result<Vec<F>, Error> {
|
||||
b.len(),
|
||||
));
|
||||
}
|
||||
Ok(a.iter().zip(b.iter()).map(|(x, y)| *x - y).collect())
|
||||
Ok(cfg_iter!(a).zip(b).map(|(x, y)| *x - y).collect())
|
||||
}
|
||||
|
||||
pub fn vec_scalar_mul<F: PrimeField>(vec: &[F], c: &F) -> Vec<F> {
|
||||
vec.iter().map(|a| *a * c).collect()
|
||||
cfg_iter!(vec).map(|a| *a * c).collect()
|
||||
}
|
||||
|
||||
pub fn is_zero_vec<F: PrimeField>(vec: &[F]) -> bool {
|
||||
vec.iter().all(|a| a.is_zero())
|
||||
cfg_iter!(vec).all(|a| a.is_zero())
|
||||
}
|
||||
|
||||
pub fn mat_vec_mul_dense<F: PrimeField>(M: &[Vec<F>], z: &[F]) -> Result<Vec<F>, Error> {
|
||||
@@ -118,13 +118,9 @@ pub fn mat_vec_mul_dense<F: PrimeField>(M: &[Vec<F>], z: &[F]) -> Result<Vec<F>,
|
||||
));
|
||||
}
|
||||
|
||||
let mut r: Vec<F> = vec![F::zero(); M.len()];
|
||||
for (i, M_i) in M.iter().enumerate() {
|
||||
for (j, M_ij) in M_i.iter().enumerate() {
|
||||
r[i] += *M_ij * z[j];
|
||||
}
|
||||
}
|
||||
Ok(r)
|
||||
Ok(cfg_iter!(M)
|
||||
.map(|row| row.iter().zip(z).map(|(a, b)| *a * b).sum())
|
||||
.collect())
|
||||
}
|
||||
|
||||
pub fn mat_vec_mul<F: PrimeField>(M: &SparseMatrix<F>, z: &[F]) -> Result<Vec<F>, Error> {
|
||||
@@ -136,13 +132,9 @@ pub fn mat_vec_mul<F: PrimeField>(M: &SparseMatrix<F>, z: &[F]) -> Result<Vec<F>
|
||||
z.len(),
|
||||
));
|
||||
}
|
||||
let mut res = vec![F::zero(); M.n_rows];
|
||||
for (row_i, row) in M.coeffs.iter().enumerate() {
|
||||
for &(value, col_i) in row.iter() {
|
||||
res[row_i] += value * z[col_i];
|
||||
}
|
||||
}
|
||||
Ok(res)
|
||||
Ok(cfg_iter!(M.coeffs)
|
||||
.map(|row| row.iter().map(|(value, col_i)| *value * z[*col_i]).sum())
|
||||
.collect())
|
||||
}
|
||||
|
||||
pub fn mat_from_str_mat<F: PrimeField>(str_mat: Vec<Vec<&str>>) -> Result<Vec<Vec<F>>, Error> {
|
||||
|
||||
Reference in New Issue
Block a user