mirror of
https://github.com/arnaucube/sonobe.git
synced 2026-01-07 14:31:31 +01:00
Onchain decider circuit for Protogalaxy (#145)
* Move r1cs and ccs to standalone folders * Simplify type bounds of SparseMatrixVar * Implement `EquivalenceGadget` trait for `FpVar` and `NonNativeUintVar`. Together with the existing `MatrixGadget` and `VectorGadget`, we can now use the same logic for checking R1CS satisfiability of `R1CSVar` both natively and non-natively. * Simplify trait bounds * Implement `ArithGadget` for `R1CSMatricesVar` and `CCSMatricesVar` * `PedersenGadget::commit` now takes slices as input * Structs for proofs and auxiliary values in protogalaxy * `u` in LCCCS should be `z[0]` * `Inputize` trait * Generic decider circuits * Verifier should check the commitments in committed instances * Update the comments according to the new docs * Fix examples * Add `DeciderEnabledNIFS::fold_group_elements_native` to wrap code for folding commitments * Fix incorrect endian * Format * Get rid of `unwrap` when possible
This commit is contained in:
@@ -19,9 +19,12 @@ use std::time::Instant;
|
||||
|
||||
use folding_schemes::{
|
||||
commitment::{kzg::KZG, pedersen::Pedersen},
|
||||
folding::nova::{
|
||||
decider_eth::{prepare_calldata, Decider as DeciderEth},
|
||||
Nova, PreprocessorParam,
|
||||
folding::{
|
||||
nova::{
|
||||
decider_eth::{prepare_calldata, Decider as DeciderEth},
|
||||
Nova, PreprocessorParam,
|
||||
},
|
||||
traits::CommittedInstanceOps,
|
||||
},
|
||||
frontend::FCircuit,
|
||||
transcript::poseidon::poseidon_canonical_config,
|
||||
@@ -83,7 +86,6 @@ fn main() {
|
||||
// prepare the Nova prover & verifier params
|
||||
let nova_preprocess_params = PreprocessorParam::new(poseidon_config, f_circuit.clone());
|
||||
let nova_params = N::preprocess(&mut rng, &nova_preprocess_params).unwrap();
|
||||
let pp_hash = nova_params.1.pp_hash().unwrap();
|
||||
|
||||
// initialize the folding scheme engine, in our case we use Nova
|
||||
let mut nova = N::init(&nova_params, f_circuit.clone(), z_0).unwrap();
|
||||
@@ -117,8 +119,8 @@ fn main() {
|
||||
nova.i,
|
||||
nova.z_0.clone(),
|
||||
nova.z_i.clone(),
|
||||
&nova.U_i,
|
||||
&nova.u_i,
|
||||
&nova.U_i.get_commitments(),
|
||||
&nova.u_i.get_commitments(),
|
||||
&proof,
|
||||
)
|
||||
.unwrap();
|
||||
@@ -131,7 +133,6 @@ fn main() {
|
||||
|
||||
let calldata: Vec<u8> = prepare_calldata(
|
||||
function_selector,
|
||||
pp_hash,
|
||||
nova.i,
|
||||
nova.z_0,
|
||||
nova.z_i,
|
||||
|
||||
@@ -21,9 +21,12 @@ use std::time::Instant;
|
||||
|
||||
use folding_schemes::{
|
||||
commitment::{kzg::KZG, pedersen::Pedersen},
|
||||
folding::nova::{
|
||||
decider_eth::{prepare_calldata, Decider as DeciderEth},
|
||||
Nova, PreprocessorParam,
|
||||
folding::{
|
||||
nova::{
|
||||
decider_eth::{prepare_calldata, Decider as DeciderEth},
|
||||
Nova, PreprocessorParam,
|
||||
},
|
||||
traits::CommittedInstanceOps,
|
||||
},
|
||||
frontend::FCircuit,
|
||||
transcript::poseidon::poseidon_canonical_config,
|
||||
@@ -101,7 +104,6 @@ fn main() {
|
||||
// prepare the Nova prover & verifier params
|
||||
let nova_preprocess_params = PreprocessorParam::new(poseidon_config.clone(), f_circuit);
|
||||
let nova_params = N::preprocess(&mut rng, &nova_preprocess_params).unwrap();
|
||||
let pp_hash = nova_params.1.pp_hash().unwrap();
|
||||
|
||||
// initialize the folding scheme engine, in our case we use Nova
|
||||
let mut nova = N::init(&nova_params, f_circuit, z_0).unwrap();
|
||||
@@ -125,8 +127,8 @@ fn main() {
|
||||
nova.i,
|
||||
nova.z_0.clone(),
|
||||
nova.z_i.clone(),
|
||||
&nova.U_i,
|
||||
&nova.u_i,
|
||||
&nova.U_i.get_commitments(),
|
||||
&nova.u_i.get_commitments(),
|
||||
&proof,
|
||||
)
|
||||
.unwrap();
|
||||
@@ -139,7 +141,6 @@ fn main() {
|
||||
|
||||
let calldata: Vec<u8> = prepare_calldata(
|
||||
function_selector,
|
||||
pp_hash,
|
||||
nova.i,
|
||||
nova.z_0,
|
||||
nova.z_i,
|
||||
|
||||
@@ -16,9 +16,12 @@ use ark_grumpkin::{constraints::GVar as GVar2, Projective as G2};
|
||||
|
||||
use folding_schemes::{
|
||||
commitment::{kzg::KZG, pedersen::Pedersen},
|
||||
folding::nova::{
|
||||
decider_eth::{prepare_calldata, Decider as DeciderEth},
|
||||
Nova, PreprocessorParam,
|
||||
folding::{
|
||||
nova::{
|
||||
decider_eth::{prepare_calldata, Decider as DeciderEth},
|
||||
Nova, PreprocessorParam,
|
||||
},
|
||||
traits::CommittedInstanceOps,
|
||||
},
|
||||
frontend::FCircuit,
|
||||
transcript::poseidon::poseidon_canonical_config,
|
||||
@@ -46,7 +49,7 @@ fn main() {
|
||||
cur_path.to_str().unwrap()
|
||||
);
|
||||
|
||||
let circuit = load_noir_circuit(circuit_path);
|
||||
let circuit = load_noir_circuit(circuit_path).unwrap();
|
||||
let f_circuit = NoirFCircuit {
|
||||
circuit,
|
||||
state_len: 1,
|
||||
@@ -72,7 +75,6 @@ fn main() {
|
||||
// prepare the Nova prover & verifier params
|
||||
let nova_preprocess_params = PreprocessorParam::new(poseidon_config, f_circuit.clone());
|
||||
let nova_params = N::preprocess(&mut rng, &nova_preprocess_params).unwrap();
|
||||
let pp_hash = nova_params.1.pp_hash().unwrap();
|
||||
|
||||
// initialize the folding scheme engine, in our case we use Nova
|
||||
let mut nova = N::init(&nova_params, f_circuit.clone(), z_0).unwrap();
|
||||
@@ -104,8 +106,8 @@ fn main() {
|
||||
nova.i,
|
||||
nova.z_0.clone(),
|
||||
nova.z_i.clone(),
|
||||
&nova.U_i,
|
||||
&nova.u_i,
|
||||
&nova.U_i.get_commitments(),
|
||||
&nova.u_i.get_commitments(),
|
||||
&proof,
|
||||
)
|
||||
.unwrap();
|
||||
@@ -118,7 +120,6 @@ fn main() {
|
||||
|
||||
let calldata: Vec<u8> = prepare_calldata(
|
||||
function_selector,
|
||||
pp_hash,
|
||||
nova.i,
|
||||
nova.z_0,
|
||||
nova.z_i,
|
||||
|
||||
@@ -17,9 +17,12 @@ use ark_grumpkin::{constraints::GVar as GVar2, Projective as G2};
|
||||
|
||||
use folding_schemes::{
|
||||
commitment::{kzg::KZG, pedersen::Pedersen},
|
||||
folding::nova::{
|
||||
decider_eth::{prepare_calldata, Decider as DeciderEth},
|
||||
Nova, PreprocessorParam,
|
||||
folding::{
|
||||
nova::{
|
||||
decider_eth::{prepare_calldata, Decider as DeciderEth},
|
||||
Nova, PreprocessorParam,
|
||||
},
|
||||
traits::CommittedInstanceOps,
|
||||
},
|
||||
frontend::FCircuit,
|
||||
transcript::poseidon::poseidon_canonical_config,
|
||||
@@ -85,7 +88,6 @@ fn main() {
|
||||
// prepare the Nova prover & verifier params
|
||||
let nova_preprocess_params = PreprocessorParam::new(poseidon_config, f_circuit.clone());
|
||||
let nova_params = N::preprocess(&mut rng, &nova_preprocess_params).unwrap();
|
||||
let pp_hash = nova_params.1.pp_hash().unwrap();
|
||||
|
||||
// initialize the folding scheme engine, in our case we use Nova
|
||||
let mut nova = N::init(&nova_params, f_circuit.clone(), z_0).unwrap();
|
||||
@@ -119,8 +121,8 @@ fn main() {
|
||||
nova.i,
|
||||
nova.z_0.clone(),
|
||||
nova.z_i.clone(),
|
||||
&nova.U_i,
|
||||
&nova.u_i,
|
||||
&nova.U_i.get_commitments(),
|
||||
&nova.u_i.get_commitments(),
|
||||
&proof,
|
||||
)
|
||||
.unwrap();
|
||||
@@ -133,7 +135,6 @@ fn main() {
|
||||
|
||||
let calldata: Vec<u8> = prepare_calldata(
|
||||
function_selector,
|
||||
pp_hash,
|
||||
nova.i,
|
||||
nova.z_0,
|
||||
nova.z_i,
|
||||
|
||||
35
folding-schemes/src/arith/ccs/circuits.rs
Normal file
35
folding-schemes/src/arith/ccs/circuits.rs
Normal file
@@ -0,0 +1,35 @@
|
||||
use super::CCS;
|
||||
use crate::utils::gadgets::SparseMatrixVar;
|
||||
use ark_ff::PrimeField;
|
||||
use ark_r1cs_std::{
|
||||
alloc::{AllocVar, AllocationMode},
|
||||
fields::fp::FpVar,
|
||||
};
|
||||
use ark_relations::r1cs::{Namespace, SynthesisError};
|
||||
use ark_std::borrow::Borrow;
|
||||
/// CCSMatricesVar contains the matrices 'M' of the CCS without the rest of CCS parameters.
|
||||
///
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CCSMatricesVar<F: PrimeField> {
|
||||
// we only need native representation, so the constraint field==F
|
||||
pub M: Vec<SparseMatrixVar<FpVar<F>>>,
|
||||
}
|
||||
|
||||
impl<F: PrimeField> AllocVar<CCS<F>, F> for CCSMatricesVar<F> {
|
||||
fn new_variable<T: Borrow<CCS<F>>>(
|
||||
cs: impl Into<Namespace<F>>,
|
||||
f: impl FnOnce() -> Result<T, SynthesisError>,
|
||||
_mode: AllocationMode,
|
||||
) -> Result<Self, SynthesisError> {
|
||||
f().and_then(|val| {
|
||||
let cs = cs.into();
|
||||
let M: Vec<SparseMatrixVar<FpVar<F>>> = val
|
||||
.borrow()
|
||||
.M
|
||||
.iter()
|
||||
.map(|M| SparseMatrixVar::<FpVar<F>>::new_constant(cs.clone(), M.clone()))
|
||||
.collect::<Result<_, SynthesisError>>()?;
|
||||
Ok(Self { M })
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,8 @@ use crate::Error;
|
||||
use super::ArithSerializer;
|
||||
use super::{r1cs::R1CS, Arith};
|
||||
|
||||
pub mod circuits;
|
||||
|
||||
/// CCS represents the Customizable Constraint Systems structure defined in
|
||||
/// the [CCS paper](https://eprint.iacr.org/2023/552)
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
301
folding-schemes/src/arith/r1cs/circuits.rs
Normal file
301
folding-schemes/src/arith/r1cs/circuits.rs
Normal file
@@ -0,0 +1,301 @@
|
||||
use crate::{
|
||||
arith::ArithGadget,
|
||||
utils::gadgets::{EquivalenceGadget, MatrixGadget, SparseMatrixVar, VectorGadget},
|
||||
};
|
||||
use ark_ff::PrimeField;
|
||||
use ark_r1cs_std::alloc::{AllocVar, AllocationMode};
|
||||
use ark_relations::r1cs::{Namespace, SynthesisError};
|
||||
use ark_std::{borrow::Borrow, marker::PhantomData, One};
|
||||
|
||||
use super::R1CS;
|
||||
|
||||
/// An in-circuit representation of the `R1CS` struct.
|
||||
///
|
||||
/// `M` is for the modulo operation involved in the satisfiability check when
|
||||
/// the underlying `FVar` is `NonNativeUintVar`.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct R1CSMatricesVar<M, FVar> {
|
||||
_m: PhantomData<M>,
|
||||
pub A: SparseMatrixVar<FVar>,
|
||||
pub B: SparseMatrixVar<FVar>,
|
||||
pub C: SparseMatrixVar<FVar>,
|
||||
}
|
||||
|
||||
impl<F: PrimeField, ConstraintF: PrimeField, FVar: AllocVar<F, ConstraintF>>
|
||||
AllocVar<R1CS<F>, ConstraintF> for R1CSMatricesVar<F, FVar>
|
||||
{
|
||||
fn new_variable<T: Borrow<R1CS<F>>>(
|
||||
cs: impl Into<Namespace<ConstraintF>>,
|
||||
f: impl FnOnce() -> Result<T, SynthesisError>,
|
||||
_mode: AllocationMode,
|
||||
) -> Result<Self, SynthesisError> {
|
||||
f().and_then(|val| {
|
||||
let cs = cs.into();
|
||||
|
||||
Ok(Self {
|
||||
_m: PhantomData,
|
||||
A: SparseMatrixVar::<FVar>::new_constant(cs.clone(), &val.borrow().A)?,
|
||||
B: SparseMatrixVar::<FVar>::new_constant(cs.clone(), &val.borrow().B)?,
|
||||
C: SparseMatrixVar::<FVar>::new_constant(cs.clone(), &val.borrow().C)?,
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<M, FVar> R1CSMatricesVar<M, FVar>
|
||||
where
|
||||
SparseMatrixVar<FVar>: MatrixGadget<FVar>,
|
||||
[FVar]: VectorGadget<FVar>,
|
||||
{
|
||||
pub fn eval_at_z(&self, z: &[FVar]) -> Result<(Vec<FVar>, Vec<FVar>), SynthesisError> {
|
||||
// Multiply Cz by z[0] (u) here, allowing this method to be reused for
|
||||
// both relaxed and unrelaxed R1CS.
|
||||
let Az = self.A.mul_vector(z)?;
|
||||
let Bz = self.B.mul_vector(z)?;
|
||||
let Cz = self.C.mul_vector(z)?;
|
||||
let uCz = Cz.mul_scalar(&z[0])?;
|
||||
let AzBz = Az.hadamard(&Bz)?;
|
||||
Ok((AzBz, uCz))
|
||||
}
|
||||
}
|
||||
|
||||
impl<M, FVar, WVar: AsRef<[FVar]>, UVar: AsRef<[FVar]>> ArithGadget<WVar, UVar>
|
||||
for R1CSMatricesVar<M, FVar>
|
||||
where
|
||||
SparseMatrixVar<FVar>: MatrixGadget<FVar>,
|
||||
[FVar]: VectorGadget<FVar> + EquivalenceGadget<M>,
|
||||
FVar: Clone + One,
|
||||
{
|
||||
/// Evaluation is a tuple of two vectors (`AzBz` and `uCz`) instead of a
|
||||
/// single vector `AzBz - uCz`, because subtraction is not supported for
|
||||
/// `FVar = NonNativeUintVar`.
|
||||
type Evaluation = (Vec<FVar>, Vec<FVar>);
|
||||
|
||||
fn eval_relation(&self, w: &WVar, u: &UVar) -> Result<Self::Evaluation, SynthesisError> {
|
||||
self.eval_at_z(&[&[FVar::one()], u.as_ref(), w.as_ref()].concat())
|
||||
}
|
||||
|
||||
fn enforce_evaluation(
|
||||
_w: &WVar,
|
||||
_u: &UVar,
|
||||
(lhs, rhs): Self::Evaluation,
|
||||
) -> Result<(), SynthesisError> {
|
||||
lhs.enforce_equivalent(&rhs)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use std::cmp::max;
|
||||
|
||||
use ark_crypto_primitives::crh::{
|
||||
sha256::{
|
||||
constraints::{Sha256Gadget, UnitVar},
|
||||
Sha256,
|
||||
},
|
||||
CRHScheme, CRHSchemeGadget,
|
||||
};
|
||||
use ark_ec::CurveGroup;
|
||||
use ark_ff::BigInteger;
|
||||
use ark_pallas::{Fq, Fr, Projective};
|
||||
use ark_r1cs_std::{bits::uint8::UInt8, eq::EqGadget, fields::fp::FpVar};
|
||||
use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystem, ConstraintSystemRef};
|
||||
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},
|
||||
},
|
||||
Arith,
|
||||
};
|
||||
use crate::commitment::{pedersen::Pedersen, CommitmentScheme};
|
||||
use crate::folding::{
|
||||
circuits::{
|
||||
cyclefold::{CycleFoldCommittedInstanceVar, CycleFoldWitnessVar},
|
||||
nonnative::uint::NonNativeUintVar,
|
||||
},
|
||||
nova::{
|
||||
circuits::CommittedInstanceVar, decider_eth_circuit::WitnessVar, CommittedInstance,
|
||||
Witness,
|
||||
},
|
||||
};
|
||||
use crate::frontend::{
|
||||
utils::{CubicFCircuit, CustomFCircuit, WrapperCircuit},
|
||||
FCircuit,
|
||||
};
|
||||
|
||||
fn prepare_instances<C: CurveGroup, CS: CommitmentScheme<C>, R: Rng>(
|
||||
mut rng: R,
|
||||
r1cs: &R1CS<C::ScalarField>,
|
||||
z: &[C::ScalarField],
|
||||
) -> (Witness<C>, CommittedInstance<C>) {
|
||||
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_at_z(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 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 wVar = WitnessVar::new_witness(cs.clone(), || Ok(w)).unwrap();
|
||||
let uVar = CommittedInstanceVar::new_witness(cs.clone(), || Ok(u)).unwrap();
|
||||
let r1csVar =
|
||||
R1CSMatricesVar::<Fr, FpVar<Fr>>::new_witness(cs.clone(), || Ok(r1cs)).unwrap();
|
||||
|
||||
r1csVar.enforce_relation(&wVar, &uVar).unwrap();
|
||||
assert!(cs.is_satisfied().unwrap());
|
||||
}
|
||||
|
||||
// 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();
|
||||
cs.finalize();
|
||||
assert!(cs.is_satisfied().unwrap());
|
||||
|
||||
let cs = cs.into_inner().unwrap();
|
||||
|
||||
let r1cs = extract_r1cs::<Fr>(&cs).unwrap();
|
||||
let (w, x) = extract_w_x::<Fr>(&cs);
|
||||
r1cs.check_relation(&w, &x).unwrap();
|
||||
let mut z = [vec![Fr::one()], x, w].concat();
|
||||
z[0] = Fr::rand(rng);
|
||||
|
||||
let (w, u) = prepare_instances::<_, Pedersen<Projective>, _>(rng, &r1cs, &z);
|
||||
r1cs.check_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 wVar = WitnessVar::new_witness(cs.clone(), || Ok(w)).unwrap();
|
||||
let uVar = CommittedInstanceVar::new_witness(cs.clone(), || Ok(u)).unwrap();
|
||||
let r1csVar =
|
||||
R1CSMatricesVar::<Fr, FpVar<Fr>>::new_witness(cs.clone(), || Ok(r1cs)).unwrap();
|
||||
|
||||
r1csVar.enforce_relation(&wVar, &uVar).unwrap();
|
||||
assert!(cs.is_satisfied().unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_relaxed_r1cs_small_gadget_arkworks() {
|
||||
let z_i = vec![Fr::from(3_u32)];
|
||||
let cubic_circuit = CubicFCircuit::<Fr>::new(()).unwrap();
|
||||
let circuit = WrapperCircuit::<Fr, CubicFCircuit<Fr>> {
|
||||
FC: cubic_circuit,
|
||||
z_i: Some(z_i.clone()),
|
||||
z_i1: Some(cubic_circuit.step_native(0, z_i, vec![]).unwrap()),
|
||||
};
|
||||
|
||||
test_relaxed_r1cs_gadget(circuit);
|
||||
}
|
||||
|
||||
struct Sha256TestCircuit<F: PrimeField> {
|
||||
_f: PhantomData<F>,
|
||||
pub x: Vec<u8>,
|
||||
pub y: Vec<u8>,
|
||||
}
|
||||
impl<F: PrimeField> ConstraintSynthesizer<F> for Sha256TestCircuit<F> {
|
||||
fn generate_constraints(self, cs: ConstraintSystemRef<F>) -> Result<(), SynthesisError> {
|
||||
let x = Vec::<UInt8<F>>::new_witness(cs.clone(), || Ok(self.x))?;
|
||||
let y = Vec::<UInt8<F>>::new_input(cs.clone(), || Ok(self.y))?;
|
||||
|
||||
let unitVar = UnitVar::default();
|
||||
let comp_y = <Sha256Gadget<F> as CRHSchemeGadget<Sha256, F>>::evaluate(&unitVar, &x)?;
|
||||
comp_y.0.enforce_equal(&y)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
fn test_relaxed_r1cs_medium_gadget_arkworks() {
|
||||
let x = Fr::from(5_u32).into_bigint().to_bytes_le();
|
||||
let y = <Sha256 as CRHScheme>::evaluate(&(), x.clone()).unwrap();
|
||||
|
||||
let circuit = Sha256TestCircuit::<Fr> {
|
||||
_f: PhantomData,
|
||||
x,
|
||||
y,
|
||||
};
|
||||
test_relaxed_r1cs_gadget(circuit);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_relaxed_r1cs_custom_circuit() {
|
||||
let n_constraints = 10_000;
|
||||
let custom_circuit = CustomFCircuit::<Fr>::new(n_constraints).unwrap();
|
||||
let z_i = vec![Fr::from(5_u32)];
|
||||
let circuit = WrapperCircuit::<Fr, CustomFCircuit<Fr>> {
|
||||
FC: custom_circuit,
|
||||
z_i: Some(z_i.clone()),
|
||||
z_i1: Some(custom_circuit.step_native(0, z_i, vec![]).unwrap()),
|
||||
};
|
||||
test_relaxed_r1cs_gadget(circuit);
|
||||
}
|
||||
|
||||
#[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
|
||||
// custom circuit.
|
||||
let custom_circuit = CustomFCircuit::<Fq>::new(10).unwrap();
|
||||
let z_i = vec![Fq::from(5_u32)];
|
||||
let circuit = WrapperCircuit::<Fq, CustomFCircuit<Fq>> {
|
||||
FC: custom_circuit,
|
||||
z_i: Some(z_i.clone()),
|
||||
z_i1: Some(custom_circuit.step_native(0, z_i, vec![]).unwrap()),
|
||||
};
|
||||
circuit.generate_constraints(cs.clone()).unwrap();
|
||||
cs.finalize();
|
||||
let cs = cs.into_inner().unwrap();
|
||||
let r1cs = extract_r1cs::<Fq>(&cs).unwrap();
|
||||
let (w, x) = extract_w_x::<Fq>(&cs);
|
||||
let z = [vec![Fq::rand(rng)], x, w].concat();
|
||||
|
||||
let (w, u) = prepare_instances::<_, Pedersen<Projective2>, _>(rng, &r1cs, &z);
|
||||
|
||||
// natively
|
||||
let cs = ConstraintSystem::<Fq>::new_ref();
|
||||
let wVar = WitnessVar::new_witness(cs.clone(), || Ok(&w)).unwrap();
|
||||
let uVar = CommittedInstanceVar::new_witness(cs.clone(), || Ok(&u)).unwrap();
|
||||
let r1csVar =
|
||||
R1CSMatricesVar::<Fq, FpVar<Fq>>::new_witness(cs.clone(), || Ok(&r1cs)).unwrap();
|
||||
r1csVar.enforce_relation(&wVar, &uVar).unwrap();
|
||||
|
||||
// non-natively
|
||||
let cs = ConstraintSystem::<Fr>::new_ref();
|
||||
let wVar = CycleFoldWitnessVar::new_witness(cs.clone(), || Ok(w)).unwrap();
|
||||
let uVar =
|
||||
CycleFoldCommittedInstanceVar::<_, GVar2>::new_witness(cs.clone(), || Ok(u)).unwrap();
|
||||
let r1csVar =
|
||||
R1CSMatricesVar::<Fq, NonNativeUintVar<Fr>>::new_witness(cs.clone(), || Ok(r1cs))
|
||||
.unwrap();
|
||||
r1csVar.enforce_relation(&wVar, &uVar).unwrap();
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,8 @@ use crate::utils::vec::{
|
||||
};
|
||||
use crate::Error;
|
||||
|
||||
pub mod circuits;
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)]
|
||||
pub struct R1CS<F: PrimeField> {
|
||||
pub l: usize, // io len
|
||||
@@ -121,8 +123,14 @@ impl<F: PrimeField> From<CCS<F>> for R1CS<F> {
|
||||
|
||||
/// extracts arkworks ConstraintSystem matrices into crate::utils::vec::SparseMatrix format as R1CS
|
||||
/// struct.
|
||||
pub fn extract_r1cs<F: PrimeField>(cs: &ConstraintSystem<F>) -> R1CS<F> {
|
||||
let m = cs.to_matrices().unwrap();
|
||||
pub fn extract_r1cs<F: PrimeField>(cs: &ConstraintSystem<F>) -> Result<R1CS<F>, Error> {
|
||||
let m = cs.to_matrices().ok_or_else(|| {
|
||||
Error::ConversionError(
|
||||
"ConstraintSystem".into(),
|
||||
"ConstraintMatrices".into(),
|
||||
"The matrices have not been generated yet".into(),
|
||||
)
|
||||
})?;
|
||||
|
||||
let n_rows = cs.num_constraints;
|
||||
let n_cols = cs.num_instance_variables + cs.num_witness_variables; // cs.num_instance_variables already counts the 1
|
||||
@@ -143,12 +151,12 @@ pub fn extract_r1cs<F: PrimeField>(cs: &ConstraintSystem<F>) -> R1CS<F> {
|
||||
coeffs: m.c,
|
||||
};
|
||||
|
||||
R1CS::<F> {
|
||||
Ok(R1CS::<F> {
|
||||
l: cs.num_instance_variables - 1, // -1 to subtract the first '1'
|
||||
A,
|
||||
B,
|
||||
C,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// extracts the witness and the public inputs from arkworks ConstraintSystem.
|
||||
@@ -13,7 +13,6 @@ use ark_r1cs_std::{
|
||||
alloc::{AllocVar, AllocationMode},
|
||||
boolean::Boolean,
|
||||
fields::{nonnative::NonNativeFieldVar, FieldVar},
|
||||
groups::GroupOpsBounds,
|
||||
prelude::CurveVar,
|
||||
ToBitsGadget,
|
||||
};
|
||||
@@ -492,9 +491,6 @@ impl<C, GC, const H: bool> IPAGadget<C, GC, H>
|
||||
where
|
||||
C: CurveGroup,
|
||||
GC: CurveVar<C, CF<C>>,
|
||||
|
||||
<C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField,
|
||||
for<'a> &'a GC: GroupOpsBounds<'a, C, GC>,
|
||||
{
|
||||
/// Verify the IPA opening proof, K=log2(d), where d is the degree of the committed polynomial,
|
||||
/// and H indicates if the commitment is in hiding mode and thus uses blinding factors, if not,
|
||||
@@ -515,7 +511,7 @@ where
|
||||
return Err(SynthesisError::Unsatisfiable);
|
||||
}
|
||||
|
||||
let P_ = P + U.scalar_mul_le(v.to_bits_le()?.iter())?;
|
||||
let P_ = U.scalar_mul_le(v.to_bits_le()?.iter())? + P;
|
||||
let mut q_0 = P_;
|
||||
let mut r = r.clone();
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use ark_ec::CurveGroup;
|
||||
use ark_ff::Field;
|
||||
use ark_r1cs_std::{boolean::Boolean, groups::GroupOpsBounds, prelude::CurveVar};
|
||||
use ark_r1cs_std::{boolean::Boolean, prelude::CurveVar};
|
||||
use ark_relations::r1cs::SynthesisError;
|
||||
use ark_serialize::{CanonicalDeserialize, CanonicalSerialize};
|
||||
use ark_std::Zero;
|
||||
@@ -194,15 +194,12 @@ impl<C, GC, const H: bool> PedersenGadget<C, GC, H>
|
||||
where
|
||||
C: CurveGroup,
|
||||
GC: CurveVar<C, CF<C>>,
|
||||
|
||||
<C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField,
|
||||
for<'a> &'a GC: GroupOpsBounds<'a, C, GC>,
|
||||
{
|
||||
pub fn commit(
|
||||
h: GC,
|
||||
g: Vec<GC>,
|
||||
v: Vec<Vec<Boolean<CF<C>>>>,
|
||||
r: Vec<Boolean<CF<C>>>,
|
||||
h: &GC,
|
||||
g: &[GC],
|
||||
v: &[Vec<Boolean<CF<C>>>],
|
||||
r: &[Boolean<CF<C>>],
|
||||
) -> Result<GC, SynthesisError> {
|
||||
let mut res = GC::zero();
|
||||
if H {
|
||||
@@ -303,7 +300,7 @@ mod tests {
|
||||
|
||||
// use the gadget
|
||||
let cmVar =
|
||||
PedersenGadget::<Projective, GVar, hiding>::commit(hVar, gVar, vVar, rVar).unwrap();
|
||||
PedersenGadget::<Projective, GVar, hiding>::commit(&hVar, &gVar, &vVar, &rVar).unwrap();
|
||||
cmVar.enforce_equal(&expected_cmVar).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ use ark_r1cs_std::{
|
||||
boolean::Boolean,
|
||||
eq::EqGadget,
|
||||
fields::fp::FpVar,
|
||||
groups::GroupOpsBounds,
|
||||
prelude::CurveVar,
|
||||
ToConstraintFieldGadget,
|
||||
};
|
||||
@@ -17,16 +16,23 @@ use ark_relations::r1cs::{
|
||||
};
|
||||
use ark_std::fmt::Debug;
|
||||
use ark_std::rand::RngCore;
|
||||
use ark_std::Zero;
|
||||
use ark_std::{One, Zero};
|
||||
use core::{borrow::Borrow, marker::PhantomData};
|
||||
|
||||
use super::{nonnative::uint::NonNativeUintVar, CF1, CF2};
|
||||
use crate::arith::r1cs::{extract_w_x, R1CS};
|
||||
use crate::commitment::CommitmentScheme;
|
||||
use crate::constants::NOVA_N_BITS_RO;
|
||||
use crate::folding::nova::nifs::{nova::NIFS, NIFSTrait};
|
||||
use crate::transcript::{AbsorbNonNative, AbsorbNonNativeGadget, Transcript, TranscriptVar};
|
||||
use crate::utils::gadgets::{EquivalenceGadget, VectorGadget};
|
||||
use crate::Error;
|
||||
use crate::{
|
||||
arith::{
|
||||
r1cs::{circuits::R1CSMatricesVar, extract_w_x, R1CS},
|
||||
ArithGadget,
|
||||
},
|
||||
folding::traits::Inputize,
|
||||
};
|
||||
use ark_crypto_primitives::sponge::poseidon::PoseidonSponge;
|
||||
|
||||
/// Re-export the Nova committed instance as `CycleFoldCommittedInstance` and
|
||||
@@ -35,13 +41,39 @@ pub use crate::folding::nova::{
|
||||
CommittedInstance as CycleFoldCommittedInstance, Witness as CycleFoldWitness,
|
||||
};
|
||||
|
||||
impl<C: CurveGroup, GC: CurveVar<C, CF2<C>>> Inputize<CF2<C>, CycleFoldCommittedInstanceVar<C, GC>>
|
||||
for CycleFoldCommittedInstance<C>
|
||||
{
|
||||
fn inputize(&self) -> Vec<CF2<C>> {
|
||||
let zero = (&C::BaseField::zero(), &C::BaseField::zero());
|
||||
let cmE = self.cmE.into_affine();
|
||||
let cmW = self.cmW.into_affine();
|
||||
let (cmE_x, cmE_y) = cmE.xy().unwrap_or(zero);
|
||||
let (cmW_x, cmW_y) = cmW.xy().unwrap_or(zero);
|
||||
self.u
|
||||
.inputize()
|
||||
.into_iter()
|
||||
.chain(self.x.iter().flat_map(|x| x.inputize()))
|
||||
.chain(
|
||||
[
|
||||
*cmE_x,
|
||||
*cmE_y,
|
||||
C::BaseField::one(),
|
||||
*cmW_x,
|
||||
*cmW_y,
|
||||
C::BaseField::one(),
|
||||
]
|
||||
.into_iter()
|
||||
.flat_map(|x| x.to_base_prime_field_elements()),
|
||||
)
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
/// CycleFoldCommittedInstanceVar is the CycleFold CommittedInstance represented
|
||||
/// in folding verifier circuit
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CycleFoldCommittedInstanceVar<C: CurveGroup, GC: CurveVar<C, CF2<C>>>
|
||||
where
|
||||
for<'a> &'a GC: GroupOpsBounds<'a, C, GC>,
|
||||
{
|
||||
pub struct CycleFoldCommittedInstanceVar<C: CurveGroup, GC: CurveVar<C, CF2<C>>> {
|
||||
pub cmE: GC,
|
||||
pub u: NonNativeUintVar<CF2<C>>,
|
||||
pub cmW: GC,
|
||||
@@ -51,8 +83,6 @@ impl<C, GC> AllocVar<CycleFoldCommittedInstance<C>, CF2<C>> for CycleFoldCommitt
|
||||
where
|
||||
C: CurveGroup,
|
||||
GC: CurveVar<C, CF2<C>>,
|
||||
<C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField,
|
||||
for<'a> &'a GC: GroupOpsBounds<'a, C, GC>,
|
||||
{
|
||||
fn new_variable<T: Borrow<CycleFoldCommittedInstance<C>>>(
|
||||
cs: impl Into<Namespace<CF2<C>>>,
|
||||
@@ -101,8 +131,7 @@ impl<C, GC> AbsorbNonNativeGadget<C::BaseField> for CycleFoldCommittedInstanceVa
|
||||
where
|
||||
C: CurveGroup,
|
||||
GC: CurveVar<C, CF2<C>> + ToConstraintFieldGadget<CF2<C>>,
|
||||
<C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField + Absorb,
|
||||
for<'a> &'a GC: GroupOpsBounds<'a, C, GC>,
|
||||
C::BaseField: PrimeField + Absorb,
|
||||
{
|
||||
/// Extracts the underlying field elements from `CycleFoldCommittedInstanceVar`, in the order
|
||||
/// of `u`, `x`, `cmE.x`, `cmE.y`, `cmW.x`, `cmW.y`, `cmE.is_inf || cmW.is_inf` (|| is for
|
||||
@@ -132,7 +161,7 @@ where
|
||||
|
||||
impl<C: CurveGroup> CycleFoldCommittedInstance<C>
|
||||
where
|
||||
<C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField + Absorb,
|
||||
C::BaseField: PrimeField + Absorb,
|
||||
{
|
||||
/// hash_cyclefold implements the committed instance hash compatible with the
|
||||
/// in-circuit implementation `CycleFoldCommittedInstanceVar::hash`.
|
||||
@@ -153,8 +182,7 @@ impl<C, GC> CycleFoldCommittedInstanceVar<C, GC>
|
||||
where
|
||||
C: CurveGroup,
|
||||
GC: CurveVar<C, CF2<C>> + ToConstraintFieldGadget<CF2<C>>,
|
||||
<C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField + Absorb,
|
||||
for<'a> &'a GC: GroupOpsBounds<'a, C, GC>,
|
||||
C::BaseField: PrimeField + Absorb,
|
||||
{
|
||||
/// hash implements the committed instance hash compatible with the native
|
||||
/// implementation `CycleFoldCommittedInstance::hash_cyclefold`.
|
||||
@@ -165,7 +193,7 @@ where
|
||||
/// (reconstraining) them.
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub fn hash<S: CryptographicSponge, T: TranscriptVar<CF2<C>, S>>(
|
||||
self,
|
||||
&self,
|
||||
sponge: &T,
|
||||
pp_hash: FpVar<CF2<C>>, // public params hash
|
||||
) -> Result<(FpVar<CF2<C>>, Vec<FpVar<CF2<C>>>), SynthesisError> {
|
||||
@@ -173,7 +201,11 @@ where
|
||||
let U_vec = self.to_native_sponge_field_elements()?;
|
||||
sponge.absorb(&pp_hash)?;
|
||||
sponge.absorb(&U_vec)?;
|
||||
Ok((sponge.squeeze_field_elements(1)?.pop().unwrap(), U_vec))
|
||||
Ok((
|
||||
// `unwrap` is safe because the sponge is guaranteed to return a single element
|
||||
sponge.squeeze_field_elements(1)?.pop().unwrap(),
|
||||
U_vec,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,10 +214,7 @@ where
|
||||
/// represented as native points, which are folded on the auxiliary curve constraints field (E2::Fr
|
||||
/// = E1::Fq).
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CommittedInstanceInCycleFoldVar<C: CurveGroup, GC: CurveVar<C, CF2<C>>>
|
||||
where
|
||||
for<'a> &'a GC: GroupOpsBounds<'a, C, GC>,
|
||||
{
|
||||
pub struct CommittedInstanceInCycleFoldVar<C: CurveGroup, GC: CurveVar<C, CF2<C>>> {
|
||||
_c: PhantomData<C>,
|
||||
pub cmE: GC,
|
||||
pub cmW: GC,
|
||||
@@ -196,7 +225,6 @@ impl<C, GC> AllocVar<CycleFoldCommittedInstance<C>, CF2<C>>
|
||||
where
|
||||
C: CurveGroup,
|
||||
GC: CurveVar<C, CF2<C>>,
|
||||
for<'a> &'a GC: GroupOpsBounds<'a, C, GC>,
|
||||
{
|
||||
fn new_variable<T: Borrow<CycleFoldCommittedInstance<C>>>(
|
||||
cs: impl Into<Namespace<CF2<C>>>,
|
||||
@@ -232,7 +260,7 @@ pub struct CycleFoldWitnessVar<C: CurveGroup> {
|
||||
impl<C> AllocVar<CycleFoldWitness<C>, CF2<C>> for CycleFoldWitnessVar<C>
|
||||
where
|
||||
C: CurveGroup,
|
||||
<C as ark_ec::CurveGroup>::BaseField: PrimeField,
|
||||
C::BaseField: PrimeField,
|
||||
{
|
||||
fn new_variable<T: Borrow<CycleFoldWitness<C>>>(
|
||||
cs: impl Into<Namespace<CF2<C>>>,
|
||||
@@ -265,8 +293,7 @@ impl<C: CurveGroup, GC: CurveVar<C, CF2<C>>> NIFSFullGadget<C, GC>
|
||||
where
|
||||
C: CurveGroup,
|
||||
GC: CurveVar<C, CF2<C>>,
|
||||
<C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField,
|
||||
for<'a> &'a GC: GroupOpsBounds<'a, C, GC>,
|
||||
C::BaseField: PrimeField,
|
||||
{
|
||||
pub fn fold_committed_instance(
|
||||
r_bits: Vec<Boolean<CF2<C>>>,
|
||||
@@ -284,13 +311,13 @@ where
|
||||
Ok(CycleFoldCommittedInstanceVar {
|
||||
cmE: cmT.scalar_mul_le(r_bits.iter())? + ci1.cmE,
|
||||
cmW: ci1.cmW + ci2.cmW.scalar_mul_le(r_bits.iter())?,
|
||||
u: ci1.u.add_no_align(&r_nonnat).modulo::<CF1<C>>()?,
|
||||
u: ci1.u.add_no_align(&r_nonnat)?.modulo::<CF1<C>>()?,
|
||||
x: ci1
|
||||
.x
|
||||
.iter()
|
||||
.zip(ci2.x)
|
||||
.map(|(a, b)| {
|
||||
a.add_no_align(&r_nonnat.mul_no_align(&b)?)
|
||||
a.add_no_align(&r_nonnat.mul_no_align(&b)?)?
|
||||
.modulo::<CF1<C>>()
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?,
|
||||
@@ -319,6 +346,29 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: CurveGroup, GC: CurveVar<C, CF2<C>>>
|
||||
ArithGadget<CycleFoldWitnessVar<C>, CycleFoldCommittedInstanceVar<C, GC>>
|
||||
for R1CSMatricesVar<CF1<C>, NonNativeUintVar<CF2<C>>>
|
||||
{
|
||||
type Evaluation = (Vec<NonNativeUintVar<CF2<C>>>, Vec<NonNativeUintVar<CF2<C>>>);
|
||||
|
||||
fn eval_relation(
|
||||
&self,
|
||||
w: &CycleFoldWitnessVar<C>,
|
||||
u: &CycleFoldCommittedInstanceVar<C, GC>,
|
||||
) -> Result<Self::Evaluation, SynthesisError> {
|
||||
self.eval_at_z(&[&[u.u.clone()][..], &u.x, &w.W].concat())
|
||||
}
|
||||
|
||||
fn enforce_evaluation(
|
||||
w: &CycleFoldWitnessVar<C>,
|
||||
_u: &CycleFoldCommittedInstanceVar<C, GC>,
|
||||
(AzBz, uCz): Self::Evaluation,
|
||||
) -> Result<(), SynthesisError> {
|
||||
EquivalenceGadget::<CF1<C>>::enforce_equivalent(&AzBz[..], &uCz.add(&w.E)?[..])
|
||||
}
|
||||
}
|
||||
|
||||
/// CycleFoldChallengeGadget computes the RO challenge used for the CycleFold instances NIFS, it contains a
|
||||
/// rust-native and a in-circuit compatible versions.
|
||||
pub struct CycleFoldChallengeGadget<C: CurveGroup, GC: CurveVar<C, CF2<C>>> {
|
||||
@@ -329,9 +379,7 @@ impl<C, GC> CycleFoldChallengeGadget<C, GC>
|
||||
where
|
||||
C: CurveGroup,
|
||||
GC: CurveVar<C, CF2<C>> + ToConstraintFieldGadget<CF2<C>>,
|
||||
<C as CurveGroup>::BaseField: PrimeField,
|
||||
<C as CurveGroup>::BaseField: Absorb,
|
||||
for<'a> &'a GC: GroupOpsBounds<'a, C, GC>,
|
||||
C::BaseField: PrimeField + Absorb,
|
||||
{
|
||||
pub fn get_challenge_native<T: Transcript<C::BaseField>>(
|
||||
transcript: &mut T,
|
||||
@@ -425,7 +473,6 @@ impl<CFG: CycleFoldConfig, GC: CurveVar<CFG::C, CFG::F>> ConstraintSynthesizer<C
|
||||
where
|
||||
GC: ToConstraintFieldGadget<CFG::F>,
|
||||
CFG::F: PrimeField,
|
||||
for<'a> &'a GC: GroupOpsBounds<'a, CFG::C, GC>,
|
||||
{
|
||||
fn generate_constraints(self, cs: ConstraintSystemRef<CFG::F>) -> Result<(), SynthesisError> {
|
||||
let r_bits = Vec::<Boolean<CFG::F>>::new_witness(cs.clone(), || {
|
||||
@@ -510,7 +557,6 @@ pub struct CycleFoldNIFS<
|
||||
> where
|
||||
<C1 as CurveGroup>::BaseField: PrimeField,
|
||||
<C2 as CurveGroup>::BaseField: PrimeField,
|
||||
for<'a> &'a GC2: GroupOpsBounds<'a, C2, GC2>,
|
||||
{
|
||||
_c1: PhantomData<C1>,
|
||||
_c2: PhantomData<C2>,
|
||||
@@ -526,7 +572,6 @@ where
|
||||
<C2 as Group>::ScalarField: Absorb,
|
||||
C1: CurveGroup<BaseField = C2::ScalarField, ScalarField = C2::BaseField>,
|
||||
GC2: CurveVar<C2, CF2<C2>> + ToConstraintFieldGadget<CF2<C2>>,
|
||||
for<'a> &'a GC2: GroupOpsBounds<'a, C2, GC2>,
|
||||
{
|
||||
fn prove(
|
||||
cf_r_Fq: C2::ScalarField, // C2::Fr==C1::Fq
|
||||
@@ -597,8 +642,6 @@ where
|
||||
<C1 as Group>::ScalarField: Absorb,
|
||||
<C2 as Group>::ScalarField: Absorb,
|
||||
C1: CurveGroup<BaseField = C2::ScalarField, ScalarField = C2::BaseField>,
|
||||
for<'a> &'a GC1: GroupOpsBounds<'a, C1, GC1>,
|
||||
for<'a> &'a GC2: GroupOpsBounds<'a, C2, GC2>,
|
||||
{
|
||||
let cs2 = ConstraintSystem::<C1::BaseField>::new_ref();
|
||||
cf_circuit.generate_constraints(cs2.clone())?;
|
||||
|
||||
211
folding-schemes/src/folding/circuits/decider/mod.rs
Normal file
211
folding-schemes/src/folding/circuits/decider/mod.rs
Normal file
@@ -0,0 +1,211 @@
|
||||
use ark_crypto_primitives::sponge::{
|
||||
poseidon::constraints::PoseidonSpongeVar, CryptographicSponge,
|
||||
};
|
||||
use ark_ec::CurveGroup;
|
||||
use ark_ff::PrimeField;
|
||||
use ark_poly::Polynomial;
|
||||
use ark_r1cs_std::{
|
||||
fields::{fp::FpVar, FieldVar},
|
||||
poly::{domain::Radix2DomainVar, evaluations::univariate::EvaluationsVar},
|
||||
ToConstraintFieldGadget,
|
||||
};
|
||||
use ark_relations::r1cs::SynthesisError;
|
||||
use ark_std::log2;
|
||||
|
||||
use crate::folding::traits::{CommittedInstanceOps, CommittedInstanceVarOps, Dummy, WitnessOps};
|
||||
use crate::transcript::{Transcript, TranscriptVar};
|
||||
use crate::utils::vec::poly_from_vec;
|
||||
use crate::Error;
|
||||
use crate::{arith::Arith, folding::circuits::CF1};
|
||||
|
||||
pub mod off_chain;
|
||||
pub mod on_chain;
|
||||
|
||||
/// Gadget that computes the KZG challenges.
|
||||
/// It also offers the rust native implementation compatible with the gadget.
|
||||
pub struct KZGChallengesGadget {}
|
||||
|
||||
impl KZGChallengesGadget {
|
||||
pub fn get_challenges_native<
|
||||
C: CurveGroup,
|
||||
T: Transcript<CF1<C>>,
|
||||
U: CommittedInstanceOps<C>,
|
||||
>(
|
||||
transcript: &mut T,
|
||||
U_i: &U,
|
||||
) -> Vec<CF1<C>> {
|
||||
let mut challenges = vec![];
|
||||
for cm in U_i.get_commitments() {
|
||||
transcript.absorb_nonnative(&cm);
|
||||
challenges.push(transcript.get_challenge());
|
||||
}
|
||||
challenges
|
||||
}
|
||||
|
||||
pub fn get_challenges_gadget<
|
||||
C: CurveGroup,
|
||||
S: CryptographicSponge,
|
||||
T: TranscriptVar<CF1<C>, S>,
|
||||
U: CommittedInstanceVarOps<C>,
|
||||
>(
|
||||
transcript: &mut T,
|
||||
U_i: &U,
|
||||
) -> Result<Vec<FpVar<CF1<C>>>, SynthesisError> {
|
||||
let mut challenges = vec![];
|
||||
for cm in U_i.get_commitments() {
|
||||
transcript.absorb(&cm.to_constraint_field()?)?;
|
||||
challenges.push(transcript.get_challenge()?);
|
||||
}
|
||||
Ok(challenges)
|
||||
}
|
||||
}
|
||||
|
||||
/// Gadget that interpolates the polynomial from the given vector and returns
|
||||
/// its evaluation at the given point.
|
||||
/// It also offers the rust native implementation compatible with the gadget.
|
||||
pub struct EvalGadget {}
|
||||
|
||||
impl EvalGadget {
|
||||
pub fn evaluate_native<F: PrimeField>(v: &[F], point: F) -> Result<F, Error> {
|
||||
let mut v = v.to_vec();
|
||||
v.resize(v.len().next_power_of_two(), F::zero());
|
||||
|
||||
Ok(poly_from_vec(v)?.evaluate(&point))
|
||||
}
|
||||
|
||||
pub fn evaluate_gadget<F: PrimeField>(
|
||||
v: &[FpVar<F>],
|
||||
point: &FpVar<F>,
|
||||
) -> Result<FpVar<F>, SynthesisError> {
|
||||
let mut v = v.to_vec();
|
||||
v.resize(v.len().next_power_of_two(), FpVar::zero());
|
||||
let n = v.len() as u64;
|
||||
let gen = F::get_root_of_unity(n).ok_or(SynthesisError::PolynomialDegreeTooLarge)?;
|
||||
// `unwrap` below is safe because `Radix2DomainVar::new` only fails if
|
||||
// `offset.enforce_not_equal(&FpVar::zero())` returns an error.
|
||||
// But in our case, `offset` is `FpVar::one()`, i.e., both operands of
|
||||
// `enforce_not_equal` are constants.
|
||||
// Consequently, `FpVar`'s implementation of `enforce_not_equal` will
|
||||
// always return `Ok(())`.
|
||||
let domain = Radix2DomainVar::new(gen, log2(v.len()) as u64, FpVar::one()).unwrap();
|
||||
|
||||
let evaluations_var = EvaluationsVar::from_vec_and_domain(v, domain, true);
|
||||
evaluations_var.interpolate_and_evaluate(point)
|
||||
}
|
||||
}
|
||||
|
||||
/// This is a temporary workaround for step 6 (running NIFS.V for group elements
|
||||
/// in circuit) in an NIFS-agnostic way, because different folding schemes have
|
||||
/// different interfaces of folding verification now.
|
||||
///
|
||||
/// In the future, we may introduce a better solution that uses a trait for all
|
||||
/// folding schemes that specifies their native and in-circuit behaviors.
|
||||
pub trait DeciderEnabledNIFS<
|
||||
C: CurveGroup,
|
||||
RU: CommittedInstanceOps<C>, // Running instance
|
||||
IU: CommittedInstanceOps<C>, // Incoming instance
|
||||
W: WitnessOps<CF1<C>>,
|
||||
A: Arith<W, RU>,
|
||||
>
|
||||
{
|
||||
type ProofDummyCfg;
|
||||
type Proof: Dummy<Self::ProofDummyCfg>;
|
||||
type RandomnessDummyCfg;
|
||||
type Randomness: Dummy<Self::RandomnessDummyCfg>;
|
||||
|
||||
/// Fold the field elements in `U` and `u` inside the circuit.
|
||||
///
|
||||
/// `U_vec` is `U` expressed as a vector of `FpVar`s, which can be reused
|
||||
/// before or after calling this function to save constraints.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn fold_field_elements_gadget(
|
||||
arith: &A,
|
||||
transcript: &mut PoseidonSpongeVar<CF1<C>>,
|
||||
pp_hash: FpVar<CF1<C>>,
|
||||
U: RU::Var,
|
||||
U_vec: Vec<FpVar<CF1<C>>>,
|
||||
u: IU::Var,
|
||||
proof: Self::Proof,
|
||||
randomness: Self::Randomness,
|
||||
) -> Result<RU::Var, SynthesisError>;
|
||||
|
||||
/// Fold the group elements (i.e., commitments) in `U` and `u` outside the
|
||||
/// circuit.
|
||||
fn fold_group_elements_native(
|
||||
U_commitments: &[C],
|
||||
u_commitments: &[C],
|
||||
proof: Option<Self::Proof>,
|
||||
randomness: Self::Randomness,
|
||||
) -> Result<Vec<C>, Error>;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use ark_crypto_primitives::sponge::{
|
||||
constraints::CryptographicSpongeVar, poseidon::PoseidonSponge,
|
||||
};
|
||||
use ark_pallas::{Fr, Projective};
|
||||
use ark_r1cs_std::{alloc::AllocVar, R1CSVar};
|
||||
use ark_relations::r1cs::ConstraintSystem;
|
||||
use ark_std::UniformRand;
|
||||
|
||||
use super::*;
|
||||
use crate::folding::nova::{circuits::CommittedInstanceVar, CommittedInstance};
|
||||
use crate::transcript::poseidon::poseidon_canonical_config;
|
||||
|
||||
// checks that the gadget and native implementations of the challenge computation match
|
||||
#[test]
|
||||
fn test_kzg_challenge_gadget() {
|
||||
let mut rng = ark_std::test_rng();
|
||||
let poseidon_config = poseidon_canonical_config::<Fr>();
|
||||
let mut transcript = PoseidonSponge::<Fr>::new(&poseidon_config);
|
||||
|
||||
let U_i = CommittedInstance::<Projective> {
|
||||
cmE: Projective::rand(&mut rng),
|
||||
u: Fr::rand(&mut rng),
|
||||
cmW: Projective::rand(&mut rng),
|
||||
x: vec![Fr::rand(&mut rng); 1],
|
||||
};
|
||||
|
||||
// compute the challenge natively
|
||||
let challenges = KZGChallengesGadget::get_challenges_native(&mut transcript, &U_i);
|
||||
|
||||
let cs = ConstraintSystem::<Fr>::new_ref();
|
||||
let U_iVar =
|
||||
CommittedInstanceVar::<Projective>::new_witness(cs.clone(), || Ok(U_i.clone()))
|
||||
.unwrap();
|
||||
let mut transcript_var = PoseidonSpongeVar::<Fr>::new(cs.clone(), &poseidon_config);
|
||||
|
||||
let challenges_var =
|
||||
KZGChallengesGadget::get_challenges_gadget(&mut transcript_var, &U_iVar).unwrap();
|
||||
assert!(cs.is_satisfied().unwrap());
|
||||
|
||||
// check that the natively computed and in-circuit computed hashes match
|
||||
assert_eq!(challenges_var.value().unwrap(), challenges);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_polynomial_interpolation() {
|
||||
let mut rng = ark_std::test_rng();
|
||||
let n = 12;
|
||||
let l = 1 << n;
|
||||
|
||||
let v: Vec<Fr> = std::iter::repeat_with(|| Fr::rand(&mut rng))
|
||||
.take(l)
|
||||
.collect();
|
||||
let challenge = Fr::rand(&mut rng);
|
||||
|
||||
use ark_poly::Polynomial;
|
||||
let polynomial = poly_from_vec(v.to_vec()).unwrap();
|
||||
let eval = polynomial.evaluate(&challenge);
|
||||
|
||||
let cs = ConstraintSystem::<Fr>::new_ref();
|
||||
let vVar = Vec::<FpVar<Fr>>::new_witness(cs.clone(), || Ok(v)).unwrap();
|
||||
let challengeVar = FpVar::<Fr>::new_witness(cs.clone(), || Ok(challenge)).unwrap();
|
||||
|
||||
let evalVar = EvalGadget::evaluate_gadget(&vVar, &challengeVar).unwrap();
|
||||
|
||||
assert_eq!(evalVar.value().unwrap(), eval);
|
||||
assert!(cs.is_satisfied().unwrap());
|
||||
}
|
||||
}
|
||||
316
folding-schemes/src/folding/circuits/decider/off_chain.rs
Normal file
316
folding-schemes/src/folding/circuits/decider/off_chain.rs
Normal file
@@ -0,0 +1,316 @@
|
||||
/// This file implements a generic offchain decider circuit.
|
||||
/// For ethereum use cases, use the `GenericOnchainDeciderCircuit`.
|
||||
/// More details can be found at the documentation page:
|
||||
/// https://privacy-scaling-explorations.github.io/sonobe-docs/design/nova-decider-offchain.html
|
||||
use ark_crypto_primitives::sponge::{
|
||||
constraints::{AbsorbGadget, CryptographicSpongeVar},
|
||||
poseidon::{constraints::PoseidonSpongeVar, PoseidonConfig},
|
||||
Absorb,
|
||||
};
|
||||
use ark_ec::CurveGroup;
|
||||
use ark_r1cs_std::{
|
||||
alloc::AllocVar, eq::EqGadget, fields::fp::FpVar, prelude::CurveVar, ToConstraintFieldGadget,
|
||||
};
|
||||
use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystemRef, SynthesisError};
|
||||
use ark_std::{marker::PhantomData, Zero};
|
||||
|
||||
use crate::{
|
||||
arith::{
|
||||
r1cs::{circuits::R1CSMatricesVar, R1CS},
|
||||
Arith, ArithGadget,
|
||||
},
|
||||
folding::{
|
||||
circuits::{
|
||||
cyclefold::{
|
||||
CycleFoldCommittedInstance, CycleFoldCommittedInstanceVar, CycleFoldWitness,
|
||||
},
|
||||
decider::{EvalGadget, KZGChallengesGadget},
|
||||
nonnative::affine::NonNativeAffineVar,
|
||||
CF1, CF2,
|
||||
},
|
||||
nova::{circuits::CommittedInstanceVar, decider_eth_circuit::WitnessVar},
|
||||
traits::{CommittedInstanceOps, CommittedInstanceVarOps, Dummy, WitnessOps, WitnessVarOps},
|
||||
},
|
||||
};
|
||||
|
||||
use super::DeciderEnabledNIFS;
|
||||
|
||||
/// Circuit that implements part of the in-circuit checks needed for the offchain verification over
|
||||
/// the Curve2's BaseField (=Curve1's ScalarField).
|
||||
pub struct GenericOffchainDeciderCircuit1<
|
||||
C1: CurveGroup,
|
||||
C2: CurveGroup,
|
||||
GC2: CurveVar<C2, CF2<C2>> + ToConstraintFieldGadget<CF2<C2>>,
|
||||
RU: CommittedInstanceOps<C1>, // Running instance
|
||||
IU: CommittedInstanceOps<C1>, // Incoming instance
|
||||
W: WitnessOps<CF1<C1>>, // Witness
|
||||
A: Arith<W, RU>, // Constraint system
|
||||
AVar: ArithGadget<W::Var, RU::Var>, // In-circuit representation of `A`
|
||||
D: DeciderEnabledNIFS<C1, RU, IU, W, A>,
|
||||
> {
|
||||
pub _gc2: PhantomData<GC2>,
|
||||
pub _avar: PhantomData<AVar>,
|
||||
/// Constraint system of the Augmented Function circuit
|
||||
pub arith: A,
|
||||
pub poseidon_config: PoseidonConfig<CF1<C1>>,
|
||||
/// public params hash
|
||||
pub pp_hash: CF1<C1>,
|
||||
pub i: CF1<C1>,
|
||||
/// initial state
|
||||
pub z_0: Vec<CF1<C1>>,
|
||||
/// current i-th state
|
||||
pub z_i: Vec<CF1<C1>>,
|
||||
/// Folding scheme instances
|
||||
pub U_i: RU,
|
||||
pub W_i: W,
|
||||
pub u_i: IU,
|
||||
pub w_i: W,
|
||||
pub U_i1: RU,
|
||||
pub W_i1: W,
|
||||
|
||||
/// Helper for folding verification
|
||||
pub proof: D::Proof,
|
||||
pub randomness: D::Randomness,
|
||||
|
||||
/// CycleFold running instance
|
||||
pub cf_U_i: CycleFoldCommittedInstance<C2>,
|
||||
|
||||
/// KZG challenges
|
||||
pub kzg_challenges: Vec<CF1<C1>>,
|
||||
pub kzg_evaluations: Vec<CF1<C1>>,
|
||||
}
|
||||
|
||||
impl<
|
||||
C1: CurveGroup,
|
||||
C2: CurveGroup<ScalarField = CF2<C1>, BaseField = CF1<C1>>,
|
||||
GC2: CurveVar<C2, CF2<C2>> + ToConstraintFieldGadget<CF2<C2>>,
|
||||
RU: CommittedInstanceOps<C1> + for<'a> Dummy<&'a A>,
|
||||
IU: CommittedInstanceOps<C1> + for<'a> Dummy<&'a A>,
|
||||
W: WitnessOps<CF1<C1>> + for<'a> Dummy<&'a A>,
|
||||
A: Arith<W, RU>,
|
||||
AVar: ArithGadget<W::Var, RU::Var> + AllocVar<A, CF1<C1>>,
|
||||
D: DeciderEnabledNIFS<C1, RU, IU, W, A>,
|
||||
>
|
||||
Dummy<(
|
||||
A,
|
||||
&R1CS<CF1<C2>>,
|
||||
PoseidonConfig<CF1<C1>>,
|
||||
D::ProofDummyCfg,
|
||||
D::RandomnessDummyCfg,
|
||||
usize,
|
||||
usize,
|
||||
)> for GenericOffchainDeciderCircuit1<C1, C2, GC2, RU, IU, W, A, AVar, D>
|
||||
{
|
||||
fn dummy(
|
||||
(
|
||||
arith,
|
||||
cf_arith,
|
||||
poseidon_config,
|
||||
proof_config,
|
||||
randomness_config,
|
||||
state_len,
|
||||
num_commitments,
|
||||
): (
|
||||
A,
|
||||
&R1CS<CF1<C2>>,
|
||||
PoseidonConfig<CF1<C1>>,
|
||||
D::ProofDummyCfg,
|
||||
D::RandomnessDummyCfg,
|
||||
usize,
|
||||
usize,
|
||||
),
|
||||
) -> Self {
|
||||
Self {
|
||||
_gc2: PhantomData,
|
||||
_avar: PhantomData,
|
||||
poseidon_config,
|
||||
pp_hash: Zero::zero(),
|
||||
i: Zero::zero(),
|
||||
z_0: vec![Zero::zero(); state_len],
|
||||
z_i: vec![Zero::zero(); state_len],
|
||||
U_i: RU::dummy(&arith),
|
||||
W_i: W::dummy(&arith),
|
||||
u_i: IU::dummy(&arith),
|
||||
w_i: W::dummy(&arith),
|
||||
U_i1: RU::dummy(&arith),
|
||||
W_i1: W::dummy(&arith),
|
||||
proof: D::Proof::dummy(proof_config),
|
||||
randomness: D::Randomness::dummy(randomness_config),
|
||||
cf_U_i: CycleFoldCommittedInstance::dummy(cf_arith),
|
||||
kzg_challenges: vec![Zero::zero(); num_commitments],
|
||||
kzg_evaluations: vec![Zero::zero(); num_commitments],
|
||||
arith,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<
|
||||
C1: CurveGroup,
|
||||
C2: CurveGroup<ScalarField = CF2<C1>, BaseField = CF1<C1>>,
|
||||
GC2: CurveVar<C2, CF2<C2>> + ToConstraintFieldGadget<CF2<C2>>,
|
||||
RU: CommittedInstanceOps<C1>,
|
||||
IU: CommittedInstanceOps<C1>,
|
||||
W: WitnessOps<CF1<C1>>,
|
||||
A: Arith<W, RU>,
|
||||
AVar: ArithGadget<W::Var, RU::Var> + AllocVar<A, CF1<C1>>,
|
||||
D: DeciderEnabledNIFS<C1, RU, IU, W, A>,
|
||||
> ConstraintSynthesizer<CF1<C1>>
|
||||
for GenericOffchainDeciderCircuit1<C1, C2, GC2, RU, IU, W, A, AVar, D>
|
||||
where
|
||||
RU::Var: AbsorbGadget<CF1<C1>> + CommittedInstanceVarOps<C1, PointVar = NonNativeAffineVar<C1>>,
|
||||
CF1<C1>: Absorb,
|
||||
{
|
||||
fn generate_constraints(self, cs: ConstraintSystemRef<CF1<C1>>) -> Result<(), SynthesisError> {
|
||||
let arith = AVar::new_witness(cs.clone(), || Ok(&self.arith))?;
|
||||
|
||||
let pp_hash = FpVar::new_input(cs.clone(), || Ok(self.pp_hash))?;
|
||||
let i = FpVar::new_input(cs.clone(), || Ok(self.i))?;
|
||||
let z_0 = Vec::new_input(cs.clone(), || Ok(self.z_0))?;
|
||||
let z_i = Vec::new_input(cs.clone(), || Ok(self.z_i))?;
|
||||
|
||||
let u_i = IU::Var::new_witness(cs.clone(), || Ok(self.u_i))?;
|
||||
let U_i = RU::Var::new_witness(cs.clone(), || Ok(self.U_i))?;
|
||||
// here (U_i1, W_i1) = NIFS.P( (U_i,W_i), (u_i,w_i))
|
||||
let U_i1_commitments = Vec::<NonNativeAffineVar<C1>>::new_input(cs.clone(), || {
|
||||
Ok(self.U_i1.get_commitments())
|
||||
})?;
|
||||
let U_i1 = RU::Var::new_witness(cs.clone(), || Ok(self.U_i1))?;
|
||||
let W_i1 = W::Var::new_witness(cs.clone(), || Ok(self.W_i1))?;
|
||||
U_i1.get_commitments().enforce_equal(&U_i1_commitments)?;
|
||||
|
||||
let cf_U_i =
|
||||
CycleFoldCommittedInstanceVar::<C2, GC2>::new_input(cs.clone(), || Ok(self.cf_U_i))?;
|
||||
|
||||
// allocate the inputs for the checks 7.1 and 7.2
|
||||
let kzg_challenges = Vec::new_input(cs.clone(), || Ok(self.kzg_challenges))?;
|
||||
let kzg_evaluations = Vec::new_input(cs.clone(), || Ok(self.kzg_evaluations))?;
|
||||
|
||||
// `sponge` is for digest computation.
|
||||
let sponge = PoseidonSpongeVar::new(cs.clone(), &self.poseidon_config);
|
||||
// `transcript` is for challenge generation.
|
||||
let mut transcript = sponge.clone();
|
||||
// notice that the `pp_hash` is absorbed inside the ChallengeGadget::get_challenge_gadget call
|
||||
|
||||
// 1. enforce `U_{i+1}` and `W_{i+1}` satisfy `arith`
|
||||
arith.enforce_relation(&W_i1, &U_i1)?;
|
||||
|
||||
// 2. enforce `u_i` is an incoming instance
|
||||
u_i.enforce_incoming()?;
|
||||
|
||||
// 3. u_i.x[0] == H(i, z_0, z_i, U_i), u_i.x[1] == H(cf_U_i)
|
||||
let (u_i_x, U_i_vec) = U_i.hash(&sponge, &pp_hash, &i, &z_0, &z_i)?;
|
||||
let (cf_u_i_x, _) = cf_U_i.hash(&sponge, pp_hash.clone())?;
|
||||
u_i.get_public_inputs().enforce_equal(&[u_i_x, cf_u_i_x])?;
|
||||
|
||||
// 6.1. partially enforce `NIFS.V(U_i, u_i) = U_{i+1}`.
|
||||
D::fold_field_elements_gadget(
|
||||
&self.arith,
|
||||
&mut transcript,
|
||||
pp_hash,
|
||||
U_i,
|
||||
U_i_vec,
|
||||
u_i,
|
||||
self.proof,
|
||||
self.randomness,
|
||||
)?
|
||||
.enforce_partial_equal(&U_i1)?;
|
||||
|
||||
// 7.1. compute and check KZG challenges
|
||||
KZGChallengesGadget::get_challenges_gadget(&mut transcript, &U_i1)?
|
||||
.enforce_equal(&kzg_challenges)?;
|
||||
|
||||
// 7.2. check the claimed evaluations
|
||||
for (((v, _r), c), e) in W_i1
|
||||
.get_openings()
|
||||
.iter()
|
||||
.zip(&kzg_challenges)
|
||||
.zip(&kzg_evaluations)
|
||||
{
|
||||
// The randomness `_r` is currently not used.
|
||||
EvalGadget::evaluate_gadget(v, c)?.enforce_equal(e)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Circuit that implements part of the in-circuit checks needed for the offchain verification over
|
||||
/// the Curve1's BaseField (=Curve2's ScalarField).
|
||||
pub struct GenericOffchainDeciderCircuit2<C2: CurveGroup> {
|
||||
/// R1CS of the CycleFold circuit
|
||||
pub cf_arith: R1CS<CF1<C2>>,
|
||||
pub poseidon_config: PoseidonConfig<CF1<C2>>,
|
||||
/// public params hash
|
||||
pub pp_hash: CF1<C2>,
|
||||
|
||||
/// CycleFold running instance
|
||||
pub cf_U_i: CycleFoldCommittedInstance<C2>,
|
||||
pub cf_W_i: CycleFoldWitness<C2>,
|
||||
|
||||
/// KZG challenges
|
||||
pub kzg_challenges: Vec<CF1<C2>>,
|
||||
pub kzg_evaluations: Vec<CF1<C2>>,
|
||||
}
|
||||
|
||||
impl<C2: CurveGroup> Dummy<(R1CS<CF1<C2>>, PoseidonConfig<CF1<C2>>, usize)>
|
||||
for GenericOffchainDeciderCircuit2<C2>
|
||||
{
|
||||
fn dummy(
|
||||
(cf_arith, poseidon_config, num_commitments): (
|
||||
R1CS<CF1<C2>>,
|
||||
PoseidonConfig<CF1<C2>>,
|
||||
usize,
|
||||
),
|
||||
) -> Self {
|
||||
Self {
|
||||
poseidon_config,
|
||||
pp_hash: Zero::zero(),
|
||||
cf_U_i: CycleFoldCommittedInstance::dummy(&cf_arith),
|
||||
cf_W_i: CycleFoldWitness::dummy(&cf_arith),
|
||||
kzg_challenges: vec![Zero::zero(); num_commitments],
|
||||
kzg_evaluations: vec![Zero::zero(); num_commitments],
|
||||
cf_arith,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C2: CurveGroup> ConstraintSynthesizer<CF1<C2>> for GenericOffchainDeciderCircuit2<C2> {
|
||||
fn generate_constraints(self, cs: ConstraintSystemRef<CF1<C2>>) -> Result<(), SynthesisError> {
|
||||
let cf_r1cs = R1CSMatricesVar::<CF1<C2>, FpVar<CF1<C2>>>::new_witness(cs.clone(), || {
|
||||
Ok(self.cf_arith.clone())
|
||||
})?;
|
||||
|
||||
let pp_hash = FpVar::new_input(cs.clone(), || Ok(self.pp_hash))?;
|
||||
|
||||
let cf_U_i = CommittedInstanceVar::new_input(cs.clone(), || Ok(self.cf_U_i))?;
|
||||
let cf_W_i = WitnessVar::new_witness(cs.clone(), || Ok(self.cf_W_i))?;
|
||||
|
||||
// allocate the inputs for the checks 4.1 and 4.2
|
||||
let kzg_challenges = Vec::new_input(cs.clone(), || Ok(self.kzg_challenges))?;
|
||||
let kzg_evaluations = Vec::new_input(cs.clone(), || Ok(self.kzg_evaluations))?;
|
||||
|
||||
// `transcript` is for challenge generation.
|
||||
let mut transcript = PoseidonSpongeVar::new(cs.clone(), &self.poseidon_config);
|
||||
transcript.absorb(&pp_hash)?;
|
||||
|
||||
// 5. enforce `cf_U_i` and `cf_W_i` satisfy `cf_r1cs`
|
||||
cf_r1cs.enforce_relation(&cf_W_i, &cf_U_i)?;
|
||||
|
||||
// 4.1. compute and check KZG challenges
|
||||
KZGChallengesGadget::get_challenges_gadget(&mut transcript, &cf_U_i)?
|
||||
.enforce_equal(&kzg_challenges)?;
|
||||
|
||||
// 4.2. check the claimed evaluations
|
||||
for (((v, _r), c), e) in cf_W_i
|
||||
.get_openings()
|
||||
.iter()
|
||||
.zip(&kzg_challenges)
|
||||
.zip(&kzg_evaluations)
|
||||
{
|
||||
// The randomness `_r` is currently not used.
|
||||
EvalGadget::evaluate_gadget(v, c)?.enforce_equal(e)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
325
folding-schemes/src/folding/circuits/decider/on_chain.rs
Normal file
325
folding-schemes/src/folding/circuits/decider/on_chain.rs
Normal file
@@ -0,0 +1,325 @@
|
||||
/// This file implements the onchain (Ethereum's EVM) decider circuit. For non-ethereum use cases,
|
||||
/// other more efficient approaches can be used.
|
||||
use ark_crypto_primitives::sponge::{
|
||||
constraints::{AbsorbGadget, CryptographicSpongeVar},
|
||||
poseidon::{constraints::PoseidonSpongeVar, PoseidonConfig},
|
||||
Absorb,
|
||||
};
|
||||
use ark_ec::CurveGroup;
|
||||
use ark_r1cs_std::{
|
||||
alloc::AllocVar, eq::EqGadget, fields::fp::FpVar, prelude::CurveVar, ToConstraintFieldGadget,
|
||||
};
|
||||
use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystemRef, SynthesisError};
|
||||
use ark_std::{marker::PhantomData, Zero};
|
||||
|
||||
use crate::{
|
||||
arith::{r1cs::R1CS, Arith, ArithGadget},
|
||||
commitment::pedersen::Params as PedersenParams,
|
||||
folding::{
|
||||
circuits::{
|
||||
cyclefold::{
|
||||
CycleFoldCommittedInstance, CycleFoldCommittedInstanceVar, CycleFoldWitness,
|
||||
},
|
||||
decider::{EvalGadget, KZGChallengesGadget},
|
||||
nonnative::affine::NonNativeAffineVar,
|
||||
CF1, CF2,
|
||||
},
|
||||
traits::{CommittedInstanceOps, CommittedInstanceVarOps, Dummy, WitnessOps, WitnessVarOps},
|
||||
},
|
||||
};
|
||||
|
||||
use super::DeciderEnabledNIFS;
|
||||
|
||||
/// A generic circuit tailored for the onchain (Ethereum's EVM) verification of
|
||||
/// IVC proofs, where we support IVC built upon any folding scheme.
|
||||
///
|
||||
/// Specifically, `GenericDeciderEthCircuit` implements the in-circuit version
|
||||
/// of the IVC verification algorithm, which essentially checks the following:
|
||||
/// - `R_arith(W_i, U_i)`:
|
||||
/// The running instance `U_i` and witness `W_i` satisfy `arith`,
|
||||
/// and the commitments in `U_i` open to the values in `W_i`.
|
||||
/// - `R_arith(w_i, u_i)`:
|
||||
/// The incoming instance `u_i` and witness `w_i` satisfy `arith`,
|
||||
/// and the commitments in `u_i` open to the values in `w_i`.
|
||||
/// - `R_cf_arith(cf_W_i, cf_U_i)`:
|
||||
/// The CycleFold instance `cf_U_i` and witness `cf_W_i` satisfy `cf_arith`,
|
||||
/// and the commitments in `cf_U_i` open to the values in `cf_W_i`.
|
||||
/// - `u_i` contains the correct hash of the initial and final states.
|
||||
///
|
||||
/// To reduce the number of relation checks, the prover, before invoking the
|
||||
/// circuit, further folds `U_i, u_i` into `U_{i+1}`, and `W_i, w_i` into
|
||||
/// `W_{i+1}`.
|
||||
/// Now, the circuit only needs to perform two relation checks, i.e.,
|
||||
/// `R_arith(W_{i+1}, U_{i+1})` and `R_cf_arith(cf_W_i, cf_U_i)`, plus a few
|
||||
/// constraints for enforcing the correct hash in `u_i` and the correct folding
|
||||
/// from `U_i, u_i` to `U_{i+1}`.
|
||||
///
|
||||
/// We further reduce the circuit size by avoiding the non-native commitment
|
||||
/// checks involved in `R_arith(W_{i+1}, U_{i+1})`.
|
||||
/// Now, we now only check the satisfiability of the constraint system `arith`
|
||||
/// with the witness `W_{i+1}` and instance `U_{i+1}` in the circuit, but the
|
||||
/// actual commitment checks are done with the help of KZG.
|
||||
///
|
||||
/// For more details, see [https://privacy-scaling-explorations.github.io/sonobe-docs/design/nova-decider-onchain.html].
|
||||
pub struct GenericOnchainDeciderCircuit<
|
||||
C1: CurveGroup,
|
||||
C2: CurveGroup,
|
||||
GC2: CurveVar<C2, CF2<C2>> + ToConstraintFieldGadget<CF2<C2>>,
|
||||
RU: CommittedInstanceOps<C1>, // Running instance
|
||||
IU: CommittedInstanceOps<C1>, // Incoming instance
|
||||
W: WitnessOps<CF1<C1>>, // Witness
|
||||
A: Arith<W, RU>, // Constraint system
|
||||
AVar: ArithGadget<W::Var, RU::Var>, // In-circuit representation of `A`
|
||||
D: DeciderEnabledNIFS<C1, RU, IU, W, A>,
|
||||
> {
|
||||
pub _gc2: PhantomData<GC2>,
|
||||
pub _avar: PhantomData<AVar>,
|
||||
/// Constraint system of the Augmented Function circuit
|
||||
pub arith: A,
|
||||
/// R1CS of the CycleFold circuit
|
||||
pub cf_arith: R1CS<CF1<C2>>,
|
||||
/// CycleFold PedersenParams over C2
|
||||
pub cf_pedersen_params: PedersenParams<C2>,
|
||||
pub poseidon_config: PoseidonConfig<CF1<C1>>,
|
||||
/// public params hash
|
||||
pub pp_hash: CF1<C1>,
|
||||
pub i: CF1<C1>,
|
||||
/// initial state
|
||||
pub z_0: Vec<CF1<C1>>,
|
||||
/// current i-th state
|
||||
pub z_i: Vec<CF1<C1>>,
|
||||
/// Folding scheme instances
|
||||
pub U_i: RU,
|
||||
pub W_i: W,
|
||||
pub u_i: IU,
|
||||
pub w_i: W,
|
||||
pub U_i1: RU,
|
||||
pub W_i1: W,
|
||||
|
||||
/// Helper for folding verification
|
||||
pub proof: D::Proof,
|
||||
pub randomness: D::Randomness,
|
||||
|
||||
/// CycleFold running instance
|
||||
pub cf_U_i: CycleFoldCommittedInstance<C2>,
|
||||
pub cf_W_i: CycleFoldWitness<C2>,
|
||||
|
||||
/// KZG challenges
|
||||
pub kzg_challenges: Vec<CF1<C1>>,
|
||||
pub kzg_evaluations: Vec<CF1<C1>>,
|
||||
}
|
||||
|
||||
impl<
|
||||
C1: CurveGroup,
|
||||
C2: CurveGroup<ScalarField = CF2<C1>, BaseField = CF1<C1>>,
|
||||
GC2: CurveVar<C2, CF2<C2>> + ToConstraintFieldGadget<CF2<C2>>,
|
||||
RU: CommittedInstanceOps<C1> + for<'a> Dummy<&'a A>,
|
||||
IU: CommittedInstanceOps<C1> + for<'a> Dummy<&'a A>,
|
||||
W: WitnessOps<CF1<C1>> + for<'a> Dummy<&'a A>,
|
||||
A: Arith<W, RU>,
|
||||
AVar: ArithGadget<W::Var, RU::Var> + AllocVar<A, CF1<C1>>,
|
||||
D: DeciderEnabledNIFS<C1, RU, IU, W, A>,
|
||||
>
|
||||
Dummy<(
|
||||
A,
|
||||
R1CS<CF1<C2>>,
|
||||
PedersenParams<C2>,
|
||||
PoseidonConfig<CF1<C1>>,
|
||||
D::ProofDummyCfg,
|
||||
D::RandomnessDummyCfg,
|
||||
usize,
|
||||
usize,
|
||||
)> for GenericOnchainDeciderCircuit<C1, C2, GC2, RU, IU, W, A, AVar, D>
|
||||
{
|
||||
fn dummy(
|
||||
(
|
||||
arith,
|
||||
cf_arith,
|
||||
cf_pedersen_params,
|
||||
poseidon_config,
|
||||
proof_config,
|
||||
randomness_config,
|
||||
state_len,
|
||||
num_commitments,
|
||||
): (
|
||||
A,
|
||||
R1CS<CF1<C2>>,
|
||||
PedersenParams<C2>,
|
||||
PoseidonConfig<CF1<C1>>,
|
||||
D::ProofDummyCfg,
|
||||
D::RandomnessDummyCfg,
|
||||
usize,
|
||||
usize,
|
||||
),
|
||||
) -> Self {
|
||||
Self {
|
||||
_gc2: PhantomData,
|
||||
_avar: PhantomData,
|
||||
cf_pedersen_params,
|
||||
poseidon_config,
|
||||
pp_hash: Zero::zero(),
|
||||
i: Zero::zero(),
|
||||
z_0: vec![Zero::zero(); state_len],
|
||||
z_i: vec![Zero::zero(); state_len],
|
||||
U_i: RU::dummy(&arith),
|
||||
W_i: W::dummy(&arith),
|
||||
u_i: IU::dummy(&arith),
|
||||
w_i: W::dummy(&arith),
|
||||
U_i1: RU::dummy(&arith),
|
||||
W_i1: W::dummy(&arith),
|
||||
proof: D::Proof::dummy(proof_config),
|
||||
randomness: D::Randomness::dummy(randomness_config),
|
||||
cf_U_i: CycleFoldCommittedInstance::dummy(&cf_arith),
|
||||
cf_W_i: CycleFoldWitness::dummy(&cf_arith),
|
||||
kzg_challenges: vec![Zero::zero(); num_commitments],
|
||||
kzg_evaluations: vec![Zero::zero(); num_commitments],
|
||||
arith,
|
||||
cf_arith,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<
|
||||
C1: CurveGroup,
|
||||
C2: CurveGroup<ScalarField = CF2<C1>, BaseField = CF1<C1>>,
|
||||
GC2: CurveVar<C2, CF2<C2>> + ToConstraintFieldGadget<CF2<C2>>,
|
||||
RU: CommittedInstanceOps<C1>,
|
||||
IU: CommittedInstanceOps<C1>,
|
||||
W: WitnessOps<CF1<C1>>,
|
||||
A: Arith<W, RU>,
|
||||
AVar: ArithGadget<W::Var, RU::Var> + AllocVar<A, CF1<C1>>,
|
||||
D: DeciderEnabledNIFS<C1, RU, IU, W, A>,
|
||||
> ConstraintSynthesizer<CF1<C1>>
|
||||
for GenericOnchainDeciderCircuit<C1, C2, GC2, RU, IU, W, A, AVar, D>
|
||||
where
|
||||
RU::Var: AbsorbGadget<CF1<C1>> + CommittedInstanceVarOps<C1, PointVar = NonNativeAffineVar<C1>>,
|
||||
CF1<C1>: Absorb,
|
||||
{
|
||||
fn generate_constraints(self, cs: ConstraintSystemRef<CF1<C1>>) -> Result<(), SynthesisError> {
|
||||
let arith = AVar::new_witness(cs.clone(), || Ok(&self.arith))?;
|
||||
|
||||
let pp_hash = FpVar::new_input(cs.clone(), || Ok(self.pp_hash))?;
|
||||
let i = FpVar::new_input(cs.clone(), || Ok(self.i))?;
|
||||
let z_0 = Vec::new_input(cs.clone(), || Ok(self.z_0))?;
|
||||
let z_i = Vec::new_input(cs.clone(), || Ok(self.z_i))?;
|
||||
|
||||
let u_i = IU::Var::new_witness(cs.clone(), || Ok(self.u_i))?;
|
||||
let U_i = RU::Var::new_witness(cs.clone(), || Ok(self.U_i))?;
|
||||
// here (U_i1, W_i1) = NIFS.P( (U_i,W_i), (u_i,w_i))
|
||||
let U_i1_commitments = Vec::<NonNativeAffineVar<C1>>::new_input(cs.clone(), || {
|
||||
Ok(self.U_i1.get_commitments())
|
||||
})?;
|
||||
let U_i1 = RU::Var::new_witness(cs.clone(), || Ok(self.U_i1))?;
|
||||
let W_i1 = W::Var::new_witness(cs.clone(), || Ok(self.W_i1))?;
|
||||
U_i1.get_commitments().enforce_equal(&U_i1_commitments)?;
|
||||
|
||||
let cf_U_i =
|
||||
CycleFoldCommittedInstanceVar::<C2, GC2>::new_witness(cs.clone(), || Ok(self.cf_U_i))?;
|
||||
|
||||
// allocate the inputs for the check 7.1 and 7.2
|
||||
let kzg_challenges = Vec::new_input(cs.clone(), || Ok(self.kzg_challenges))?;
|
||||
let kzg_evaluations = Vec::new_input(cs.clone(), || Ok(self.kzg_evaluations))?;
|
||||
|
||||
// `sponge` is for digest computation.
|
||||
let sponge = PoseidonSpongeVar::new(cs.clone(), &self.poseidon_config);
|
||||
// `transcript` is for challenge generation.
|
||||
let mut transcript = sponge.clone();
|
||||
|
||||
// NOTE: we use the same enumeration as in
|
||||
// https://privacy-scaling-explorations.github.io/sonobe-docs/design/nova-decider-onchain.html
|
||||
// in order to make it easier to reason about.
|
||||
|
||||
// 1. enforce `U_{i+1}` and `W_{i+1}` satisfy `arith`
|
||||
arith.enforce_relation(&W_i1, &U_i1)?;
|
||||
|
||||
// 2. enforce `u_i` is an incoming instance
|
||||
u_i.enforce_incoming()?;
|
||||
|
||||
// 3. u_i.x[0] == H(i, z_0, z_i, U_i), u_i.x[1] == H(cf_U_i)
|
||||
let (u_i_x, U_i_vec) = U_i.hash(&sponge, &pp_hash, &i, &z_0, &z_i)?;
|
||||
let (cf_u_i_x, _) = cf_U_i.hash(&sponge, pp_hash.clone())?;
|
||||
u_i.get_public_inputs().enforce_equal(&[u_i_x, cf_u_i_x])?;
|
||||
|
||||
#[cfg(feature = "light-test")]
|
||||
log::warn!("[WARNING]: Running with the 'light-test' feature, skipping the big part of the DeciderEthCircuit.\n Only for testing purposes.");
|
||||
|
||||
// The following two checks (and their respective allocations) are disabled for normal
|
||||
// tests since they take several millions of constraints and would take several minutes
|
||||
// (and RAM) to run the test. It is active by default, and not active only when
|
||||
// 'light-test' feature is used.
|
||||
#[cfg(not(feature = "light-test"))]
|
||||
{
|
||||
// imports here instead of at the top of the file, so we avoid having multiple
|
||||
// `#[cfg(not(test))]`
|
||||
use crate::{
|
||||
arith::r1cs::circuits::R1CSMatricesVar,
|
||||
commitment::pedersen::PedersenGadget,
|
||||
folding::circuits::{
|
||||
cyclefold::CycleFoldWitnessVar, nonnative::uint::NonNativeUintVar,
|
||||
},
|
||||
};
|
||||
use ark_r1cs_std::ToBitsGadget;
|
||||
let cf_W_i = CycleFoldWitnessVar::<C2>::new_witness(cs.clone(), || Ok(self.cf_W_i))?;
|
||||
// 4. check Pedersen commitments of cf_U_i.{cmE, cmW}
|
||||
let H = GC2::constant(self.cf_pedersen_params.h);
|
||||
let G = self
|
||||
.cf_pedersen_params
|
||||
.generators
|
||||
.iter()
|
||||
.map(|&g| GC2::constant(g.into()))
|
||||
.collect::<Vec<_>>();
|
||||
let cf_W_i_E_bits = cf_W_i
|
||||
.E
|
||||
.iter()
|
||||
.map(|E_i| E_i.to_bits_le())
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
let cf_W_i_W_bits = cf_W_i
|
||||
.W
|
||||
.iter()
|
||||
.map(|W_i| W_i.to_bits_le())
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
PedersenGadget::<C2, GC2>::commit(&H, &G, &cf_W_i_E_bits, &cf_W_i.rE.to_bits_le()?)?
|
||||
.enforce_equal(&cf_U_i.cmE)?;
|
||||
PedersenGadget::<C2, GC2>::commit(&H, &G, &cf_W_i_W_bits, &cf_W_i.rW.to_bits_le()?)?
|
||||
.enforce_equal(&cf_U_i.cmW)?;
|
||||
|
||||
let cf_r1cs = R1CSMatricesVar::<CF1<C2>, NonNativeUintVar<CF2<C2>>>::new_constant(
|
||||
ConstraintSystemRef::None,
|
||||
self.cf_arith,
|
||||
)?;
|
||||
|
||||
// 5. enforce `cf_U_i` and `cf_W_i` satisfy `cf_r1cs`
|
||||
cf_r1cs.enforce_relation(&cf_W_i, &cf_U_i)?;
|
||||
}
|
||||
|
||||
// 6.1. partially enforce `NIFS.V(U_i, u_i) = U_{i+1}`.
|
||||
D::fold_field_elements_gadget(
|
||||
&self.arith,
|
||||
&mut transcript,
|
||||
pp_hash,
|
||||
U_i,
|
||||
U_i_vec,
|
||||
u_i,
|
||||
self.proof,
|
||||
self.randomness,
|
||||
)?
|
||||
.enforce_partial_equal(&U_i1)?;
|
||||
|
||||
// 7.1. compute and check KZG challenges
|
||||
KZGChallengesGadget::get_challenges_gadget(&mut transcript, &U_i1)?
|
||||
.enforce_equal(&kzg_challenges)?;
|
||||
|
||||
// 7.2. check the claimed evaluations
|
||||
for (((v, _r), c), e) in W_i1
|
||||
.get_openings()
|
||||
.iter()
|
||||
.zip(&kzg_challenges)
|
||||
.zip(&kzg_evaluations)
|
||||
{
|
||||
// The randomness `_r` is currently not used.
|
||||
EvalGadget::evaluate_gadget(v, c)?.enforce_equal(e)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
/// Circuits and gadgets shared across the different folding schemes.
|
||||
use ark_ec::{AffineRepr, CurveGroup};
|
||||
use ark_ec::{CurveGroup, Group};
|
||||
use ark_ff::Field;
|
||||
|
||||
pub mod cyclefold;
|
||||
pub mod decider;
|
||||
pub mod nonnative;
|
||||
pub mod sum_check;
|
||||
pub mod utils;
|
||||
@@ -10,7 +11,7 @@ pub mod utils;
|
||||
/// CF1 uses the ScalarField of the given C. CF1 represents the ConstraintField used for the main
|
||||
/// folding circuit which is over E1::Fr, where E1 is the main curve where we do the folding.
|
||||
/// In CF1, the points of C can not be natively represented.
|
||||
pub type CF1<C> = <<C as CurveGroup>::Affine as AffineRepr>::ScalarField;
|
||||
pub type CF1<C> = <C as Group>::ScalarField;
|
||||
/// CF2 uses the BaseField of the given C. CF2 represents the ConstraintField used for the
|
||||
/// CycleFold circuit which is over E2::Fr=E1::Fq, where E2 is the auxiliary curve (from
|
||||
/// [CycleFold](https://eprint.iacr.org/2023/1192.pdf) approach) where we check the folding of the
|
||||
|
||||
@@ -2,7 +2,9 @@ use ark_ec::{short_weierstrass::SWFlags, AffineRepr, CurveGroup};
|
||||
use ark_ff::{Field, PrimeField};
|
||||
use ark_r1cs_std::{
|
||||
alloc::{AllocVar, AllocationMode},
|
||||
eq::EqGadget,
|
||||
fields::fp::FpVar,
|
||||
prelude::Boolean,
|
||||
R1CSVar, ToConstraintFieldGadget,
|
||||
};
|
||||
use ark_relations::r1cs::{ConstraintSystemRef, Namespace, SynthesisError};
|
||||
@@ -10,7 +12,10 @@ use ark_serialize::{CanonicalSerialize, CanonicalSerializeWithFlags};
|
||||
use ark_std::Zero;
|
||||
use core::borrow::Borrow;
|
||||
|
||||
use crate::transcript::{AbsorbNonNative, AbsorbNonNativeGadget};
|
||||
use crate::{
|
||||
folding::traits::Inputize,
|
||||
transcript::{AbsorbNonNative, AbsorbNonNativeGadget},
|
||||
};
|
||||
|
||||
use super::uint::{nonnative_field_to_field_elements, NonNativeUintVar};
|
||||
|
||||
@@ -63,8 +68,15 @@ impl<C: CurveGroup> R1CSVar<C::ScalarField> for NonNativeAffineVar<C> {
|
||||
let y = <C::BaseField as Field>::BasePrimeField::from_le_bytes_mod_order(
|
||||
&self.y.value()?.to_bytes_le(),
|
||||
);
|
||||
// Below is a workaround to convert the `x` and `y` coordinates to a
|
||||
// point. This is because the `CurveGroup` trait does not provide a
|
||||
// method to construct a point from `BaseField` elements.
|
||||
let mut bytes = vec![];
|
||||
// `unwrap` below is safe because serialization of a `PrimeField` value
|
||||
// only fails if the serialization flag has more than 8 bits, but here
|
||||
// we call `serialize_uncompressed` which uses an empty flag.
|
||||
x.serialize_uncompressed(&mut bytes).unwrap();
|
||||
// `unwrap` below is also safe, because the bit size of `SWFlags` is 2.
|
||||
y.serialize_with_flags(
|
||||
&mut bytes,
|
||||
if x.is_zero() && y.is_zero() {
|
||||
@@ -76,6 +88,9 @@ impl<C: CurveGroup> R1CSVar<C::ScalarField> for NonNativeAffineVar<C> {
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
// `unwrap` below is safe because `bytes` is constructed from the `x`
|
||||
// and `y` coordinates of a valid point, and these coordinates are
|
||||
// serialized in the same way as the `CurveGroup` implementation.
|
||||
Ok(C::deserialize_uncompressed_unchecked(&bytes[..]).unwrap())
|
||||
}
|
||||
}
|
||||
@@ -90,6 +105,53 @@ impl<C: CurveGroup> ToConstraintFieldGadget<C::ScalarField> for NonNativeAffineV
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: CurveGroup> EqGadget<C::ScalarField> for NonNativeAffineVar<C> {
|
||||
fn is_eq(&self, other: &Self) -> Result<Boolean<C::ScalarField>, SynthesisError> {
|
||||
let mut result = Boolean::TRUE;
|
||||
if self.x.0.len() != other.x.0.len() {
|
||||
return Err(SynthesisError::Unsatisfiable);
|
||||
}
|
||||
if self.y.0.len() != other.y.0.len() {
|
||||
return Err(SynthesisError::Unsatisfiable);
|
||||
}
|
||||
for (l, r) in self
|
||||
.x
|
||||
.0
|
||||
.iter()
|
||||
.chain(&self.y.0)
|
||||
.zip(other.x.0.iter().chain(&other.y.0))
|
||||
{
|
||||
if l.ub != r.ub {
|
||||
return Err(SynthesisError::Unsatisfiable);
|
||||
}
|
||||
result = result.and(&l.v.is_eq(&r.v)?)?;
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
fn enforce_equal(&self, other: &Self) -> Result<(), SynthesisError> {
|
||||
if self.x.0.len() != other.x.0.len() {
|
||||
return Err(SynthesisError::Unsatisfiable);
|
||||
}
|
||||
if self.y.0.len() != other.y.0.len() {
|
||||
return Err(SynthesisError::Unsatisfiable);
|
||||
}
|
||||
for (l, r) in self
|
||||
.x
|
||||
.0
|
||||
.iter()
|
||||
.chain(&self.y.0)
|
||||
.zip(other.x.0.iter().chain(&other.y.0))
|
||||
{
|
||||
if l.ub != r.ub {
|
||||
return Err(SynthesisError::Unsatisfiable);
|
||||
}
|
||||
l.v.enforce_equal(&r.v)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// The out-circuit counterpart of `NonNativeAffineVar::to_constraint_field`
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub(crate) fn nonnative_affine_to_field_elements<C: CurveGroup>(
|
||||
@@ -104,22 +166,22 @@ pub(crate) fn nonnative_affine_to_field_elements<C: CurveGroup>(
|
||||
(x, y)
|
||||
}
|
||||
|
||||
impl<C: CurveGroup> NonNativeAffineVar<C> {
|
||||
// Extracts a list of field elements of type `C::ScalarField` from the public input
|
||||
// `p`, in exactly the same way as how `NonNativeAffineVar` is represented as limbs of type
|
||||
// `FpVar` in-circuit.
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub fn inputize(p: C) -> Result<(Vec<C::ScalarField>, Vec<C::ScalarField>), SynthesisError> {
|
||||
let affine = p.into_affine();
|
||||
impl<C: CurveGroup> Inputize<C::ScalarField, NonNativeAffineVar<C>> for C {
|
||||
fn inputize(&self) -> Vec<C::ScalarField> {
|
||||
let affine = self.into_affine();
|
||||
let zero = (&C::BaseField::zero(), &C::BaseField::zero());
|
||||
let (x, y) = affine.xy().unwrap_or(zero);
|
||||
|
||||
let x = NonNativeUintVar::inputize(*x);
|
||||
let y = NonNativeUintVar::inputize(*y);
|
||||
Ok((x, y))
|
||||
let x = x.inputize();
|
||||
let y = y.inputize();
|
||||
[x, y].concat()
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: CurveGroup> NonNativeAffineVar<C> {
|
||||
pub fn zero() -> Self {
|
||||
// `unwrap` below is safe because we are allocating a constant value,
|
||||
// which is guaranteed to succeed.
|
||||
Self::new_constant(ConstraintSystemRef::None, C::zero()).unwrap()
|
||||
}
|
||||
}
|
||||
@@ -179,9 +241,11 @@ mod tests {
|
||||
let mut rng = ark_std::test_rng();
|
||||
let p = Projective::rand(&mut rng);
|
||||
let pVar = NonNativeAffineVar::<Projective>::new_witness(cs.clone(), || Ok(p)).unwrap();
|
||||
let (x, y) = NonNativeAffineVar::inputize(p).unwrap();
|
||||
let xy = p.inputize();
|
||||
|
||||
assert_eq!(pVar.x.0.value().unwrap(), x);
|
||||
assert_eq!(pVar.y.0.value().unwrap(), y);
|
||||
assert_eq!(
|
||||
[pVar.x.0.value().unwrap(), pVar.y.0.value().unwrap()].concat(),
|
||||
xy
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,8 +17,9 @@ use num_bigint::BigUint;
|
||||
use num_integer::Integer;
|
||||
|
||||
use crate::{
|
||||
folding::traits::Inputize,
|
||||
transcript::{AbsorbNonNative, AbsorbNonNativeGadget},
|
||||
utils::gadgets::{MatrixGadget, SparseMatrixVar, VectorGadget},
|
||||
utils::gadgets::{EquivalenceGadget, MatrixGadget, SparseMatrixVar, VectorGadget},
|
||||
};
|
||||
|
||||
/// `LimbVar` represents a single limb of a non-native unsigned integer in the
|
||||
@@ -36,6 +37,11 @@ pub struct LimbVar<F: PrimeField> {
|
||||
impl<F: PrimeField, B: AsRef<[Boolean<F>]>> From<B> for LimbVar<F> {
|
||||
fn from(bits: B) -> Self {
|
||||
Self {
|
||||
// `Boolean::le_bits_to_fp_var` will return an error if the internal
|
||||
// invocation of `Boolean::enforce_in_field_le` fails.
|
||||
// However, this method is only called when the length of `bits` is
|
||||
// greater than `F::MODULUS_BIT_SIZE`, which should not happen in
|
||||
// our case where `bits` is guaranteed to be short.
|
||||
v: Boolean::le_bits_to_fp_var(bits.as_ref()).unwrap(),
|
||||
ub: (BigUint::one() << bits.as_ref().len()) - BigUint::one(),
|
||||
}
|
||||
@@ -219,7 +225,7 @@ impl<F: PrimeField> AllocVar<BoundedBigUint, F> for NonNativeUintVar<F> {
|
||||
.collect::<Vec<_>>()
|
||||
.chunks(Self::bits_per_limb())
|
||||
{
|
||||
let limb = F::from_bigint(F::BigInt::from_bits_le(chunk)).unwrap();
|
||||
let limb = F::from(F::BigInt::from_bits_le(chunk));
|
||||
let limb = FpVar::new_variable(cs.clone(), || Ok(limb), mode)?;
|
||||
Self::enforce_bit_length(&limb, chunk.len())?;
|
||||
limbs.push(LimbVar {
|
||||
@@ -241,12 +247,15 @@ impl<F: PrimeField, G: Field> AllocVar<G, F> for NonNativeUintVar<F> {
|
||||
let cs = cs.into().cs();
|
||||
let v = f()?;
|
||||
assert_eq!(G::extension_degree(), 1);
|
||||
// `unwrap` is safe because `G` is a field with extension degree 1, and
|
||||
// thus `G::to_base_prime_field_elements` should return an iterator with
|
||||
// exactly one element.
|
||||
let v = v.borrow().to_base_prime_field_elements().next().unwrap();
|
||||
|
||||
let mut limbs = vec![];
|
||||
|
||||
for chunk in v.into_bigint().to_bits_le().chunks(Self::bits_per_limb()) {
|
||||
let limb = F::from_bigint(F::BigInt::from_bits_le(chunk)).unwrap();
|
||||
let limb = F::from(F::BigInt::from_bits_le(chunk));
|
||||
let limb = FpVar::new_variable(cs.clone(), || Ok(limb), mode)?;
|
||||
Self::enforce_bit_length(&limb, chunk.len())?;
|
||||
limbs.push(LimbVar {
|
||||
@@ -259,16 +268,19 @@ impl<F: PrimeField, G: Field> AllocVar<G, F> for NonNativeUintVar<F> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: PrimeField> NonNativeUintVar<F> {
|
||||
pub fn inputize<T: Field>(x: T) -> Vec<F> {
|
||||
impl<F: PrimeField, T: Field> Inputize<F, NonNativeUintVar<F>> for T {
|
||||
fn inputize(&self) -> Vec<F> {
|
||||
assert_eq!(T::extension_degree(), 1);
|
||||
x.to_base_prime_field_elements()
|
||||
// `unwrap` is safe because `T` is a field with extension degree 1, and
|
||||
// thus `T::to_base_prime_field_elements` should return an iterator with
|
||||
// exactly one element.
|
||||
self.to_base_prime_field_elements()
|
||||
.next()
|
||||
.unwrap()
|
||||
.into_bigint()
|
||||
.to_bits_le()
|
||||
.chunks(Self::bits_per_limb())
|
||||
.map(|chunk| F::from_bigint(F::BigInt::from_bits_le(chunk)).unwrap())
|
||||
.chunks(NonNativeUintVar::<F>::bits_per_limb())
|
||||
.map(|chunk| F::from(F::BigInt::from_bits_le(chunk)))
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
@@ -450,7 +462,7 @@ impl<F: PrimeField> NonNativeUintVar<F> {
|
||||
// (i.e., all of them are "non-negative"), implying that all
|
||||
// limbs should be zero to make the sum zero.
|
||||
LimbVar::add_many(&remaining_limbs[1..])
|
||||
.unwrap()
|
||||
.ok_or(SynthesisError::Unsatisfiable)?
|
||||
.v
|
||||
.enforce_equal(&FpVar::zero())?;
|
||||
remaining_limbs[0].v.clone()
|
||||
@@ -621,15 +633,15 @@ impl<F: PrimeField> NonNativeUintVar<F> {
|
||||
}
|
||||
|
||||
/// Compute `self + other`, without aligning the limbs.
|
||||
pub fn add_no_align(&self, other: &Self) -> Self {
|
||||
pub fn add_no_align(&self, other: &Self) -> Result<Self, SynthesisError> {
|
||||
let mut z = vec![LimbVar::zero(); max(self.0.len(), other.0.len())];
|
||||
for (i, v) in self.0.iter().enumerate() {
|
||||
z[i] = z[i].add(v).unwrap();
|
||||
z[i] = z[i].add(v).ok_or(SynthesisError::Unsatisfiable)?;
|
||||
}
|
||||
for (i, v) in other.0.iter().enumerate() {
|
||||
z[i] = z[i].add(v).unwrap();
|
||||
z[i] = z[i].add(v).ok_or(SynthesisError::Unsatisfiable)?;
|
||||
}
|
||||
Self(z)
|
||||
Ok(Self(z))
|
||||
}
|
||||
|
||||
/// Compute `self * other`, without aligning the limbs.
|
||||
@@ -650,7 +662,7 @@ impl<F: PrimeField> NonNativeUintVar<F> {
|
||||
)
|
||||
})
|
||||
.collect::<Option<Vec<_>>>()
|
||||
.unwrap();
|
||||
.ok_or(SynthesisError::Unsatisfiable)?;
|
||||
return Ok(Self(z));
|
||||
}
|
||||
let cs = self.cs().or(other.cs());
|
||||
@@ -753,7 +765,7 @@ impl<F: PrimeField> NonNativeUintVar<F> {
|
||||
let m = Self::new_constant(cs.clone(), BoundedBigUint(m, M::MODULUS_BIT_SIZE as usize))?;
|
||||
// Enforce `self = q * m + r`
|
||||
q.mul_no_align(&m)?
|
||||
.add_no_align(&r)
|
||||
.add_no_align(&r)?
|
||||
.enforce_equal_unaligned(self)?;
|
||||
// Enforce `r < m` (and `r >= 0` already holds)
|
||||
r.enforce_lt(&m)?;
|
||||
@@ -789,8 +801,8 @@ impl<F: PrimeField> NonNativeUintVar<F> {
|
||||
|
||||
let zero = Self::new_constant(cs.clone(), BoundedBigUint(BigUint::zero(), bits))?;
|
||||
let m = Self::new_constant(cs.clone(), BoundedBigUint(m, M::MODULUS_BIT_SIZE as usize))?;
|
||||
let l = self.add_no_align(&is_ge.select(&zero, &q)?.mul_no_align(&m)?);
|
||||
let r = other.add_no_align(&is_ge.select(&q, &zero)?.mul_no_align(&m)?);
|
||||
let l = self.add_no_align(&is_ge.select(&zero, &q)?.mul_no_align(&m)?)?;
|
||||
let r = other.add_no_align(&is_ge.select(&q, &zero)?.mul_no_align(&m)?)?;
|
||||
// If `self >= other`, enforce `self = other + q * m`
|
||||
// Otherwise, enforce `self + q * m = other`
|
||||
// Soundness holds because if `self` and `other` are not congruent, then
|
||||
@@ -799,6 +811,12 @@ impl<F: PrimeField> NonNativeUintVar<F> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: PrimeField, M: PrimeField> EquivalenceGadget<M> for NonNativeUintVar<F> {
|
||||
fn enforce_equivalent(&self, other: &Self) -> Result<(), SynthesisError> {
|
||||
self.enforce_congruent::<M>(other)
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: PrimeField, B: AsRef<[Boolean<F>]>> From<B> for NonNativeUintVar<F> {
|
||||
fn from(bits: B) -> Self {
|
||||
Self(
|
||||
@@ -834,6 +852,9 @@ pub(super) fn nonnative_field_to_field_elements<TargetField: Field, BaseField: P
|
||||
f: &TargetField,
|
||||
) -> Vec<BaseField> {
|
||||
assert_eq!(TargetField::extension_degree(), 1);
|
||||
// `unwrap` is safe because `TargetField` is a field with extension degree
|
||||
// 1, and thus `TargetField::to_base_prime_field_elements` should return an
|
||||
// iterator with exactly one element.
|
||||
let bits = f
|
||||
.to_base_prime_field_elements()
|
||||
.next()
|
||||
@@ -864,11 +885,10 @@ pub(super) fn nonnative_field_to_field_elements<TargetField: Field, BaseField: P
|
||||
|
||||
impl<F: PrimeField> VectorGadget<NonNativeUintVar<F>> for [NonNativeUintVar<F>] {
|
||||
fn add(&self, other: &Self) -> Result<Vec<NonNativeUintVar<F>>, SynthesisError> {
|
||||
Ok(self
|
||||
.iter()
|
||||
self.iter()
|
||||
.zip(other.iter())
|
||||
.map(|(x, y)| x.add_no_align(y))
|
||||
.collect())
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn hadamard(&self, other: &Self) -> Result<Vec<NonNativeUintVar<F>>, SynthesisError> {
|
||||
@@ -886,15 +906,12 @@ impl<F: PrimeField> VectorGadget<NonNativeUintVar<F>> for [NonNativeUintVar<F>]
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: PrimeField, CF: PrimeField> MatrixGadget<NonNativeUintVar<CF>>
|
||||
for SparseMatrixVar<F, CF, NonNativeUintVar<CF>>
|
||||
{
|
||||
impl<CF: PrimeField> MatrixGadget<NonNativeUintVar<CF>> for SparseMatrixVar<NonNativeUintVar<CF>> {
|
||||
fn mul_vector(
|
||||
&self,
|
||||
v: &[NonNativeUintVar<CF>],
|
||||
) -> Result<Vec<NonNativeUintVar<CF>>, SynthesisError> {
|
||||
Ok(self
|
||||
.coeffs
|
||||
self.coeffs
|
||||
.iter()
|
||||
.map(|row| {
|
||||
let len = row
|
||||
@@ -906,7 +923,7 @@ impl<F: PrimeField, CF: PrimeField> MatrixGadget<NonNativeUintVar<CF>>
|
||||
// that results in more flattened `LinearCombination`s.
|
||||
// Consequently, `ConstraintSystem::inline_all_lcs` costs less
|
||||
// time, thus making trusted setup and proof generation faster.
|
||||
let limbs = (0..len)
|
||||
(0..len)
|
||||
.map(|i| {
|
||||
LimbVar::add_many(
|
||||
&row.iter()
|
||||
@@ -919,10 +936,10 @@ impl<F: PrimeField, CF: PrimeField> MatrixGadget<NonNativeUintVar<CF>>
|
||||
)
|
||||
})
|
||||
.collect::<Option<Vec<_>>>()
|
||||
.unwrap();
|
||||
NonNativeUintVar(limbs)
|
||||
.ok_or(SynthesisError::Unsatisfiable)
|
||||
.map(NonNativeUintVar)
|
||||
})
|
||||
.collect())
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1041,7 +1058,7 @@ mod tests {
|
||||
let mut r_var =
|
||||
NonNativeUintVar::new_constant(cs.clone(), BoundedBigUint(BigUint::zero(), 0))?;
|
||||
for (a, b) in a_var.into_iter().zip(b_var.into_iter()) {
|
||||
r_var = r_var.add_no_align(&a.mul_no_align(&b)?);
|
||||
r_var = r_var.add_no_align(&a.mul_no_align(&b)?)?;
|
||||
}
|
||||
r_var.enforce_congruent::<Fq>(&c_var)?;
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ use super::Witness;
|
||||
use crate::arith::{ccs::CCS, Arith};
|
||||
use crate::commitment::CommitmentScheme;
|
||||
use crate::folding::circuits::CF1;
|
||||
use crate::folding::traits::Inputize;
|
||||
use crate::folding::traits::{CommittedInstanceOps, Dummy};
|
||||
use crate::transcript::AbsorbNonNative;
|
||||
use crate::utils::mle::dense_vec_to_dense_mle;
|
||||
@@ -152,6 +153,12 @@ impl<C: CurveGroup> CommittedInstanceOps<C> for CCCS<C> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: CurveGroup> Inputize<C::ScalarField, CCCSVar<C>> for CCCS<C> {
|
||||
fn inputize(&self) -> Vec<C::ScalarField> {
|
||||
[&self.C.inputize()[..], &self.x].concat()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use ark_pallas::Fr;
|
||||
|
||||
@@ -12,7 +12,6 @@ use ark_r1cs_std::{
|
||||
boolean::Boolean,
|
||||
eq::EqGadget,
|
||||
fields::{fp::FpVar, FieldVar},
|
||||
groups::GroupOpsBounds,
|
||||
prelude::CurveVar,
|
||||
uint8::UInt8,
|
||||
R1CSVar, ToConstraintFieldGadget,
|
||||
@@ -195,7 +194,6 @@ pub struct ProofVar<C: CurveGroup> {
|
||||
impl<C> AllocVar<NIMFSProof<C>, CF1<C>> for ProofVar<C>
|
||||
where
|
||||
C: CurveGroup,
|
||||
<C as ark_ec::CurveGroup>::BaseField: PrimeField,
|
||||
<C as Group>::ScalarField: Absorb,
|
||||
{
|
||||
fn new_variable<T: Borrow<NIMFSProof<C>>>(
|
||||
@@ -237,10 +235,7 @@ where
|
||||
pub struct NIMFSGadget<C: CurveGroup> {
|
||||
_c: PhantomData<C>,
|
||||
}
|
||||
impl<C: CurveGroup> NIMFSGadget<C>
|
||||
where
|
||||
<C as CurveGroup>::BaseField: PrimeField,
|
||||
{
|
||||
impl<C: CurveGroup> NIMFSGadget<C> {
|
||||
/// Runs (in-circuit) the NIMFS.V, which outputs the new folded LCCCS instance together with
|
||||
/// the rho_powers, which will be used in other parts of the AugmentedFCircuit
|
||||
#[allow(clippy::type_complexity)]
|
||||
@@ -492,9 +487,7 @@ pub struct AugmentedFCircuit<
|
||||
FC: FCircuit<CF1<C1>>,
|
||||
const MU: usize,
|
||||
const NU: usize,
|
||||
> where
|
||||
for<'a> &'a GC2: GroupOpsBounds<'a, C2, GC2>,
|
||||
{
|
||||
> {
|
||||
pub(super) _c2: PhantomData<C2>,
|
||||
pub(super) _gc2: PhantomData<GC2>,
|
||||
pub(super) poseidon_config: PoseidonConfig<CF1<C1>>,
|
||||
@@ -532,7 +525,6 @@ where
|
||||
<C1 as Group>::ScalarField: Absorb,
|
||||
<C2 as Group>::ScalarField: Absorb,
|
||||
C1: CurveGroup<BaseField = C2::ScalarField, ScalarField = C2::BaseField>,
|
||||
for<'a> &'a GC2: GroupOpsBounds<'a, C2, GC2>,
|
||||
{
|
||||
pub fn default(
|
||||
poseidon_config: &PoseidonConfig<CF1<C1>>,
|
||||
@@ -588,11 +580,9 @@ where
|
||||
M: vec![],
|
||||
};
|
||||
let mut augmented_f_circuit = Self::default(poseidon_config, F, initial_ccs)?;
|
||||
if ccs.is_some() {
|
||||
augmented_f_circuit.ccs = ccs.unwrap();
|
||||
} else {
|
||||
augmented_f_circuit.ccs = augmented_f_circuit.upper_bound_ccs()?;
|
||||
}
|
||||
augmented_f_circuit.ccs = ccs
|
||||
.ok_or(())
|
||||
.or_else(|_| augmented_f_circuit.upper_bound_ccs())?;
|
||||
Ok(augmented_f_circuit)
|
||||
}
|
||||
|
||||
@@ -601,7 +591,7 @@ where
|
||||
/// For a stable FCircuit circuit, the CCS parameters can be computed in advance and can be
|
||||
/// feed in as parameter for the AugmentedFCircuit::empty method to avoid computing them there.
|
||||
pub fn upper_bound_ccs(&self) -> Result<CCS<C1::ScalarField>, Error> {
|
||||
let r1cs = get_r1cs_from_cs::<CF1<C1>>(self.clone()).unwrap();
|
||||
let r1cs = get_r1cs_from_cs::<CF1<C1>>(self.clone())?;
|
||||
let mut ccs = CCS::from(r1cs);
|
||||
|
||||
let z_0 = vec![C1::ScalarField::zero(); self.F.state_len()];
|
||||
@@ -690,7 +680,7 @@ where
|
||||
self.clone().generate_constraints(cs.clone())?;
|
||||
cs.finalize();
|
||||
let cs = cs.into_inner().ok_or(Error::NoInnerConstraintSystem)?;
|
||||
let r1cs = extract_r1cs::<C1::ScalarField>(&cs);
|
||||
let r1cs = extract_r1cs::<C1::ScalarField>(&cs)?;
|
||||
let ccs = CCS::from(r1cs);
|
||||
|
||||
Ok((cs, ccs))
|
||||
@@ -709,7 +699,6 @@ where
|
||||
<C1 as Group>::ScalarField: Absorb,
|
||||
<C2 as Group>::ScalarField: Absorb,
|
||||
C1: CurveGroup<BaseField = C2::ScalarField, ScalarField = C2::BaseField>,
|
||||
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(), || {
|
||||
@@ -883,7 +872,7 @@ where
|
||||
// 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::new_constant(cs.clone(), cf_u_dummy)?
|
||||
CycleFoldCommittedInstanceVar::<C2, GC2>::new_constant(cs.clone(), cf_u_dummy)?
|
||||
.hash(&sponge, pp_hash)?;
|
||||
let cf_x = FpVar::new_input(cs.clone(), || {
|
||||
Ok(self.cf_x.unwrap_or(cf_u_i1_x_base.value()?))
|
||||
@@ -1232,7 +1221,7 @@ mod tests {
|
||||
.into_inner()
|
||||
.ok_or(Error::NoInnerConstraintSystem)
|
||||
.unwrap();
|
||||
let cf_r1cs = extract_r1cs::<Fq>(&cs2);
|
||||
let cf_r1cs = extract_r1cs::<Fq>(&cs2).unwrap();
|
||||
println!("CF m x n: {} x {}", cf_r1cs.A.n_rows, cf_r1cs.A.n_cols);
|
||||
|
||||
let (pedersen_params, _) =
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
use ark_crypto_primitives::sponge::Absorb;
|
||||
use ark_ec::{CurveGroup, Group};
|
||||
use ark_ff::PrimeField;
|
||||
use ark_r1cs_std::{groups::GroupOpsBounds, prelude::CurveVar, ToConstraintFieldGadget};
|
||||
use ark_r1cs_std::{prelude::CurveVar, ToConstraintFieldGadget};
|
||||
use ark_serialize::{CanonicalDeserialize, CanonicalSerialize};
|
||||
use ark_snark::SNARK;
|
||||
use ark_std::rand::{CryptoRng, RngCore};
|
||||
@@ -10,12 +10,15 @@ use ark_std::{One, Zero};
|
||||
use core::marker::PhantomData;
|
||||
|
||||
pub use super::decider_eth_circuit::DeciderEthCircuit;
|
||||
use super::{lcccs::LCCCS, HyperNova};
|
||||
use super::decider_eth_circuit::DeciderHyperNovaGadget;
|
||||
use super::HyperNova;
|
||||
use crate::commitment::{
|
||||
kzg::Proof as KZGProof, pedersen::Params as PedersenParams, CommitmentScheme,
|
||||
};
|
||||
use crate::folding::circuits::{nonnative::affine::NonNativeAffineVar, CF2};
|
||||
use crate::folding::circuits::decider::DeciderEnabledNIFS;
|
||||
use crate::folding::circuits::CF2;
|
||||
use crate::folding::nova::decider_eth::VerifierParam;
|
||||
use crate::folding::traits::{Inputize, WitnessOps};
|
||||
use crate::frontend::FCircuit;
|
||||
use crate::Error;
|
||||
use crate::{Decider as DeciderTrait, FoldingScheme};
|
||||
@@ -31,7 +34,6 @@ where
|
||||
kzg_proof: CS1::Proof,
|
||||
// rho used at the last fold, U_{i+1}=NIMFS.V(rho, U_i, u_i), it is checked in-circuit
|
||||
rho: C1::ScalarField,
|
||||
U_i1: LCCCS<C1>, // U_{i+1}, which is checked in-circuit
|
||||
// the KZG challenge is provided by the prover, but in-circuit it is checked to match
|
||||
// the in-circuit computed computed one.
|
||||
kzg_challenge: C1::ScalarField,
|
||||
@@ -75,8 +77,6 @@ where
|
||||
<C1 as Group>::ScalarField: Absorb,
|
||||
<C2 as Group>::ScalarField: Absorb,
|
||||
C1: CurveGroup<BaseField = C2::ScalarField, ScalarField = C2::BaseField>,
|
||||
for<'b> &'b GC1: GroupOpsBounds<'b, C1, GC1>,
|
||||
for<'b> &'b GC2: GroupOpsBounds<'b, C2, GC2>,
|
||||
// constrain FS into HyperNova, since this is a Decider specifically for HyperNova
|
||||
HyperNova<C1, GC1, C2, GC2, FC, CS1, CS2, MU, NU, false>: From<FS>,
|
||||
crate::folding::hypernova::ProverParams<C1, C2, CS1, CS2, false>:
|
||||
@@ -89,21 +89,18 @@ where
|
||||
type Proof = Proof<C1, CS1, S>;
|
||||
type VerifierParam = VerifierParam<C1, CS1::VerifierParams, S::VerifyingKey>;
|
||||
type PublicInput = Vec<C1::ScalarField>;
|
||||
type CommittedInstance = ();
|
||||
type CommittedInstance = Vec<C1>;
|
||||
|
||||
fn preprocess(
|
||||
mut rng: impl RngCore + CryptoRng,
|
||||
prep_param: Self::PreprocessorParam,
|
||||
fs: FS,
|
||||
) -> Result<(Self::ProverParam, Self::VerifierParam), Error> {
|
||||
let circuit =
|
||||
DeciderEthCircuit::<C1, GC1, C2, GC2, CS1, CS2>::from_hypernova::<FC, MU, NU>(
|
||||
fs.into(),
|
||||
)
|
||||
.unwrap();
|
||||
let circuit = DeciderEthCircuit::<C1, C2, GC2>::try_from(HyperNova::from(fs))?;
|
||||
|
||||
// get the Groth16 specific setup for the circuit
|
||||
let (g16_pk, g16_vk) = S::circuit_specific_setup(circuit, &mut rng).unwrap();
|
||||
let (g16_pk, g16_vk) = S::circuit_specific_setup(circuit, &mut rng)
|
||||
.map_err(|e| Error::SNARKSetupFail(e.to_string()))?;
|
||||
|
||||
// get the FoldingScheme prover & verifier params from HyperNova
|
||||
#[allow(clippy::type_complexity)]
|
||||
@@ -122,7 +119,7 @@ where
|
||||
|
||||
let pp = (g16_pk, hypernova_pp.cs_pp);
|
||||
|
||||
let vp = VerifierParam {
|
||||
let vp = Self::VerifierParam {
|
||||
pp_hash,
|
||||
snark_vp: g16_vk,
|
||||
cs_vp: hypernova_vp.cs_vp,
|
||||
@@ -137,40 +134,37 @@ where
|
||||
) -> Result<Self::Proof, Error> {
|
||||
let (snark_pk, cs_pk): (S::ProvingKey, CS1::ProverParams) = pp;
|
||||
|
||||
let circuit = DeciderEthCircuit::<C1, GC1, C2, GC2, CS1, CS2>::from_hypernova::<FC, MU, NU>(
|
||||
folding_scheme.into(),
|
||||
)?;
|
||||
let circuit = DeciderEthCircuit::<C1, C2, GC2>::try_from(HyperNova::from(folding_scheme))?;
|
||||
|
||||
let snark_proof = S::prove(&snark_pk, circuit.clone(), &mut rng)
|
||||
.map_err(|e| Error::Other(e.to_string()))?;
|
||||
|
||||
// Notice that since the `circuit` has been constructed at the `from_hypernova` call, which
|
||||
// in case of failure it would have returned an error there, the next two unwraps should
|
||||
// never reach an error.
|
||||
let rho_Fr = circuit.rho.ok_or(Error::Empty)?;
|
||||
let W_i1 = circuit.W_i1.ok_or(Error::Empty)?;
|
||||
let rho = circuit.randomness;
|
||||
|
||||
// get the challenges that have been already computed when preparing the circuit inputs in
|
||||
// the above `from_hypernova` call
|
||||
let challenge_W = circuit
|
||||
.kzg_challenge
|
||||
.ok_or(Error::MissingValue("kzg_challenge".to_string()))?;
|
||||
// the above `try_from` call
|
||||
let kzg_challenges = circuit.kzg_challenges.clone();
|
||||
|
||||
// generate KZG proofs
|
||||
let U_cmW_proof = CS1::prove_with_challenge(
|
||||
&cs_pk,
|
||||
challenge_W,
|
||||
&W_i1.w,
|
||||
&C1::ScalarField::zero(),
|
||||
None,
|
||||
)?;
|
||||
let kzg_proofs = circuit
|
||||
.W_i1
|
||||
.get_openings()
|
||||
.iter()
|
||||
.zip(&kzg_challenges)
|
||||
.map(|((v, _), &c)| {
|
||||
CS1::prove_with_challenge(&cs_pk, c, v, &C1::ScalarField::zero(), None)
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
let snark_proof =
|
||||
S::prove(&snark_pk, circuit, &mut rng).map_err(|e| Error::Other(e.to_string()))?;
|
||||
|
||||
Ok(Self::Proof {
|
||||
snark_proof,
|
||||
kzg_proof: U_cmW_proof,
|
||||
rho: rho_Fr,
|
||||
U_i1: circuit.U_i1.ok_or(Error::Empty)?,
|
||||
kzg_challenge: challenge_W,
|
||||
rho,
|
||||
kzg_proof: (kzg_proofs.len() == 1)
|
||||
.then(|| kzg_proofs[0].clone())
|
||||
.ok_or(Error::NotExpectedLength(kzg_proofs.len(), 1))?,
|
||||
kzg_challenge: (kzg_challenges.len() == 1)
|
||||
.then(|| kzg_challenges[0])
|
||||
.ok_or(Error::NotExpectedLength(kzg_challenges.len(), 1))?,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -180,45 +174,48 @@ where
|
||||
z_0: Vec<C1::ScalarField>,
|
||||
z_i: Vec<C1::ScalarField>,
|
||||
// we don't use the instances at the verifier level, since we check them in-circuit
|
||||
_running_instance: &Self::CommittedInstance,
|
||||
_incoming_instance: &Self::CommittedInstance,
|
||||
running_commitments: &Self::CommittedInstance,
|
||||
incoming_commitments: &Self::CommittedInstance,
|
||||
proof: &Self::Proof,
|
||||
) -> Result<bool, Error> {
|
||||
if i <= C1::ScalarField::one() {
|
||||
return Err(Error::NotEnoughSteps);
|
||||
}
|
||||
|
||||
let (pp_hash, snark_vk, cs_vk): (C1::ScalarField, S::VerifyingKey, CS1::VerifierParams) =
|
||||
(vp.pp_hash, vp.snark_vp, vp.cs_vp);
|
||||
let Self::VerifierParam {
|
||||
pp_hash,
|
||||
snark_vp,
|
||||
cs_vp,
|
||||
} = vp;
|
||||
|
||||
// 6.2. Fold the commitments
|
||||
let C = DeciderHyperNovaGadget::fold_group_elements_native(
|
||||
running_commitments,
|
||||
incoming_commitments,
|
||||
None,
|
||||
proof.rho,
|
||||
)?[0];
|
||||
|
||||
// Note: the NIMFS proof is checked inside the DeciderEthCircuit, which ensures that the
|
||||
// 'proof.U_i1' is correctly computed
|
||||
|
||||
let (cmC_x, cmC_y) = NonNativeAffineVar::inputize(proof.U_i1.C)?;
|
||||
|
||||
let public_input: Vec<C1::ScalarField> = [
|
||||
vec![pp_hash, i],
|
||||
z_0,
|
||||
z_i,
|
||||
// U_i+1:
|
||||
cmC_x,
|
||||
cmC_y,
|
||||
vec![proof.U_i1.u],
|
||||
proof.U_i1.x.clone(),
|
||||
proof.U_i1.r_x.clone(),
|
||||
proof.U_i1.v.clone(),
|
||||
vec![proof.kzg_challenge, proof.kzg_proof.eval, proof.rho],
|
||||
&[pp_hash, i][..],
|
||||
&z_0,
|
||||
&z_i,
|
||||
&C.inputize(),
|
||||
&[proof.kzg_challenge, proof.kzg_proof.eval, proof.rho],
|
||||
]
|
||||
.concat();
|
||||
|
||||
let snark_v = S::verify(&snark_vk, &public_input, &proof.snark_proof)
|
||||
let snark_v = S::verify(&snark_vp, &public_input, &proof.snark_proof)
|
||||
.map_err(|e| Error::Other(e.to_string()))?;
|
||||
if !snark_v {
|
||||
return Err(Error::SNARKVerificationFail);
|
||||
}
|
||||
|
||||
// 7.3. Verify the KZG proof
|
||||
// we're at the Ethereum EVM case, so the CS1 is KZG commitments
|
||||
CS1::verify_with_challenge(&cs_vk, proof.kzg_challenge, &proof.U_i1.C, &proof.kzg_proof)?;
|
||||
CS1::verify_with_challenge(&cs_vp, proof.kzg_challenge, &C, &proof.kzg_proof)?;
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
@@ -229,13 +226,14 @@ pub mod tests {
|
||||
use ark_bn254::{constraints::GVar, Bn254, Fr, G1Projective as Projective};
|
||||
use ark_groth16::Groth16;
|
||||
use ark_grumpkin::{constraints::GVar as GVar2, Projective as Projective2};
|
||||
use ark_serialize::{Compress, Validate};
|
||||
use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, Compress, Validate};
|
||||
|
||||
use super::*;
|
||||
use crate::commitment::{kzg::KZG, pedersen::Pedersen};
|
||||
use crate::folding::hypernova::cccs::CCCS;
|
||||
use crate::folding::hypernova::lcccs::LCCCS;
|
||||
use crate::folding::hypernova::PreprocessorParam;
|
||||
use crate::folding::nova::decider_eth::VerifierParam;
|
||||
use crate::folding::traits::CommittedInstanceOps;
|
||||
use crate::frontend::utils::CubicFCircuit;
|
||||
use crate::transcript::poseidon::poseidon_canonical_config;
|
||||
|
||||
@@ -300,8 +298,8 @@ pub mod tests {
|
||||
hypernova.i,
|
||||
hypernova.z_0,
|
||||
hypernova.z_i,
|
||||
&(),
|
||||
&(),
|
||||
&hypernova.U_i.get_commitments(),
|
||||
&hypernova.u_i.get_commitments(),
|
||||
&proof,
|
||||
)
|
||||
.unwrap();
|
||||
@@ -403,8 +401,8 @@ pub mod tests {
|
||||
hypernova.i,
|
||||
hypernova.z_0.clone(),
|
||||
hypernova.z_i.clone(),
|
||||
&(),
|
||||
&(),
|
||||
&hypernova.U_i.get_commitments(),
|
||||
&hypernova.u_i.get_commitments(),
|
||||
&proof,
|
||||
)
|
||||
.unwrap();
|
||||
@@ -470,8 +468,8 @@ pub mod tests {
|
||||
i_deserialized,
|
||||
z_0_deserialized.clone(),
|
||||
z_i_deserialized.clone(),
|
||||
&(),
|
||||
&(),
|
||||
&hypernova.U_i.get_commitments(),
|
||||
&hypernova.u_i.get_commitments(),
|
||||
&proof_deserialized,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -2,50 +2,73 @@
|
||||
/// other more efficient approaches can be used.
|
||||
use ark_crypto_primitives::sponge::{
|
||||
constraints::CryptographicSpongeVar,
|
||||
poseidon::{constraints::PoseidonSpongeVar, PoseidonConfig, PoseidonSponge},
|
||||
poseidon::{constraints::PoseidonSpongeVar, PoseidonSponge},
|
||||
Absorb, CryptographicSponge,
|
||||
};
|
||||
use ark_ec::{CurveGroup, Group};
|
||||
use ark_ec::CurveGroup;
|
||||
use ark_ff::PrimeField;
|
||||
use ark_poly::Polynomial;
|
||||
use ark_r1cs_std::{
|
||||
alloc::{AllocVar, AllocationMode},
|
||||
boolean::Boolean,
|
||||
eq::EqGadget,
|
||||
fields::fp::FpVar,
|
||||
groups::GroupOpsBounds,
|
||||
prelude::CurveVar,
|
||||
ToConstraintFieldGadget,
|
||||
};
|
||||
use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystemRef, Namespace, SynthesisError};
|
||||
use ark_std::Zero;
|
||||
use core::{borrow::Borrow, marker::PhantomData};
|
||||
use ark_relations::r1cs::{Namespace, SynthesisError};
|
||||
use ark_std::{borrow::Borrow, log2, marker::PhantomData};
|
||||
|
||||
use super::{
|
||||
circuits::{CCCSVar, LCCCSVar, NIMFSGadget, ProofVar as NIMFSProofVar},
|
||||
nimfs::{NIMFSProof, NIMFS},
|
||||
HyperNova, Witness, CCCS, LCCCS,
|
||||
};
|
||||
use crate::folding::circuits::{
|
||||
cyclefold::{CycleFoldCommittedInstance, CycleFoldWitness},
|
||||
CF1, CF2,
|
||||
};
|
||||
use crate::folding::circuits::{decider::on_chain::GenericOnchainDeciderCircuit, CF1, CF2};
|
||||
use crate::folding::traits::{WitnessOps, WitnessVarOps};
|
||||
use crate::frontend::FCircuit;
|
||||
use crate::transcript::{Transcript, TranscriptVar};
|
||||
use crate::utils::{
|
||||
gadgets::{eval_mle, MatrixGadget, SparseMatrixVar},
|
||||
vec::poly_from_vec,
|
||||
};
|
||||
use crate::utils::gadgets::{eval_mle, MatrixGadget};
|
||||
use crate::Error;
|
||||
use crate::{
|
||||
arith::{ccs::CCS, r1cs::R1CS},
|
||||
folding::traits::{CommittedInstanceVarOps, Dummy, WitnessVarOps},
|
||||
arith::{
|
||||
ccs::{circuits::CCSMatricesVar, CCS},
|
||||
ArithGadget,
|
||||
},
|
||||
folding::circuits::decider::{EvalGadget, KZGChallengesGadget},
|
||||
};
|
||||
use crate::{
|
||||
commitment::{pedersen::Params as PedersenParams, CommitmentScheme},
|
||||
folding::nova::decider_eth_circuit::evaluate_gadget,
|
||||
folding::circuits::decider::DeciderEnabledNIFS,
|
||||
};
|
||||
|
||||
impl<C: CurveGroup> ArithGadget<WitnessVar<CF1<C>>, LCCCSVar<C>> for CCSMatricesVar<CF1<C>> {
|
||||
type Evaluation = Vec<FpVar<CF1<C>>>;
|
||||
|
||||
fn eval_relation(
|
||||
&self,
|
||||
w: &WitnessVar<CF1<C>>,
|
||||
u: &LCCCSVar<C>,
|
||||
) -> Result<Self::Evaluation, SynthesisError> {
|
||||
let z = [&[u.u.clone()][..], &u.x, &w.w].concat();
|
||||
|
||||
self.M
|
||||
.iter()
|
||||
.map(|M_j| {
|
||||
let s = log2(M_j.n_rows) as usize;
|
||||
let Mz = M_j.mul_vector(&z)?;
|
||||
Ok(eval_mle(s, Mz, u.r_x.clone()))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn enforce_evaluation(
|
||||
_w: &WitnessVar<CF1<C>>,
|
||||
u: &LCCCSVar<C>,
|
||||
v: Self::Evaluation,
|
||||
) -> Result<(), SynthesisError> {
|
||||
v.enforce_equal(&u.v)
|
||||
}
|
||||
}
|
||||
|
||||
/// In-circuit representation of the Witness associated to the CommittedInstance.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct WitnessVar<F: PrimeField> {
|
||||
@@ -77,135 +100,38 @@ impl<F: PrimeField> WitnessVarOps<F> for WitnessVar<F> {
|
||||
}
|
||||
}
|
||||
|
||||
/// CCSMatricesVar contains the matrices 'M' of the CCS without the rest of CCS parameters.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CCSMatricesVar<F: PrimeField> {
|
||||
// we only need native representation, so the constraint field==F
|
||||
pub M: Vec<SparseMatrixVar<F, F, FpVar<F>>>,
|
||||
}
|
||||
pub type DeciderEthCircuit<C1, C2, GC2> = GenericOnchainDeciderCircuit<
|
||||
C1,
|
||||
C2,
|
||||
GC2,
|
||||
LCCCS<C1>,
|
||||
CCCS<C1>,
|
||||
Witness<CF1<C1>>,
|
||||
CCS<CF1<C1>>,
|
||||
CCSMatricesVar<CF1<C1>>,
|
||||
DeciderHyperNovaGadget,
|
||||
>;
|
||||
|
||||
impl<F> AllocVar<CCS<F>, F> for CCSMatricesVar<F>
|
||||
impl<
|
||||
C1: CurveGroup,
|
||||
GC1: CurveVar<C1, CF2<C1>> + ToConstraintFieldGadget<CF2<C1>>,
|
||||
C2: CurveGroup,
|
||||
GC2: CurveVar<C2, CF2<C2>> + ToConstraintFieldGadget<CF2<C2>>,
|
||||
FC: FCircuit<C1::ScalarField>,
|
||||
CS1: CommitmentScheme<C1, H>,
|
||||
// enforce that the CS2 is Pedersen commitment scheme, since we're at Ethereum's EVM decider
|
||||
CS2: CommitmentScheme<C2, H, ProverParams = PedersenParams<C2>>,
|
||||
const MU: usize,
|
||||
const NU: usize,
|
||||
const H: bool,
|
||||
> TryFrom<HyperNova<C1, GC1, C2, GC2, FC, CS1, CS2, MU, NU, H>>
|
||||
for DeciderEthCircuit<C1, C2, GC2>
|
||||
where
|
||||
F: PrimeField,
|
||||
CF1<C1>: Absorb,
|
||||
{
|
||||
fn new_variable<T: Borrow<CCS<F>>>(
|
||||
cs: impl Into<Namespace<F>>,
|
||||
f: impl FnOnce() -> Result<T, SynthesisError>,
|
||||
_mode: AllocationMode,
|
||||
) -> Result<Self, SynthesisError> {
|
||||
f().and_then(|val| {
|
||||
let cs = cs.into();
|
||||
type Error = Error;
|
||||
|
||||
let M: Vec<SparseMatrixVar<F, F, FpVar<F>>> = val
|
||||
.borrow()
|
||||
.M
|
||||
.iter()
|
||||
.map(|M| SparseMatrixVar::<F, F, FpVar<F>>::new_constant(cs.clone(), M.clone()))
|
||||
.collect::<Result<_, SynthesisError>>()?;
|
||||
|
||||
Ok(Self { M })
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Gadget to check the LCCCS relation both over the native constraint field and over the
|
||||
/// non-native constraint field.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct LCCCSCheckerGadget {}
|
||||
impl LCCCSCheckerGadget {
|
||||
/// performs in-circuit the RelaxedR1CS check for native variables (Az∘Bz==uCz+E)
|
||||
pub fn check<F: PrimeField>(
|
||||
s: usize,
|
||||
ccs_mat: CCSMatricesVar<F>,
|
||||
z: Vec<FpVar<F>>,
|
||||
// LCCCS values
|
||||
r_x: Vec<FpVar<F>>,
|
||||
v: Vec<FpVar<F>>,
|
||||
) -> Result<(), SynthesisError> {
|
||||
let computed_v: Vec<FpVar<F>> = ccs_mat
|
||||
.M
|
||||
.iter()
|
||||
.map(|M_j| {
|
||||
let Mz = M_j.mul_vector(&z)?;
|
||||
Ok(eval_mle(s, Mz, r_x.clone()))
|
||||
})
|
||||
.collect::<Result<Vec<FpVar<F>>, SynthesisError>>()?;
|
||||
|
||||
(computed_v).enforce_equal(&v)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Circuit that implements the in-circuit checks needed for the HyperNova's onchain (Ethereum's
|
||||
/// EVM) verification.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct DeciderEthCircuit<C1, GC1, C2, GC2, CS1, CS2, const H: bool = false>
|
||||
where
|
||||
C1: CurveGroup,
|
||||
GC1: CurveVar<C1, CF2<C1>>,
|
||||
C2: CurveGroup,
|
||||
GC2: CurveVar<C2, CF2<C2>>,
|
||||
CS1: CommitmentScheme<C1, H>,
|
||||
CS2: CommitmentScheme<C2, H>,
|
||||
{
|
||||
_c1: PhantomData<C1>,
|
||||
_gc1: PhantomData<GC1>,
|
||||
_c2: PhantomData<C2>,
|
||||
_gc2: PhantomData<GC2>,
|
||||
_cs1: PhantomData<CS1>,
|
||||
_cs2: PhantomData<CS2>,
|
||||
|
||||
/// E vector's length of the CycleFold instance witness
|
||||
pub cf_E_len: usize,
|
||||
/// CCS of the Augmented Function circuit
|
||||
pub ccs: CCS<C1::ScalarField>,
|
||||
/// R1CS of the CycleFold circuit
|
||||
pub cf_r1cs: R1CS<C2::ScalarField>,
|
||||
/// CycleFold PedersenParams over C2
|
||||
pub cf_pedersen_params: PedersenParams<C2>,
|
||||
pub poseidon_config: PoseidonConfig<CF1<C1>>,
|
||||
/// public params hash
|
||||
pub pp_hash: Option<C1::ScalarField>,
|
||||
pub i: Option<CF1<C1>>,
|
||||
/// initial state
|
||||
pub z_0: Option<Vec<C1::ScalarField>>,
|
||||
/// current i-th state
|
||||
pub z_i: Option<Vec<C1::ScalarField>>,
|
||||
/// Nova instances
|
||||
pub U_i: Option<LCCCS<C1>>,
|
||||
pub W_i: Option<Witness<C1::ScalarField>>,
|
||||
pub u_i: Option<CCCS<C1>>,
|
||||
pub w_i: Option<Witness<C1::ScalarField>>,
|
||||
pub U_i1: Option<LCCCS<C1>>,
|
||||
pub W_i1: Option<Witness<C1::ScalarField>>,
|
||||
pub nimfs_proof: Option<NIMFSProof<C1>>,
|
||||
// rho is the 'random' value used for the fold of the last 2 instances
|
||||
pub rho: Option<C1::ScalarField>,
|
||||
/// CycleFold running instance
|
||||
pub cf_U_i: Option<CycleFoldCommittedInstance<C2>>,
|
||||
pub cf_W_i: Option<CycleFoldWitness<C2>>,
|
||||
|
||||
/// KZG challenge & eval
|
||||
pub kzg_challenge: Option<C1::ScalarField>,
|
||||
pub eval_W: Option<C1::ScalarField>,
|
||||
}
|
||||
|
||||
impl<C1, GC1, C2, GC2, CS1, CS2, const H: bool> DeciderEthCircuit<C1, GC1, C2, GC2, CS1, CS2, H>
|
||||
where
|
||||
C1: CurveGroup,
|
||||
C2: CurveGroup,
|
||||
GC1: CurveVar<C1, CF2<C1>> + ToConstraintFieldGadget<CF2<C1>>,
|
||||
GC2: CurveVar<C2, CF2<C2>> + ToConstraintFieldGadget<CF2<C2>>,
|
||||
CS1: CommitmentScheme<C1, H>,
|
||||
// enforce that the CS2 is Pedersen commitment scheme, since we're at Ethereum's EVM decider
|
||||
CS2: CommitmentScheme<C2, H, ProverParams = PedersenParams<C2>>,
|
||||
<C1 as Group>::ScalarField: Absorb,
|
||||
<C1 as CurveGroup>::BaseField: PrimeField,
|
||||
{
|
||||
/// returns an instance of the DeciderEthCircuit from the given HyperNova struct
|
||||
pub fn from_hypernova<FC: FCircuit<C1::ScalarField>, const MU: usize, const NU: usize>(
|
||||
hn: HyperNova<C1, GC1, C2, GC2, FC, CS1, CS2, MU, NU, H>,
|
||||
) -> Result<Self, Error> {
|
||||
fn try_from(hn: HyperNova<C1, GC1, C2, GC2, FC, CS1, CS2, MU, NU, H>) -> Result<Self, Error> {
|
||||
// compute the U_{i+1}, W_{i+1}, by folding the last running & incoming instances
|
||||
let mut transcript = PoseidonSponge::<C1::ScalarField>::new(&hn.poseidon_config);
|
||||
transcript.absorb(&hn.pp_hash);
|
||||
@@ -219,288 +145,92 @@ where
|
||||
)?;
|
||||
|
||||
// compute the KZG challenges used as inputs in the circuit
|
||||
let kzg_challenge =
|
||||
KZGChallengeGadget::<C1>::get_challenge_native(&mut transcript, U_i1.clone())?;
|
||||
let kzg_challenges = KZGChallengesGadget::get_challenges_native(&mut transcript, &U_i1);
|
||||
|
||||
// get KZG evals
|
||||
let mut W = W_i1.w.clone();
|
||||
W.extend(
|
||||
std::iter::repeat(C1::ScalarField::zero())
|
||||
.take(W_i1.w.len().next_power_of_two() - W_i1.w.len()),
|
||||
);
|
||||
let p_W = poly_from_vec(W.to_vec())?;
|
||||
let eval_W = p_W.evaluate(&kzg_challenge);
|
||||
let kzg_evaluations = W_i1
|
||||
.get_openings()
|
||||
.iter()
|
||||
.zip(&kzg_challenges)
|
||||
.map(|((v, _), &c)| EvalGadget::evaluate_native(v, c))
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
Ok(Self {
|
||||
_c1: PhantomData,
|
||||
_gc1: PhantomData,
|
||||
_c2: PhantomData,
|
||||
_gc2: PhantomData,
|
||||
_cs1: PhantomData,
|
||||
_cs2: PhantomData,
|
||||
|
||||
cf_E_len: hn.cf_W_i.E.len(),
|
||||
ccs: hn.ccs,
|
||||
cf_r1cs: hn.cf_r1cs,
|
||||
_avar: PhantomData,
|
||||
arith: hn.ccs,
|
||||
cf_arith: hn.cf_r1cs,
|
||||
cf_pedersen_params: hn.cf_cs_pp,
|
||||
poseidon_config: hn.poseidon_config,
|
||||
pp_hash: Some(hn.pp_hash),
|
||||
i: Some(hn.i),
|
||||
z_0: Some(hn.z_0),
|
||||
z_i: Some(hn.z_i),
|
||||
U_i: Some(hn.U_i),
|
||||
W_i: Some(hn.W_i),
|
||||
u_i: Some(hn.u_i),
|
||||
w_i: Some(hn.w_i),
|
||||
U_i1: Some(U_i1),
|
||||
W_i1: Some(W_i1),
|
||||
nimfs_proof: Some(nimfs_proof),
|
||||
rho: Some(rho),
|
||||
cf_U_i: Some(hn.cf_U_i),
|
||||
cf_W_i: Some(hn.cf_W_i),
|
||||
kzg_challenge: Some(kzg_challenge),
|
||||
eval_W: Some(eval_W),
|
||||
pp_hash: hn.pp_hash,
|
||||
i: hn.i,
|
||||
z_0: hn.z_0,
|
||||
z_i: hn.z_i,
|
||||
U_i: hn.U_i,
|
||||
W_i: hn.W_i,
|
||||
u_i: hn.u_i,
|
||||
w_i: hn.w_i,
|
||||
U_i1,
|
||||
W_i1,
|
||||
proof: nimfs_proof,
|
||||
randomness: rho,
|
||||
cf_U_i: hn.cf_U_i,
|
||||
cf_W_i: hn.cf_W_i,
|
||||
kzg_challenges,
|
||||
kzg_evaluations,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<C1, GC1, C2, GC2, CS1, CS2> ConstraintSynthesizer<CF1<C1>>
|
||||
for DeciderEthCircuit<C1, GC1, C2, GC2, CS1, CS2>
|
||||
pub struct DeciderHyperNovaGadget;
|
||||
|
||||
impl<C: CurveGroup> DeciderEnabledNIFS<C, LCCCS<C>, CCCS<C>, Witness<C::ScalarField>, CCS<CF1<C>>>
|
||||
for DeciderHyperNovaGadget
|
||||
where
|
||||
C1: CurveGroup,
|
||||
C2: CurveGroup,
|
||||
GC1: CurveVar<C1, CF2<C1>>,
|
||||
GC2: CurveVar<C2, CF2<C2>> + ToConstraintFieldGadget<CF2<C2>>,
|
||||
CS1: CommitmentScheme<C1>,
|
||||
CS2: CommitmentScheme<C2>,
|
||||
C1::ScalarField: PrimeField,
|
||||
<C1 as CurveGroup>::BaseField: PrimeField,
|
||||
<C2 as CurveGroup>::BaseField: PrimeField,
|
||||
<C1 as Group>::ScalarField: Absorb,
|
||||
<C2 as Group>::ScalarField: Absorb,
|
||||
C1: CurveGroup<BaseField = C2::ScalarField, ScalarField = C2::BaseField>,
|
||||
for<'b> &'b GC2: GroupOpsBounds<'b, C2, GC2>,
|
||||
CF1<C>: Absorb,
|
||||
{
|
||||
fn generate_constraints(self, cs: ConstraintSystemRef<CF1<C1>>) -> Result<(), SynthesisError> {
|
||||
let ccs_matrices =
|
||||
CCSMatricesVar::<C1::ScalarField>::new_witness(cs.clone(), || Ok(self.ccs.clone()))?;
|
||||
type ProofDummyCfg = (usize, usize, usize, usize);
|
||||
type Proof = NIMFSProof<C>;
|
||||
type Randomness = CF1<C>;
|
||||
type RandomnessDummyCfg = ();
|
||||
|
||||
let pp_hash = FpVar::<CF1<C1>>::new_input(cs.clone(), || {
|
||||
Ok(self.pp_hash.unwrap_or_else(CF1::<C1>::zero))
|
||||
})?;
|
||||
let i =
|
||||
FpVar::<CF1<C1>>::new_input(cs.clone(), || Ok(self.i.unwrap_or_else(CF1::<C1>::zero)))?;
|
||||
let z_0 = Vec::<FpVar<CF1<C1>>>::new_input(cs.clone(), || {
|
||||
Ok(self.z_0.unwrap_or(vec![CF1::<C1>::zero()]))
|
||||
})?;
|
||||
let z_i = Vec::<FpVar<CF1<C1>>>::new_input(cs.clone(), || {
|
||||
Ok(self.z_i.unwrap_or(vec![CF1::<C1>::zero()]))
|
||||
})?;
|
||||
|
||||
let U_dummy_native = LCCCS::<C1>::dummy(&self.ccs);
|
||||
let u_dummy_native = CCCS::<C1>::dummy(&self.ccs);
|
||||
let w_dummy_native = Witness::<C1::ScalarField>::new(
|
||||
vec![C1::ScalarField::zero(); self.ccs.n - 3 /* (3=2+1, since u_i.x.len=2) */],
|
||||
);
|
||||
|
||||
let U_i = LCCCSVar::<C1>::new_witness(cs.clone(), || {
|
||||
Ok(self.U_i.unwrap_or(U_dummy_native.clone()))
|
||||
})?;
|
||||
let u_i = CCCSVar::<C1>::new_witness(cs.clone(), || {
|
||||
Ok(self.u_i.unwrap_or(u_dummy_native.clone()))
|
||||
})?;
|
||||
// here (U_i1, W_i1) = NIFS.P( (U_i,W_i), (u_i,w_i))
|
||||
let U_i1 = LCCCSVar::<C1>::new_input(cs.clone(), || {
|
||||
Ok(self.U_i1.unwrap_or(U_dummy_native.clone()))
|
||||
})?;
|
||||
let W_i1 = WitnessVar::<C1::ScalarField>::new_witness(cs.clone(), || {
|
||||
Ok(self.W_i1.unwrap_or(w_dummy_native.clone()))
|
||||
})?;
|
||||
let nimfs_proof_dummy = NIMFSProof::<C1>::dummy((&self.ccs, 1, 1)); // mu=1 & nu=1 because the last fold is 2-to-1
|
||||
let nimfs_proof = NIMFSProofVar::<C1>::new_witness(cs.clone(), || {
|
||||
Ok(self.nimfs_proof.unwrap_or(nimfs_proof_dummy))
|
||||
})?;
|
||||
|
||||
// allocate the inputs for the check 6
|
||||
let kzg_challenge = FpVar::<CF1<C1>>::new_input(cs.clone(), || {
|
||||
Ok(self.kzg_challenge.unwrap_or_else(CF1::<C1>::zero))
|
||||
})?;
|
||||
let eval_W = FpVar::<CF1<C1>>::new_input(cs.clone(), || {
|
||||
Ok(self.eval_W.unwrap_or_else(CF1::<C1>::zero))
|
||||
})?;
|
||||
|
||||
// `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();
|
||||
fn fold_field_elements_gadget(
|
||||
arith: &CCS<CF1<C>>,
|
||||
transcript: &mut PoseidonSpongeVar<CF1<C>>,
|
||||
pp_hash: FpVar<CF1<C>>,
|
||||
U: LCCCSVar<C>,
|
||||
_U_vec: Vec<FpVar<CF1<C>>>,
|
||||
u: CCCSVar<C>,
|
||||
proof: Self::Proof,
|
||||
randomness: Self::Randomness,
|
||||
) -> Result<LCCCSVar<C>, SynthesisError> {
|
||||
let cs = transcript.cs();
|
||||
transcript.absorb(&pp_hash)?;
|
||||
|
||||
// NOTE: we use the same enumeration as in Nova's DeciderEthCircuit described at
|
||||
// https://privacy-scaling-explorations.github.io/sonobe-docs/design/nova-decider-onchain.html
|
||||
// in order to make it easier to reason about.
|
||||
|
||||
// 1. check LCCCS relation of U_{i+1}
|
||||
let z_U1: Vec<FpVar<CF1<C1>>> =
|
||||
[vec![U_i1.u.clone()], U_i1.x.to_vec(), W_i1.w.to_vec()].concat();
|
||||
LCCCSCheckerGadget::check(
|
||||
self.ccs.s,
|
||||
ccs_matrices,
|
||||
z_U1,
|
||||
U_i1.r_x.clone(),
|
||||
U_i1.v.clone(),
|
||||
)?;
|
||||
|
||||
// 3.a u_i.x[0] == H(i, z_0, z_i, U_i)
|
||||
let (u_i_x, _) = U_i.clone().hash(&sponge, &pp_hash, &i, &z_0, &z_i)?;
|
||||
(u_i.x[0]).enforce_equal(&u_i_x)?;
|
||||
|
||||
#[cfg(feature = "light-test")]
|
||||
log::warn!("[WARNING]: Running with the 'light-test' feature, skipping the big part of the DeciderEthCircuit.\n Only for testing purposes.");
|
||||
|
||||
// The following two checks (and their respective allocations) are disabled for normal
|
||||
// tests since they take several millions of constraints and would take several minutes
|
||||
// (and RAM) to run the test. It is active by default, and not active only when
|
||||
// 'light-test' feature is used.
|
||||
#[cfg(not(feature = "light-test"))]
|
||||
{
|
||||
// imports here instead of at the top of the file, so we avoid having multiple
|
||||
// `#[cfg(not(test))]`
|
||||
use crate::commitment::pedersen::PedersenGadget;
|
||||
use crate::folding::circuits::nonnative::uint::NonNativeUintVar;
|
||||
use crate::folding::nova::decider_eth_circuit::{R1CSVar, RelaxedR1CSGadget};
|
||||
use crate::folding::{
|
||||
circuits::cyclefold::{
|
||||
CycleFoldCommittedInstanceVar, CycleFoldConfig, CycleFoldWitnessVar,
|
||||
},
|
||||
nova::NovaCycleFoldConfig,
|
||||
};
|
||||
use ark_r1cs_std::ToBitsGadget;
|
||||
|
||||
let cf_u_dummy_native =
|
||||
CycleFoldCommittedInstance::<C2>::dummy(NovaCycleFoldConfig::<C1>::IO_LEN);
|
||||
let cf_w_dummy_native = CycleFoldWitness::<C2>::dummy(&self.cf_r1cs);
|
||||
let cf_U_i = CycleFoldCommittedInstanceVar::<C2, GC2>::new_witness(cs.clone(), || {
|
||||
Ok(self.cf_U_i.unwrap_or_else(|| cf_u_dummy_native.clone()))
|
||||
})?;
|
||||
let cf_W_i = CycleFoldWitnessVar::<C2>::new_witness(cs.clone(), || {
|
||||
Ok(self.cf_W_i.unwrap_or(cf_w_dummy_native.clone()))
|
||||
})?;
|
||||
|
||||
// 3.b u_i.x[1] == H(cf_U_i)
|
||||
let (cf_u_i_x, _) = cf_U_i.clone().hash(&sponge, pp_hash.clone())?;
|
||||
(u_i.x[1]).enforce_equal(&cf_u_i_x)?;
|
||||
|
||||
// 4. check Pedersen commitments of cf_U_i.{cmE, cmW}
|
||||
let H = GC2::new_constant(cs.clone(), self.cf_pedersen_params.h)?;
|
||||
let G = Vec::<GC2>::new_constant(cs.clone(), self.cf_pedersen_params.generators)?;
|
||||
let cf_W_i_E_bits: Result<Vec<Vec<Boolean<CF1<C1>>>>, SynthesisError> =
|
||||
cf_W_i.E.iter().map(|E_i| E_i.to_bits_le()).collect();
|
||||
let cf_W_i_W_bits: Result<Vec<Vec<Boolean<CF1<C1>>>>, SynthesisError> =
|
||||
cf_W_i.W.iter().map(|W_i| W_i.to_bits_le()).collect();
|
||||
|
||||
let computed_cmE = PedersenGadget::<C2, GC2>::commit(
|
||||
H.clone(),
|
||||
G.clone(),
|
||||
cf_W_i_E_bits?,
|
||||
cf_W_i.rE.to_bits_le()?,
|
||||
)?;
|
||||
cf_U_i.cmE.enforce_equal(&computed_cmE)?;
|
||||
let computed_cmW =
|
||||
PedersenGadget::<C2, GC2>::commit(H, G, cf_W_i_W_bits?, cf_W_i.rW.to_bits_le()?)?;
|
||||
cf_U_i.cmW.enforce_equal(&computed_cmW)?;
|
||||
|
||||
let cf_r1cs =
|
||||
R1CSVar::<C1::BaseField, CF1<C1>, NonNativeUintVar<CF1<C1>>>::new_witness(
|
||||
cs.clone(),
|
||||
|| Ok(self.cf_r1cs.clone()),
|
||||
)?;
|
||||
|
||||
// 5. check RelaxedR1CS of cf_U_i
|
||||
let cf_z_U = [vec![cf_U_i.u.clone()], cf_U_i.x.to_vec(), cf_W_i.W.to_vec()].concat();
|
||||
RelaxedR1CSGadget::check_nonnative(cf_r1cs, cf_W_i.E, cf_U_i.u.clone(), cf_z_U)?;
|
||||
}
|
||||
|
||||
// The following steps are in non-increasing order because the `computed_U_i1` is computed
|
||||
// at step 8, and later used at step 6. Notice that in Nova, we compute U_i1 outside of the
|
||||
// circuit, in the smart contract, but here we're computing it in-circuit, and we reuse the
|
||||
// `rho_bits` computed along the way of computing `computed_U_i1` for the later `rho_powers`
|
||||
// check (6.b).
|
||||
|
||||
// 8.a verify the NIMFS.V of the final fold, and check that the obtained rho_powers from the
|
||||
// transcript match the one from the public input (so we avoid the onchain logic of the
|
||||
// verifier computing it).
|
||||
// Notice that the NIMFSGadget performs all the logic except of checking the fold of the
|
||||
// instances C parameter, which would require non-native arithmetic, henceforth we perform
|
||||
// that check outside the circuit.
|
||||
let (computed_U_i1, rho_bits) = NIMFSGadget::<C1>::verify(
|
||||
let nimfs_proof = NIMFSProofVar::<C>::new_witness(cs.clone(), || Ok(proof))?;
|
||||
let rho = FpVar::<CF1<C>>::new_input(cs.clone(), || Ok(randomness))?;
|
||||
let (computed_U_i1, rho_bits) = NIMFSGadget::<C>::verify(
|
||||
cs.clone(),
|
||||
&self.ccs.clone(),
|
||||
&mut transcript,
|
||||
&[U_i],
|
||||
&[u_i],
|
||||
arith,
|
||||
transcript,
|
||||
&[U],
|
||||
&[u],
|
||||
nimfs_proof,
|
||||
Boolean::TRUE, // enabled
|
||||
)?;
|
||||
|
||||
// 6.a check KZG challenges
|
||||
// Notice that this step is done after the NIMFS.V, to follow the transcript absorbs order
|
||||
// done outside the circuit, where to compute the challenge it needs first to compute the
|
||||
// U_{i+1} through the NIMFS.V
|
||||
let incircuit_challenge =
|
||||
KZGChallengeGadget::<C1>::get_challenge_gadget(&mut transcript, U_i1.clone())?;
|
||||
incircuit_challenge.enforce_equal(&kzg_challenge)?;
|
||||
|
||||
// 6.b check that the obtained U_{i+1} from the NIMFS.V matches the U_{i+1} from the input,
|
||||
// except for the C parameter, which to compute its folding would require non-native logic
|
||||
// in-circuit, and we defer it to outside the circuit.
|
||||
computed_U_i1.u.enforce_equal(&U_i1.u)?;
|
||||
computed_U_i1.r_x.enforce_equal(&U_i1.r_x)?;
|
||||
computed_U_i1.v.enforce_equal(&U_i1.v)?;
|
||||
|
||||
// 7. check eval_W==p_W(c_W)
|
||||
let incircuit_eval_W = evaluate_gadget::<CF1<C1>>(W_i1.w, incircuit_challenge)?;
|
||||
incircuit_eval_W.enforce_equal(&eval_W)?;
|
||||
|
||||
// 8.b check that the in-circuit computed r is equal to the inputted r.
|
||||
|
||||
let rho = Boolean::le_bits_to_fp_var(&rho_bits)?;
|
||||
let external_rho =
|
||||
FpVar::<CF1<C1>>::new_input(cs.clone(), || Ok(self.rho.unwrap_or(CF1::<C1>::zero())))?;
|
||||
rho.enforce_equal(&external_rho)?;
|
||||
|
||||
Ok(())
|
||||
Boolean::le_bits_to_fp_var(&rho_bits)?.enforce_equal(&rho)?;
|
||||
Ok(computed_U_i1)
|
||||
}
|
||||
}
|
||||
|
||||
/// Gadget that computes the KZG challenges, also offers the rust native implementation compatible
|
||||
/// with the gadget.
|
||||
pub struct KZGChallengeGadget<C: CurveGroup> {
|
||||
_c: PhantomData<C>,
|
||||
}
|
||||
#[allow(clippy::type_complexity)]
|
||||
impl<C> KZGChallengeGadget<C>
|
||||
where
|
||||
C: CurveGroup,
|
||||
C::ScalarField: PrimeField,
|
||||
<C as CurveGroup>::BaseField: PrimeField,
|
||||
C::ScalarField: Absorb,
|
||||
{
|
||||
pub fn get_challenge_native<T: Transcript<C::ScalarField>>(
|
||||
transcript: &mut T,
|
||||
U_i: LCCCS<C>,
|
||||
) -> Result<C::ScalarField, Error> {
|
||||
// compute the KZG challenges, which are computed in-circuit and checked that it matches
|
||||
// the inputted one
|
||||
transcript.absorb_nonnative(&U_i.C);
|
||||
Ok(transcript.get_challenge())
|
||||
}
|
||||
// compatible with the native get_challenge_native
|
||||
pub fn get_challenge_gadget<S: CryptographicSponge, T: TranscriptVar<CF1<C>, S>>(
|
||||
transcript: &mut T,
|
||||
U_i: LCCCSVar<C>,
|
||||
) -> Result<FpVar<C::ScalarField>, SynthesisError> {
|
||||
transcript.absorb(&U_i.C.to_constraint_field()?)?;
|
||||
transcript.get_challenge()
|
||||
fn fold_group_elements_native(
|
||||
U_commitments: &[C],
|
||||
u_commitments: &[C],
|
||||
_: Option<Self::Proof>,
|
||||
r: Self::Randomness,
|
||||
) -> Result<Vec<C>, Error> {
|
||||
let U_C = U_commitments[0];
|
||||
let u_C = u_commitments[0];
|
||||
let C = U_C + u_C.mul(r);
|
||||
Ok(vec![C])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -508,10 +238,11 @@ where
|
||||
pub mod tests {
|
||||
use ark_bn254::{constraints::GVar, Fr, G1Projective as Projective};
|
||||
use ark_grumpkin::{constraints::GVar as GVar2, Projective as Projective2};
|
||||
use ark_relations::r1cs::ConstraintSystem;
|
||||
use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystem};
|
||||
use ark_std::{test_rng, UniformRand};
|
||||
|
||||
use super::*;
|
||||
use crate::arith::r1cs::R1CS;
|
||||
use crate::commitment::pedersen::Pedersen;
|
||||
use crate::folding::nova::PreprocessorParam;
|
||||
use crate::frontend::utils::CubicFCircuit;
|
||||
@@ -530,7 +261,7 @@ pub mod tests {
|
||||
let (pedersen_params, _) =
|
||||
Pedersen::<Projective>::setup(&mut rng, ccs.n - ccs.l - 1).unwrap();
|
||||
|
||||
let (lcccs, _) = ccs
|
||||
let (lcccs, w) = ccs
|
||||
.to_lcccs::<_, Projective, Pedersen<Projective>, false>(&mut rng, &pedersen_params, &z)
|
||||
.unwrap();
|
||||
|
||||
@@ -538,11 +269,10 @@ pub mod tests {
|
||||
|
||||
// CCS's (sparse) matrices are constants in the circuit
|
||||
let ccs_mat = CCSMatricesVar::<Fr>::new_constant(cs.clone(), ccs.clone()).unwrap();
|
||||
let zVar = Vec::<FpVar<Fr>>::new_input(cs.clone(), || Ok(z)).unwrap();
|
||||
let r_xVar = Vec::<FpVar<Fr>>::new_input(cs.clone(), || Ok(lcccs.r_x)).unwrap();
|
||||
let vVar = Vec::<FpVar<Fr>>::new_input(cs.clone(), || Ok(lcccs.v)).unwrap();
|
||||
let w_var = WitnessVar::new_witness(cs.clone(), || Ok(w)).unwrap();
|
||||
let lcccs_var = LCCCSVar::new_input(cs.clone(), || Ok(lcccs)).unwrap();
|
||||
|
||||
LCCCSCheckerGadget::check(ccs.s, ccs_mat, zVar, r_xVar, vVar).unwrap();
|
||||
ccs_mat.enforce_relation(&w_var, &lcccs_var).unwrap();
|
||||
|
||||
assert!(cs.is_satisfied().unwrap());
|
||||
}
|
||||
@@ -588,15 +318,8 @@ pub mod tests {
|
||||
HN::verify(hn_params.1, ivc_proof).unwrap();
|
||||
|
||||
// load the DeciderEthCircuit from the generated Nova instance
|
||||
let decider_circuit = DeciderEthCircuit::<
|
||||
Projective,
|
||||
GVar,
|
||||
Projective2,
|
||||
GVar2,
|
||||
Pedersen<Projective>,
|
||||
Pedersen<Projective2>,
|
||||
>::from_hypernova(hypernova)
|
||||
.unwrap();
|
||||
let decider_circuit =
|
||||
DeciderEthCircuit::<Projective, Projective2, GVar2>::try_from(hypernova).unwrap();
|
||||
|
||||
let cs = ConstraintSystem::<Fr>::new_ref();
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ use crate::arith::ccs::CCS;
|
||||
use crate::arith::Arith;
|
||||
use crate::commitment::CommitmentScheme;
|
||||
use crate::folding::circuits::CF1;
|
||||
use crate::folding::traits::Inputize;
|
||||
use crate::folding::traits::{CommittedInstanceOps, Dummy};
|
||||
use crate::transcript::AbsorbNonNative;
|
||||
use crate::utils::mle::dense_vec_to_dense_mle;
|
||||
@@ -72,7 +73,7 @@ impl<F: PrimeField> CCS<F> {
|
||||
Ok((
|
||||
LCCCS::<C> {
|
||||
C,
|
||||
u: C::ScalarField::one(),
|
||||
u: z[0],
|
||||
x: z[1..(1 + self.l)].to_vec(),
|
||||
r_x,
|
||||
v,
|
||||
@@ -154,6 +155,19 @@ impl<C: CurveGroup> CommittedInstanceOps<C> for LCCCS<C> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: CurveGroup> Inputize<C::ScalarField, LCCCSVar<C>> for LCCCS<C> {
|
||||
fn inputize(&self) -> Vec<C::ScalarField> {
|
||||
[
|
||||
&self.C.inputize(),
|
||||
&[self.u][..],
|
||||
&self.x,
|
||||
&self.r_x,
|
||||
&self.v,
|
||||
]
|
||||
.concat()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use ark_pallas::{Fr, Projective};
|
||||
|
||||
@@ -5,7 +5,7 @@ use ark_crypto_primitives::sponge::{
|
||||
};
|
||||
use ark_ec::{CurveGroup, Group};
|
||||
use ark_ff::{BigInteger, PrimeField};
|
||||
use ark_r1cs_std::{groups::GroupOpsBounds, prelude::CurveVar, ToConstraintFieldGadget};
|
||||
use ark_r1cs_std::{prelude::CurveVar, ToConstraintFieldGadget};
|
||||
use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, Compress, SerializationError};
|
||||
use ark_std::{fmt::Debug, marker::PhantomData, rand::RngCore, One, Zero};
|
||||
|
||||
@@ -293,8 +293,6 @@ where
|
||||
<C1 as Group>::ScalarField: Absorb,
|
||||
<C2 as Group>::ScalarField: Absorb,
|
||||
C1: CurveGroup<BaseField = C2::ScalarField, ScalarField = C2::BaseField>,
|
||||
for<'a> &'a GC1: GroupOpsBounds<'a, C1, GC1>,
|
||||
for<'a> &'a GC2: GroupOpsBounds<'a, C2, GC2>,
|
||||
{
|
||||
type RunningInstance = (LCCCS<C1>, Witness<C1::ScalarField>);
|
||||
type IncomingInstance = (CCCS<C1>, Witness<C1::ScalarField>);
|
||||
@@ -360,8 +358,6 @@ where
|
||||
<C1 as Group>::ScalarField: Absorb,
|
||||
<C2 as Group>::ScalarField: Absorb,
|
||||
C1: CurveGroup<BaseField = C2::ScalarField, ScalarField = C2::BaseField>,
|
||||
for<'a> &'a GC1: GroupOpsBounds<'a, C1, GC1>,
|
||||
for<'a> &'a GC2: GroupOpsBounds<'a, C2, GC2>,
|
||||
{
|
||||
/// internal helper for new_running_instance & new_incoming_instance methods, returns the R1CS
|
||||
/// z=[u,x,w] vector to be used to create the LCCCS & CCCS fresh instances.
|
||||
@@ -467,8 +463,6 @@ where
|
||||
<C1 as Group>::ScalarField: Absorb,
|
||||
<C2 as Group>::ScalarField: Absorb,
|
||||
C1: CurveGroup<BaseField = C2::ScalarField, ScalarField = C2::BaseField>,
|
||||
for<'a> &'a GC1: GroupOpsBounds<'a, C1, GC1>,
|
||||
for<'a> &'a GC2: GroupOpsBounds<'a, C2, GC2>,
|
||||
{
|
||||
/// Reuse Nova's PreprocessorParam.
|
||||
type PreprocessorParam = PreprocessorParam<C1, C2, FC, CS1, CS2, H>;
|
||||
@@ -567,23 +561,14 @@ where
|
||||
let cf_r1cs = get_r1cs_from_cs::<C2::ScalarField>(cf_circuit)?;
|
||||
|
||||
// if cs params exist, use them, if not, generate new ones
|
||||
let cs_pp: CS1::ProverParams;
|
||||
let cs_vp: CS1::VerifierParams;
|
||||
let cf_cs_pp: CS2::ProverParams;
|
||||
let cf_cs_vp: CS2::VerifierParams;
|
||||
if prep_param.cs_pp.is_some()
|
||||
&& prep_param.cf_cs_pp.is_some()
|
||||
&& prep_param.cs_vp.is_some()
|
||||
&& prep_param.cf_cs_vp.is_some()
|
||||
{
|
||||
cs_pp = prep_param.clone().cs_pp.unwrap();
|
||||
cs_vp = prep_param.clone().cs_vp.unwrap();
|
||||
cf_cs_pp = prep_param.clone().cf_cs_pp.unwrap();
|
||||
cf_cs_vp = prep_param.clone().cf_cs_vp.unwrap();
|
||||
} else {
|
||||
(cs_pp, cs_vp) = CS1::setup(&mut rng, ccs.n - ccs.l - 1)?;
|
||||
(cf_cs_pp, cf_cs_vp) = CS2::setup(&mut rng, cf_r1cs.A.n_cols - cf_r1cs.l - 1)?;
|
||||
}
|
||||
let (cs_pp, cs_vp) = match (&prep_param.cs_pp, &prep_param.cs_vp) {
|
||||
(Some(cs_pp), Some(cs_vp)) => (cs_pp.clone(), cs_vp.clone()),
|
||||
_ => CS1::setup(&mut rng, ccs.n - ccs.l - 1)?,
|
||||
};
|
||||
let (cf_cs_pp, cf_cs_vp) = match (&prep_param.cf_cs_pp, &prep_param.cf_cs_vp) {
|
||||
(Some(cf_cs_pp), Some(cf_cs_vp)) => (cf_cs_pp.clone(), cf_cs_vp.clone()),
|
||||
_ => CS2::setup(&mut rng, cf_r1cs.A.n_cols - cf_r1cs.l - 1)?,
|
||||
};
|
||||
|
||||
let pp = ProverParams::<C1, C2, CS1, CS2, H> {
|
||||
poseidon_config: prep_param.poseidon_config.clone(),
|
||||
@@ -726,9 +711,9 @@ where
|
||||
let (Us, Ws): (Vec<LCCCS<C1>>, Vec<Witness<C1::ScalarField>>) =
|
||||
lcccs.into_iter().unzip();
|
||||
let (us, ws): (Vec<CCCS<C1>>, Vec<Witness<C1::ScalarField>>) = cccs.into_iter().unzip();
|
||||
(Some(Us), Some(Ws), Some(us), Some(ws))
|
||||
(Us, Ws, us, ws)
|
||||
} else {
|
||||
(None, None, None, None)
|
||||
(vec![], vec![], vec![], vec![])
|
||||
};
|
||||
|
||||
let augmented_f_circuit: AugmentedFCircuit<C1, C2, GC2, FC, MU, NU>;
|
||||
@@ -807,9 +792,9 @@ where
|
||||
z_i: Some(self.z_i.clone()),
|
||||
external_inputs: Some(external_inputs.clone()),
|
||||
U_i: Some(self.U_i.clone()),
|
||||
Us: Us.clone(),
|
||||
Us: Some(Us),
|
||||
u_i_C: Some(self.u_i.C),
|
||||
us: us.clone(),
|
||||
us: Some(us),
|
||||
U_i1_C: Some(U_i1.C),
|
||||
F: self.F.clone(),
|
||||
x: Some(u_i1_x),
|
||||
@@ -826,21 +811,12 @@ where
|
||||
PoseidonSponge::<C1::ScalarField>::new(&self.poseidon_config);
|
||||
transcript_p.absorb(&self.pp_hash);
|
||||
|
||||
let (all_Us, all_us, all_Ws, all_ws) = if MU > 1 || NU > 1 {
|
||||
(
|
||||
[vec![self.U_i.clone()], Us.clone().unwrap()].concat(),
|
||||
[vec![self.u_i.clone()], us.clone().unwrap()].concat(),
|
||||
[vec![self.W_i.clone()], Ws.unwrap()].concat(),
|
||||
[vec![self.w_i.clone()], ws.unwrap()].concat(),
|
||||
)
|
||||
} else {
|
||||
(
|
||||
vec![self.U_i.clone()],
|
||||
vec![self.u_i.clone()],
|
||||
vec![self.W_i.clone()],
|
||||
vec![self.w_i.clone()],
|
||||
)
|
||||
};
|
||||
let (all_Us, all_us, all_Ws, all_ws) = (
|
||||
[&[self.U_i.clone()][..], &Us].concat(),
|
||||
[&[self.u_i.clone()][..], &us].concat(),
|
||||
[vec![self.W_i.clone()], Ws].concat(),
|
||||
[vec![self.w_i.clone()], ws].concat(),
|
||||
);
|
||||
|
||||
let (rho, nimfs_proof);
|
||||
(nimfs_proof, U_i1, W_i1, rho) = NIMFS::<C1, PoseidonSponge<C1::ScalarField>>::prove(
|
||||
@@ -865,7 +841,9 @@ where
|
||||
);
|
||||
|
||||
let rho_bits = rho.into_bigint().to_bits_le()[..NOVA_N_BITS_RO].to_vec();
|
||||
let rho_Fq = C1::BaseField::from_bigint(BigInteger::from_bits_le(&rho_bits)).unwrap();
|
||||
let rho_Fq = C1::BaseField::from(<C1::BaseField as PrimeField>::BigInt::from_bits_le(
|
||||
&rho_bits,
|
||||
));
|
||||
|
||||
// CycleFold part:
|
||||
// get the vector used as public inputs 'x' in the CycleFold circuit.
|
||||
@@ -935,9 +913,9 @@ where
|
||||
z_i: Some(self.z_i.clone()),
|
||||
external_inputs: Some(external_inputs),
|
||||
U_i: Some(self.U_i.clone()),
|
||||
Us: Us.clone(),
|
||||
Us: Some(Us),
|
||||
u_i_C: Some(self.u_i.C),
|
||||
us: us.clone(),
|
||||
us: Some(us),
|
||||
U_i1_C: Some(U_i1.C),
|
||||
F: self.F.clone(),
|
||||
x: Some(u_i1_x),
|
||||
@@ -1030,7 +1008,7 @@ where
|
||||
} = ivc_proof;
|
||||
let (pp, vp) = params;
|
||||
|
||||
let f_circuit = FC::new(fcircuit_params).unwrap();
|
||||
let f_circuit = FC::new(fcircuit_params)?;
|
||||
let augmented_f_circuit = AugmentedFCircuit::<C1, C2, GC2, FC, MU, NU>::empty(
|
||||
&pp.poseidon_config,
|
||||
f_circuit.clone(),
|
||||
|
||||
@@ -31,28 +31,34 @@ pub struct NIMFSProof<C: CurveGroup> {
|
||||
pub sigmas_thetas: SigmasThetas<C::ScalarField>,
|
||||
}
|
||||
|
||||
impl<C: CurveGroup> Dummy<(&CCS<CF1<C>>, usize, usize)> for NIMFSProof<C> {
|
||||
fn dummy((ccs, mu, nu): (&CCS<CF1<C>>, usize, usize)) -> Self {
|
||||
impl<C: CurveGroup> Dummy<(usize, usize, usize, usize)> for NIMFSProof<C> {
|
||||
fn dummy((s, t, mu, nu): (usize, usize, usize, usize)) -> Self {
|
||||
// use 'C::ScalarField::one()' instead of 'zero()' to enforce the NIMFSProof to have the
|
||||
// same in-circuit representation to match the number of constraints of an actual proof.
|
||||
NIMFSProof::<C> {
|
||||
sc_proof: SumCheckProof::<C::ScalarField> {
|
||||
point: vec![C::ScalarField::one(); ccs.s],
|
||||
point: vec![C::ScalarField::one(); s],
|
||||
proofs: vec![
|
||||
IOPProverMessage {
|
||||
coeffs: vec![C::ScalarField::one(); ccs.t + 1]
|
||||
coeffs: vec![C::ScalarField::one(); t + 1]
|
||||
};
|
||||
ccs.s
|
||||
s
|
||||
],
|
||||
},
|
||||
sigmas_thetas: SigmasThetas(
|
||||
vec![vec![C::ScalarField::one(); ccs.t]; mu],
|
||||
vec![vec![C::ScalarField::one(); ccs.t]; nu],
|
||||
vec![vec![C::ScalarField::one(); t]; mu],
|
||||
vec![vec![C::ScalarField::one(); t]; nu],
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: CurveGroup> Dummy<(&CCS<CF1<C>>, usize, usize)> for NIMFSProof<C> {
|
||||
fn dummy((ccs, mu, nu): (&CCS<CF1<C>>, usize, usize)) -> Self {
|
||||
NIMFSProof::dummy((ccs.s, ccs.t, mu, nu))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct SigmasThetas<F: PrimeField>(pub Vec<Vec<F>>, pub Vec<Vec<F>>);
|
||||
|
||||
@@ -67,7 +73,6 @@ pub struct NIMFS<C: CurveGroup, T: Transcript<C::ScalarField>> {
|
||||
impl<C: CurveGroup, T: Transcript<C::ScalarField>> NIMFS<C, T>
|
||||
where
|
||||
<C as Group>::ScalarField: Absorb,
|
||||
C::BaseField: PrimeField,
|
||||
{
|
||||
pub fn fold(
|
||||
lcccs: &[LCCCS<C>],
|
||||
@@ -258,8 +263,9 @@ where
|
||||
let rho_scalar = C::ScalarField::from_le_bytes_mod_order(b"rho");
|
||||
transcript.absorb(&rho_scalar);
|
||||
let rho_bits: Vec<bool> = transcript.get_challenge_nbits(NOVA_N_BITS_RO);
|
||||
let rho: C::ScalarField =
|
||||
C::ScalarField::from_bigint(BigInteger::from_bits_le(&rho_bits)).unwrap();
|
||||
let rho: C::ScalarField = C::ScalarField::from(
|
||||
<C::ScalarField as PrimeField>::BigInt::from_bits_le(&rho_bits),
|
||||
);
|
||||
|
||||
// Step 7: Create the folded instance
|
||||
let folded_lcccs = Self::fold(
|
||||
@@ -377,8 +383,9 @@ where
|
||||
let rho_scalar = C::ScalarField::from_le_bytes_mod_order(b"rho");
|
||||
transcript.absorb(&rho_scalar);
|
||||
let rho_bits: Vec<bool> = transcript.get_challenge_nbits(NOVA_N_BITS_RO);
|
||||
let rho: C::ScalarField =
|
||||
C::ScalarField::from_bigint(BigInteger::from_bits_le(&rho_bits)).unwrap();
|
||||
let rho: C::ScalarField = C::ScalarField::from(
|
||||
<C::ScalarField as PrimeField>::BigInt::from_bits_le(&rho_bits),
|
||||
);
|
||||
|
||||
// Step 7: Compute the folded instance
|
||||
Ok(Self::fold(
|
||||
|
||||
@@ -11,7 +11,6 @@ use ark_r1cs_std::{
|
||||
boolean::Boolean,
|
||||
eq::EqGadget,
|
||||
fields::{fp::FpVar, FieldVar},
|
||||
groups::GroupOpsBounds,
|
||||
prelude::CurveVar,
|
||||
uint8::UInt8,
|
||||
R1CSVar, ToConstraintFieldGadget,
|
||||
@@ -73,11 +72,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<C> AbsorbGadget<C::ScalarField> for CommittedInstanceVar<C>
|
||||
where
|
||||
C: CurveGroup,
|
||||
<C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField,
|
||||
{
|
||||
impl<C: CurveGroup> AbsorbGadget<C::ScalarField> for CommittedInstanceVar<C> {
|
||||
fn to_sponge_bytes(&self) -> Result<Vec<UInt8<C::ScalarField>>, SynthesisError> {
|
||||
FpVar::batch_to_sponge_bytes(&self.to_sponge_field_elements()?)
|
||||
}
|
||||
@@ -124,11 +119,7 @@ pub struct NIFSGadget<C: CurveGroup> {
|
||||
_c: PhantomData<C>,
|
||||
}
|
||||
|
||||
impl<C: CurveGroup> NIFSGadget<C>
|
||||
where
|
||||
C: CurveGroup,
|
||||
<C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField,
|
||||
{
|
||||
impl<C: CurveGroup> NIFSGadget<C> {
|
||||
pub fn fold_committed_instance(
|
||||
r: FpVar<CF1<C>>,
|
||||
ci1: CommittedInstanceVar<C>, // U_i
|
||||
@@ -174,8 +165,6 @@ pub struct ChallengeGadget<C: CurveGroup, CI: Absorb> {
|
||||
}
|
||||
impl<C: CurveGroup, CI: Absorb> ChallengeGadget<C, CI>
|
||||
where
|
||||
C: CurveGroup,
|
||||
<C as CurveGroup>::BaseField: PrimeField,
|
||||
<C as Group>::ScalarField: Absorb,
|
||||
{
|
||||
pub fn get_challenge_native<T: Transcript<C::ScalarField>>(
|
||||
@@ -232,9 +221,7 @@ pub struct AugmentedFCircuit<
|
||||
C2: CurveGroup,
|
||||
GC2: CurveVar<C2, CF2<C2>>,
|
||||
FC: FCircuit<CF1<C1>>,
|
||||
> where
|
||||
for<'a> &'a GC2: GroupOpsBounds<'a, C2, GC2>,
|
||||
{
|
||||
> {
|
||||
pub(super) _gc2: PhantomData<GC2>,
|
||||
pub(super) poseidon_config: PoseidonConfig<CF1<C1>>,
|
||||
pub(super) pp_hash: Option<CF1<C1>>,
|
||||
@@ -264,8 +251,6 @@ pub struct AugmentedFCircuit<
|
||||
|
||||
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) -> Self {
|
||||
Self {
|
||||
@@ -306,7 +291,6 @@ where
|
||||
<C1 as Group>::ScalarField: Absorb,
|
||||
<C2 as Group>::ScalarField: Absorb,
|
||||
C1: CurveGroup<BaseField = C2::ScalarField, ScalarField = C2::BaseField>,
|
||||
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(), || {
|
||||
@@ -508,7 +492,7 @@ where
|
||||
// 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::new_constant(cs.clone(), cf_u_dummy)?
|
||||
CycleFoldCommittedInstanceVar::<C2, GC2>::new_constant(cs.clone(), cf_u_dummy)?
|
||||
.hash(&sponge, pp_hash)?;
|
||||
let cf_x = FpVar::new_input(cs.clone(), || {
|
||||
Ok(self.cf_x.unwrap_or(cf_u_i1_x_base.value()?))
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
/// DeciderEth from decider_eth.rs file.
|
||||
/// More details can be found at the documentation page:
|
||||
/// https://privacy-scaling-explorations.github.io/sonobe-docs/design/nova-decider-offchain.html
|
||||
use ark_crypto_primitives::sponge::{poseidon::PoseidonSponge, Absorb, CryptographicSponge};
|
||||
use ark_ec::{AffineRepr, CurveGroup, Group};
|
||||
use ark_crypto_primitives::sponge::Absorb;
|
||||
use ark_ec::{CurveGroup, Group};
|
||||
use ark_ff::{BigInteger, PrimeField};
|
||||
use ark_r1cs_std::{groups::GroupOpsBounds, prelude::CurveVar, ToConstraintFieldGadget};
|
||||
use ark_serialize::{CanonicalDeserialize, CanonicalSerialize};
|
||||
@@ -13,18 +13,16 @@ use ark_std::{One, Zero};
|
||||
use core::marker::PhantomData;
|
||||
|
||||
use super::decider_circuits::{DeciderCircuit1, DeciderCircuit2};
|
||||
use super::{
|
||||
nifs::{nova::NIFS, NIFSTrait},
|
||||
CommittedInstance, Nova,
|
||||
};
|
||||
use super::decider_eth_circuit::DeciderNovaGadget;
|
||||
use super::Nova;
|
||||
use crate::commitment::CommitmentScheme;
|
||||
use crate::folding::circuits::decider::DeciderEnabledNIFS;
|
||||
use crate::folding::circuits::{
|
||||
cyclefold::CycleFoldCommittedInstance,
|
||||
nonnative::{affine::NonNativeAffineVar, uint::NonNativeUintVar},
|
||||
cyclefold::{CycleFoldCommittedInstance, CycleFoldCommittedInstanceVar},
|
||||
CF2,
|
||||
};
|
||||
use crate::folding::traits::{CommittedInstanceOps, Inputize, WitnessOps};
|
||||
use crate::frontend::FCircuit;
|
||||
use crate::transcript::poseidon::poseidon_canonical_config;
|
||||
use crate::Error;
|
||||
use crate::{Decider as DeciderTrait, FoldingScheme};
|
||||
|
||||
@@ -45,8 +43,9 @@ where
|
||||
// cmT and r are values for the last fold, U_{i+1}=NIFS.V(r, U_i, u_i, cmT), and they are
|
||||
// checked in-circuit
|
||||
cmT: C1,
|
||||
r: C1::ScalarField,
|
||||
// cyclefold committed instance
|
||||
cf_U_i: CycleFoldCommittedInstance<C2>,
|
||||
cf_U_final: CycleFoldCommittedInstance<C2>,
|
||||
// the CS challenges are provided by the prover, but in-circuit they are checked to match the
|
||||
// in-circuit computed computed ones.
|
||||
cs1_challenges: [C1::ScalarField; 2],
|
||||
@@ -147,22 +146,21 @@ where
|
||||
S2::VerifyingKey,
|
||||
>;
|
||||
type PublicInput = Vec<C1::ScalarField>;
|
||||
type CommittedInstance = CommittedInstance<C1>;
|
||||
type CommittedInstance = Vec<C1>;
|
||||
|
||||
fn preprocess(
|
||||
mut rng: impl RngCore + CryptoRng,
|
||||
prep_param: Self::PreprocessorParam,
|
||||
fs: FS,
|
||||
) -> Result<(Self::ProverParam, Self::VerifierParam), Error> {
|
||||
let circuit1 = DeciderCircuit1::<C1, C2, GC2>::from_nova::<GC1, CS1, CS2, false, FC>(
|
||||
fs.clone().into(),
|
||||
)?;
|
||||
let circuit2 =
|
||||
DeciderCircuit2::<C1, GC1, C2>::from_nova::<GC2, CS1, CS2, false, FC>(fs.into())?;
|
||||
let circuit1 = DeciderCircuit1::<C1, C2, GC2>::try_from(Nova::from(fs.clone()))?;
|
||||
let circuit2 = DeciderCircuit2::<C2>::try_from(Nova::from(fs))?;
|
||||
|
||||
// get the Groth16 specific setup for the circuits
|
||||
let (c1_g16_pk, c1_g16_vk) = S1::circuit_specific_setup(circuit1, &mut rng).unwrap();
|
||||
let (c2_g16_pk, c2_g16_vk) = S2::circuit_specific_setup(circuit2, &mut rng).unwrap();
|
||||
let (c1_g16_pk, c1_g16_vk) = S1::circuit_specific_setup(circuit1, &mut rng)
|
||||
.map_err(|e| Error::SNARKSetupFail(e.to_string()))?;
|
||||
let (c2_g16_pk, c2_g16_vk) = S2::circuit_specific_setup(circuit2, &mut rng)
|
||||
.map_err(|e| Error::SNARKSetupFail(e.to_string()))?;
|
||||
|
||||
// get the FoldingScheme prover & verifier params from Nova
|
||||
#[allow(clippy::type_complexity)]
|
||||
@@ -200,76 +198,57 @@ where
|
||||
pp: Self::ProverParam,
|
||||
fs: FS,
|
||||
) -> Result<Self::Proof, Error> {
|
||||
let circuit1 = DeciderCircuit1::<C1, C2, GC2>::from_nova::<GC1, CS1, CS2, false, FC>(
|
||||
fs.clone().into(),
|
||||
)?;
|
||||
let circuit2 =
|
||||
DeciderCircuit2::<C1, GC1, C2>::from_nova::<GC2, CS1, CS2, false, FC>(fs.into())?;
|
||||
let circuit1 = DeciderCircuit1::<C1, C2, GC2>::try_from(Nova::from(fs.clone()))?;
|
||||
let circuit2 = DeciderCircuit2::<C2>::try_from(Nova::from(fs))?;
|
||||
|
||||
let c1_snark_proof = S1::prove(&pp.c1_snark_pp, circuit1.clone(), &mut rng)
|
||||
let cmT = circuit1.proof;
|
||||
let r = circuit1.randomness;
|
||||
let cf_U_final = circuit1.cf_U_i.clone();
|
||||
|
||||
let c1_kzg_challenges = circuit1.kzg_challenges.clone();
|
||||
let c1_kzg_proofs = circuit1
|
||||
.W_i1
|
||||
.get_openings()
|
||||
.iter()
|
||||
.zip(&c1_kzg_challenges)
|
||||
.map(|((v, _), &c)| {
|
||||
CS1::prove_with_challenge(&pp.c1_cs_pp, c, v, &C1::ScalarField::zero(), None)
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
let c2_kzg_challenges = circuit2.kzg_challenges.clone();
|
||||
let c2_kzg_proofs = circuit2
|
||||
.cf_W_i
|
||||
.get_openings()
|
||||
.iter()
|
||||
.zip(&c2_kzg_challenges)
|
||||
.map(|((v, _), &c)| {
|
||||
CS2::prove_with_challenge(&pp.c2_cs_pp, c, v, &C2::ScalarField::zero(), None)
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
let c1_snark_proof = S1::prove(&pp.c1_snark_pp, circuit1, &mut rng)
|
||||
.map_err(|e| Error::Other(e.to_string()))?;
|
||||
let c2_snark_proof = S2::prove(&pp.c2_snark_pp, circuit2.clone(), &mut rng)
|
||||
let c2_snark_proof = S2::prove(&pp.c2_snark_pp, circuit2, &mut rng)
|
||||
.map_err(|e| Error::Other(e.to_string()))?;
|
||||
|
||||
let cmT = circuit1.cmT.unwrap();
|
||||
let W_i1 = circuit1.W_i1.unwrap();
|
||||
let cf_W_i = circuit2.cf_W_i.unwrap();
|
||||
|
||||
// get the challenges that have been already computed when preparing the circuits inputs in
|
||||
// the above `from_nova` calls
|
||||
let challenge_W = circuit1
|
||||
.cs_c_W
|
||||
.ok_or(Error::MissingValue("cs_c_W".to_string()))?;
|
||||
let challenge_E = circuit1
|
||||
.cs_c_E
|
||||
.ok_or(Error::MissingValue("cs_c_E".to_string()))?;
|
||||
let c2_challenge_W = circuit2
|
||||
.cs_c_W
|
||||
.ok_or(Error::MissingValue("c2's cs_c_W".to_string()))?;
|
||||
let c2_challenge_E = circuit2
|
||||
.cs_c_E
|
||||
.ok_or(Error::MissingValue("c2's cs_c_E".to_string()))?;
|
||||
|
||||
// generate CommitmentScheme proofs for the main instance
|
||||
let U_cmW_proof = CS1::prove_with_challenge(
|
||||
&pp.c1_cs_pp,
|
||||
challenge_W,
|
||||
&W_i1.W,
|
||||
&C1::ScalarField::zero(),
|
||||
None,
|
||||
)?;
|
||||
let U_cmE_proof = CS1::prove_with_challenge(
|
||||
&pp.c1_cs_pp,
|
||||
challenge_E,
|
||||
&W_i1.E,
|
||||
&C1::ScalarField::zero(),
|
||||
None,
|
||||
)?;
|
||||
// CS proofs for the CycleFold instance
|
||||
let cf_cmW_proof = CS2::prove_with_challenge(
|
||||
&pp.c2_cs_pp,
|
||||
c2_challenge_W,
|
||||
&cf_W_i.W,
|
||||
&C2::ScalarField::zero(),
|
||||
None,
|
||||
)?;
|
||||
let cf_cmE_proof = CS2::prove_with_challenge(
|
||||
&pp.c2_cs_pp,
|
||||
c2_challenge_E,
|
||||
&cf_W_i.E,
|
||||
&C2::ScalarField::zero(),
|
||||
None,
|
||||
)?;
|
||||
|
||||
Ok(Self::Proof {
|
||||
c1_snark_proof,
|
||||
c2_snark_proof,
|
||||
cs1_proofs: [U_cmW_proof, U_cmE_proof],
|
||||
cs2_proofs: [cf_cmW_proof, cf_cmE_proof],
|
||||
cs1_proofs: c1_kzg_proofs
|
||||
.try_into()
|
||||
.map_err(|e: Vec<_>| Error::NotExpectedLength(e.len(), 2))?,
|
||||
cs2_proofs: c2_kzg_proofs
|
||||
.try_into()
|
||||
.map_err(|e: Vec<_>| Error::NotExpectedLength(e.len(), 2))?,
|
||||
cmT,
|
||||
cf_U_i: circuit1.cf_U_i.unwrap(),
|
||||
cs1_challenges: [challenge_W, challenge_E],
|
||||
cs2_challenges: [c2_challenge_W, c2_challenge_E],
|
||||
r,
|
||||
cf_U_final,
|
||||
cs1_challenges: c1_kzg_challenges
|
||||
.try_into()
|
||||
.map_err(|e: Vec<_>| Error::NotExpectedLength(e.len(), 2))?,
|
||||
cs2_challenges: c2_kzg_challenges
|
||||
.try_into()
|
||||
.map_err(|e: Vec<_>| Error::NotExpectedLength(e.len(), 2))?,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -278,72 +257,38 @@ where
|
||||
i: C1::ScalarField,
|
||||
z_0: Vec<C1::ScalarField>,
|
||||
z_i: Vec<C1::ScalarField>,
|
||||
running_instance: &Self::CommittedInstance,
|
||||
incoming_instance: &Self::CommittedInstance,
|
||||
// we don't use the instances at the verifier level, since we check them in-circuit
|
||||
running_commitments: &Self::CommittedInstance,
|
||||
incoming_commitments: &Self::CommittedInstance,
|
||||
proof: &Self::Proof,
|
||||
) -> Result<bool, Error> {
|
||||
if i <= C1::ScalarField::one() {
|
||||
return Err(Error::NotEnoughSteps);
|
||||
}
|
||||
|
||||
// compute U = U_{d+1}= NIFS.V(U_d, u_d, cmT)
|
||||
let poseidon_config = poseidon_canonical_config::<C1::ScalarField>();
|
||||
let mut transcript = PoseidonSponge::<C1::ScalarField>::new(&poseidon_config);
|
||||
let (U, r_bits) = NIFS::<C1, CS1, PoseidonSponge<C1::ScalarField>>::verify(
|
||||
&mut transcript,
|
||||
vp.pp_hash,
|
||||
running_instance,
|
||||
incoming_instance,
|
||||
&proof.cmT,
|
||||
// 6.2. Fold the commitments
|
||||
let U_final_commitments = DeciderNovaGadget::fold_group_elements_native(
|
||||
running_commitments,
|
||||
incoming_commitments,
|
||||
Some(proof.cmT),
|
||||
proof.r,
|
||||
)?;
|
||||
let r = C1::ScalarField::from_bigint(BigInteger::from_bits_le(&r_bits))
|
||||
.ok_or(Error::OutOfBounds)?;
|
||||
|
||||
let (cmE_x, cmE_y) = NonNativeAffineVar::inputize(U.cmE)?;
|
||||
let (cmW_x, cmW_y) = NonNativeAffineVar::inputize(U.cmW)?;
|
||||
let (cmT_x, cmT_y) = NonNativeAffineVar::inputize(proof.cmT)?;
|
||||
|
||||
let zero = (&C2::BaseField::zero(), &C2::BaseField::zero());
|
||||
let cmE_affine = proof.cf_U_i.cmE.into_affine();
|
||||
let cmW_affine = proof.cf_U_i.cmW.into_affine();
|
||||
let (cf_cmE_x, cf_cmE_y) = cmE_affine.xy().unwrap_or(zero);
|
||||
let cf_cmE_z = C1::ScalarField::one();
|
||||
let (cf_cmW_x, cf_cmW_y) = cmW_affine.xy().unwrap_or(zero);
|
||||
let cf_cmW_z = C1::ScalarField::one();
|
||||
let cf_U = proof.cf_U_final.clone();
|
||||
|
||||
// snark proof 1
|
||||
let c1_public_input: Vec<C1::ScalarField> = [
|
||||
vec![vp.pp_hash, i],
|
||||
z_0,
|
||||
z_i,
|
||||
// U_{i+1} values:
|
||||
vec![U.u],
|
||||
U.x.clone(),
|
||||
cmE_x,
|
||||
cmE_y,
|
||||
cmW_x,
|
||||
cmW_y,
|
||||
// CS1 values:
|
||||
proof.cs1_challenges.to_vec(), // c_W, c_E
|
||||
vec![
|
||||
proof.cs1_proofs[0].eval, // eval_W
|
||||
proof.cs1_proofs[1].eval, // eval_E
|
||||
],
|
||||
// cf_U_i values
|
||||
NonNativeUintVar::<CF2<C2>>::inputize(proof.cf_U_i.u),
|
||||
proof
|
||||
.cf_U_i
|
||||
.x
|
||||
let c1_public_input = [
|
||||
&[vp.pp_hash, i][..],
|
||||
&z_0,
|
||||
&z_i,
|
||||
&U_final_commitments
|
||||
.iter()
|
||||
.flat_map(|&x_i| NonNativeUintVar::<CF2<C2>>::inputize(x_i))
|
||||
.collect::<Vec<C1::ScalarField>>(),
|
||||
vec![
|
||||
*cf_cmE_x, *cf_cmE_y, cf_cmE_z, *cf_cmW_x, *cf_cmW_y, cf_cmW_z,
|
||||
],
|
||||
// NIFS values:
|
||||
cmT_x,
|
||||
cmT_y,
|
||||
vec![r],
|
||||
.flat_map(|c| c.inputize())
|
||||
.collect::<Vec<_>>(),
|
||||
&Inputize::<CF2<C2>, CycleFoldCommittedInstanceVar<C2, GC2>>::inputize(&cf_U),
|
||||
&proof.cs1_challenges,
|
||||
&proof.cs1_proofs.iter().map(|p| p.eval).collect::<Vec<_>>(),
|
||||
&proof.cmT.inputize(),
|
||||
&[proof.r],
|
||||
]
|
||||
.concat();
|
||||
|
||||
@@ -353,26 +298,15 @@ where
|
||||
return Err(Error::SNARKVerificationFail);
|
||||
}
|
||||
|
||||
let (cf2_cmE_x, cf2_cmE_y) = NonNativeAffineVar::inputize(proof.cf_U_i.cmE)?;
|
||||
let (cf2_cmW_x, cf2_cmW_y) = NonNativeAffineVar::inputize(proof.cf_U_i.cmW)?;
|
||||
|
||||
// snark proof 2
|
||||
// migrate pp_hash from C1::Fr to C1::Fq
|
||||
let pp_hash_Fq =
|
||||
C2::ScalarField::from_le_bytes_mod_order(&vp.pp_hash.into_bigint().to_bytes_le());
|
||||
let c2_public_input: Vec<C2::ScalarField> = [
|
||||
vec![pp_hash_Fq],
|
||||
vec![proof.cf_U_i.u],
|
||||
proof.cf_U_i.x.clone(),
|
||||
cf2_cmE_x,
|
||||
cf2_cmE_y,
|
||||
cf2_cmW_x,
|
||||
cf2_cmW_y,
|
||||
proof.cs2_challenges.to_vec(),
|
||||
vec![
|
||||
proof.cs2_proofs[0].eval, // eval_W
|
||||
proof.cs2_proofs[1].eval, // eval_E
|
||||
],
|
||||
&[pp_hash_Fq][..],
|
||||
&cf_U.inputize(),
|
||||
&proof.cs2_challenges,
|
||||
&proof.cs2_proofs.iter().map(|p| p.eval).collect::<Vec<_>>(),
|
||||
]
|
||||
.concat();
|
||||
|
||||
@@ -382,33 +316,24 @@ where
|
||||
return Err(Error::SNARKVerificationFail);
|
||||
}
|
||||
|
||||
// check C1 commitments (main instance commitments)
|
||||
CS1::verify_with_challenge(
|
||||
&vp.c1_cs_vp,
|
||||
proof.cs1_challenges[0],
|
||||
&U.cmW,
|
||||
&proof.cs1_proofs[0],
|
||||
)?;
|
||||
CS1::verify_with_challenge(
|
||||
&vp.c1_cs_vp,
|
||||
proof.cs1_challenges[1],
|
||||
&U.cmE,
|
||||
&proof.cs1_proofs[1],
|
||||
)?;
|
||||
// 7.3. check C1 commitments (main instance commitments)
|
||||
for ((cm, &c), pi) in U_final_commitments
|
||||
.iter()
|
||||
.zip(&proof.cs1_challenges)
|
||||
.zip(&proof.cs1_proofs)
|
||||
{
|
||||
CS1::verify_with_challenge(&vp.c1_cs_vp, c, cm, pi)?;
|
||||
}
|
||||
|
||||
// check C2 commitments (CycleFold instance commitments)
|
||||
CS2::verify_with_challenge(
|
||||
&vp.c2_cs_vp,
|
||||
proof.cs2_challenges[0],
|
||||
&proof.cf_U_i.cmW,
|
||||
&proof.cs2_proofs[0],
|
||||
)?;
|
||||
CS2::verify_with_challenge(
|
||||
&vp.c2_cs_vp,
|
||||
proof.cs2_challenges[1],
|
||||
&proof.cf_U_i.cmE,
|
||||
&proof.cs2_proofs[1],
|
||||
)?;
|
||||
// 4.3. check C2 commitments (CycleFold instance commitments)
|
||||
for ((cm, &c), pi) in cf_U
|
||||
.get_commitments()
|
||||
.iter()
|
||||
.zip(&proof.cs2_challenges)
|
||||
.zip(&proof.cs2_proofs)
|
||||
{
|
||||
CS2::verify_with_challenge(&vp.c2_cs_vp, c, cm, pi)?;
|
||||
}
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
@@ -494,7 +419,13 @@ pub mod tests {
|
||||
// decider proof verification
|
||||
let start = Instant::now();
|
||||
let verified = D::verify(
|
||||
decider_vp, nova.i, nova.z_0, nova.z_i, &nova.U_i, &nova.u_i, &proof,
|
||||
decider_vp,
|
||||
nova.i,
|
||||
nova.z_0,
|
||||
nova.z_i,
|
||||
&nova.U_i.get_commitments(),
|
||||
&nova.u_i.get_commitments(),
|
||||
&proof,
|
||||
)
|
||||
.unwrap();
|
||||
assert!(verified);
|
||||
|
||||
@@ -2,116 +2,63 @@
|
||||
/// DeciderEthCircuit.
|
||||
/// More details can be found at the documentation page:
|
||||
/// https://privacy-scaling-explorations.github.io/sonobe-docs/design/nova-decider-offchain.html
|
||||
use ark_crypto_primitives::sponge::{
|
||||
constraints::CryptographicSpongeVar,
|
||||
poseidon::{constraints::PoseidonSpongeVar, PoseidonConfig, PoseidonSponge},
|
||||
Absorb, CryptographicSponge,
|
||||
};
|
||||
use ark_ec::{CurveGroup, Group};
|
||||
use ark_crypto_primitives::sponge::{poseidon::PoseidonSponge, Absorb, CryptographicSponge};
|
||||
use ark_ec::CurveGroup;
|
||||
use ark_ff::{BigInteger, PrimeField};
|
||||
use ark_poly::Polynomial;
|
||||
use ark_r1cs_std::{
|
||||
alloc::AllocVar,
|
||||
boolean::Boolean,
|
||||
eq::EqGadget,
|
||||
fields::{fp::FpVar, FieldVar},
|
||||
groups::GroupOpsBounds,
|
||||
prelude::CurveVar,
|
||||
ToConstraintFieldGadget,
|
||||
};
|
||||
use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystemRef, SynthesisError};
|
||||
use ark_std::Zero;
|
||||
use ark_r1cs_std::{fields::fp::FpVar, prelude::CurveVar, ToConstraintFieldGadget};
|
||||
use core::marker::PhantomData;
|
||||
|
||||
use super::{
|
||||
circuits::{ChallengeGadget, CommittedInstanceVar},
|
||||
decider_eth_circuit::{
|
||||
evaluate_gadget, KZGChallengesGadget, R1CSVar, RelaxedR1CSGadget, WitnessVar,
|
||||
},
|
||||
decider_eth_circuit::DeciderNovaGadget,
|
||||
nifs::{nova::NIFS, NIFSTrait},
|
||||
CommittedInstance, Nova, Witness,
|
||||
};
|
||||
use crate::arith::r1cs::R1CS;
|
||||
use crate::commitment::CommitmentScheme;
|
||||
use crate::folding::circuits::{
|
||||
cyclefold::{
|
||||
CycleFoldCommittedInstance, CycleFoldCommittedInstanceVar, CycleFoldConfig,
|
||||
CycleFoldWitness,
|
||||
},
|
||||
nonnative::{affine::NonNativeAffineVar, uint::NonNativeUintVar},
|
||||
CF1, CF2,
|
||||
use crate::folding::{
|
||||
circuits::{CF1, CF2},
|
||||
traits::WitnessOps,
|
||||
};
|
||||
use crate::folding::nova::NovaCycleFoldConfig;
|
||||
use crate::folding::traits::{CommittedInstanceVarOps, Dummy};
|
||||
use crate::frontend::FCircuit;
|
||||
use crate::utils::vec::poly_from_vec;
|
||||
use crate::Error;
|
||||
use crate::{
|
||||
arith::r1cs::{circuits::R1CSMatricesVar, R1CS},
|
||||
folding::circuits::decider::{
|
||||
off_chain::{GenericOffchainDeciderCircuit1, GenericOffchainDeciderCircuit2},
|
||||
EvalGadget, KZGChallengesGadget,
|
||||
},
|
||||
};
|
||||
use crate::{commitment::CommitmentScheme, transcript::poseidon::poseidon_canonical_config};
|
||||
|
||||
/// Circuit that implements part of the in-circuit checks needed for the offchain verification over
|
||||
/// the Curve2's BaseField (=Curve1's ScalarField).
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct DeciderCircuit1<C1, C2, GC2>
|
||||
where
|
||||
C1: CurveGroup,
|
||||
C2: CurveGroup,
|
||||
GC2: CurveVar<C2, CF2<C2>>,
|
||||
{
|
||||
_c1: PhantomData<C1>,
|
||||
_c2: PhantomData<C2>,
|
||||
_gc2: PhantomData<GC2>,
|
||||
|
||||
/// E vector's length of the Nova instance witness
|
||||
pub E_len: usize,
|
||||
/// E vector's length of the CycleFold instance witness
|
||||
pub cf_E_len: usize,
|
||||
/// R1CS of the Augmented Function circuit
|
||||
pub r1cs: R1CS<C1::ScalarField>,
|
||||
pub poseidon_config: PoseidonConfig<CF1<C1>>,
|
||||
/// public params hash
|
||||
pub pp_hash: Option<C1::ScalarField>,
|
||||
pub i: Option<CF1<C1>>,
|
||||
/// initial state
|
||||
pub z_0: Option<Vec<C1::ScalarField>>,
|
||||
/// current i-th state
|
||||
pub z_i: Option<Vec<C1::ScalarField>>,
|
||||
/// Nova instances
|
||||
pub u_i: Option<CommittedInstance<C1>>,
|
||||
pub w_i: Option<Witness<C1>>,
|
||||
pub U_i: Option<CommittedInstance<C1>>,
|
||||
pub W_i: Option<Witness<C1>>,
|
||||
pub U_i1: Option<CommittedInstance<C1>>,
|
||||
pub W_i1: Option<Witness<C1>>,
|
||||
pub cmT: Option<C1>,
|
||||
pub r: Option<C1::ScalarField>,
|
||||
/// CycleFold running instance
|
||||
pub cf_U_i: Option<CycleFoldCommittedInstance<C2>>,
|
||||
pub type DeciderCircuit1<C1, C2, GC2> = GenericOffchainDeciderCircuit1<
|
||||
C1,
|
||||
C2,
|
||||
GC2,
|
||||
CommittedInstance<C1>,
|
||||
CommittedInstance<C1>,
|
||||
Witness<C1>,
|
||||
R1CS<CF1<C1>>,
|
||||
R1CSMatricesVar<CF1<C1>, FpVar<CF1<C1>>>,
|
||||
DeciderNovaGadget,
|
||||
>;
|
||||
|
||||
/// Commitment Scheme challenges
|
||||
pub cs_c_W: Option<C1::ScalarField>,
|
||||
pub cs_c_E: Option<C1::ScalarField>,
|
||||
/// Evaluations of the committed polynomials at the challenge
|
||||
pub eval_W: Option<C1::ScalarField>,
|
||||
pub eval_E: Option<C1::ScalarField>,
|
||||
}
|
||||
impl<C1, C2, GC2> DeciderCircuit1<C1, C2, GC2>
|
||||
where
|
||||
C1: CurveGroup,
|
||||
<C1 as CurveGroup>::BaseField: PrimeField,
|
||||
<C1 as Group>::ScalarField: Absorb,
|
||||
C2: CurveGroup,
|
||||
GC2: CurveVar<C2, CF2<C2>> + ToConstraintFieldGadget<CF2<C2>>,
|
||||
for<'b> &'b GC2: GroupOpsBounds<'b, C2, GC2>,
|
||||
{
|
||||
pub fn from_nova<GC1, CS1, CS2, const H: bool, FC: FCircuit<C1::ScalarField>>(
|
||||
nova: Nova<C1, GC1, C2, GC2, FC, CS1, CS2, H>,
|
||||
) -> Result<Self, Error>
|
||||
where
|
||||
C2: CurveGroup,
|
||||
impl<
|
||||
C1: CurveGroup,
|
||||
GC1: CurveVar<C1, CF2<C1>> + ToConstraintFieldGadget<CF2<C1>>,
|
||||
C2: CurveGroup,
|
||||
GC2: CurveVar<C2, CF2<C2>> + ToConstraintFieldGadget<CF2<C2>>,
|
||||
FC: FCircuit<C1::ScalarField>,
|
||||
CS1: CommitmentScheme<C1, H>,
|
||||
CS2: CommitmentScheme<C2, H>,
|
||||
{
|
||||
const H: bool,
|
||||
> TryFrom<Nova<C1, GC1, C2, GC2, FC, CS1, CS2, H>> for DeciderCircuit1<C1, C2, GC2>
|
||||
where
|
||||
CF1<C1>: Absorb,
|
||||
{
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(nova: Nova<C1, GC1, C2, GC2, FC, CS1, CS2, H>) -> Result<Self, Error> {
|
||||
let mut transcript = PoseidonSponge::<C1::ScalarField>::new(&nova.poseidon_config);
|
||||
// pp_hash is absorbed to transcript at the NIFS::prove call
|
||||
|
||||
@@ -129,360 +76,98 @@ where
|
||||
let r_Fr = C1::ScalarField::from_bigint(BigInteger::from_bits_le(&r_bits))
|
||||
.ok_or(Error::OutOfBounds)?;
|
||||
|
||||
// compute the commitment scheme challenges used as inputs in the circuit
|
||||
let (cs_challenge_W, cs_challenge_E) =
|
||||
KZGChallengesGadget::<C1>::get_challenges_native(&mut transcript, U_i1.clone());
|
||||
// compute the KZG challenges used as inputs in the circuit
|
||||
let kzg_challenges = KZGChallengesGadget::get_challenges_native(&mut transcript, &U_i1);
|
||||
|
||||
// get evals of the committed polys at the challenges
|
||||
let mut W = W_i1.W.clone();
|
||||
W.extend(
|
||||
std::iter::repeat(C1::ScalarField::zero())
|
||||
.take(W_i1.W.len().next_power_of_two() - W_i1.W.len()),
|
||||
);
|
||||
let mut E = W_i1.E.clone();
|
||||
E.extend(
|
||||
std::iter::repeat(C1::ScalarField::zero())
|
||||
.take(W_i1.E.len().next_power_of_two() - W_i1.E.len()),
|
||||
);
|
||||
let p_W = poly_from_vec(W.to_vec())?;
|
||||
let eval_W = p_W.evaluate(&cs_challenge_W);
|
||||
let p_E = poly_from_vec(E.to_vec())?;
|
||||
let eval_E = p_E.evaluate(&cs_challenge_E);
|
||||
// get KZG evals
|
||||
let kzg_evaluations = W_i1
|
||||
.get_openings()
|
||||
.iter()
|
||||
.zip(&kzg_challenges)
|
||||
.map(|((v, _), &c)| EvalGadget::evaluate_native(v, c))
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
Ok(Self {
|
||||
_c1: PhantomData,
|
||||
_c2: PhantomData,
|
||||
_gc2: PhantomData,
|
||||
|
||||
E_len: nova.W_i.E.len(),
|
||||
cf_E_len: nova.cf_W_i.E.len(),
|
||||
r1cs: nova.r1cs,
|
||||
_avar: PhantomData,
|
||||
arith: nova.r1cs,
|
||||
poseidon_config: nova.poseidon_config,
|
||||
pp_hash: Some(nova.pp_hash),
|
||||
i: Some(nova.i),
|
||||
z_0: Some(nova.z_0),
|
||||
z_i: Some(nova.z_i),
|
||||
u_i: Some(nova.u_i),
|
||||
w_i: Some(nova.w_i),
|
||||
U_i: Some(nova.U_i),
|
||||
W_i: Some(nova.W_i),
|
||||
U_i1: Some(U_i1),
|
||||
W_i1: Some(W_i1),
|
||||
cmT: Some(cmT),
|
||||
r: Some(r_Fr),
|
||||
cf_U_i: Some(nova.cf_U_i),
|
||||
cs_c_W: Some(cs_challenge_W),
|
||||
cs_c_E: Some(cs_challenge_E),
|
||||
eval_W: Some(eval_W),
|
||||
eval_E: Some(eval_E),
|
||||
pp_hash: nova.pp_hash,
|
||||
i: nova.i,
|
||||
z_0: nova.z_0,
|
||||
z_i: nova.z_i,
|
||||
U_i: nova.U_i,
|
||||
W_i: nova.W_i,
|
||||
u_i: nova.u_i,
|
||||
w_i: nova.w_i,
|
||||
U_i1,
|
||||
W_i1,
|
||||
proof: cmT,
|
||||
randomness: r_Fr,
|
||||
cf_U_i: nova.cf_U_i,
|
||||
kzg_challenges,
|
||||
kzg_evaluations,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<C1, C2, GC2> ConstraintSynthesizer<CF1<C1>> for DeciderCircuit1<C1, C2, GC2>
|
||||
where
|
||||
C1: CurveGroup,
|
||||
<C1 as CurveGroup>::BaseField: PrimeField,
|
||||
<C1 as Group>::ScalarField: Absorb,
|
||||
C2: CurveGroup,
|
||||
<C2 as CurveGroup>::BaseField: PrimeField,
|
||||
<C2 as Group>::ScalarField: Absorb,
|
||||
C1: CurveGroup<BaseField = C2::ScalarField, ScalarField = C2::BaseField>,
|
||||
GC2: CurveVar<C2, CF2<C2>> + ToConstraintFieldGadget<CF2<C2>>,
|
||||
for<'b> &'b GC2: GroupOpsBounds<'b, C2, GC2>,
|
||||
{
|
||||
fn generate_constraints(self, cs: ConstraintSystemRef<CF1<C1>>) -> Result<(), SynthesisError> {
|
||||
let r1cs =
|
||||
R1CSVar::<C1::ScalarField, CF1<C1>, FpVar<CF1<C1>>>::new_witness(cs.clone(), || {
|
||||
Ok(self.r1cs.clone())
|
||||
})?;
|
||||
|
||||
let pp_hash = FpVar::<CF1<C1>>::new_input(cs.clone(), || {
|
||||
Ok(self.pp_hash.unwrap_or_else(CF1::<C1>::zero))
|
||||
})?;
|
||||
let i =
|
||||
FpVar::<CF1<C1>>::new_input(cs.clone(), || Ok(self.i.unwrap_or_else(CF1::<C1>::zero)))?;
|
||||
let z_0 = Vec::<FpVar<CF1<C1>>>::new_input(cs.clone(), || {
|
||||
Ok(self.z_0.unwrap_or(vec![CF1::<C1>::zero()]))
|
||||
})?;
|
||||
let z_i = Vec::<FpVar<CF1<C1>>>::new_input(cs.clone(), || {
|
||||
Ok(self.z_i.unwrap_or(vec![CF1::<C1>::zero()]))
|
||||
})?;
|
||||
|
||||
let u_dummy_native = CommittedInstance::<C1>::dummy(&self.r1cs);
|
||||
let w_dummy_native = Witness::<C1>::dummy(&self.r1cs);
|
||||
|
||||
let u_i = CommittedInstanceVar::<C1>::new_witness(cs.clone(), || {
|
||||
Ok(self.u_i.unwrap_or(u_dummy_native.clone()))
|
||||
})?;
|
||||
let U_i = CommittedInstanceVar::<C1>::new_witness(cs.clone(), || {
|
||||
Ok(self.U_i.unwrap_or(u_dummy_native.clone()))
|
||||
})?;
|
||||
// here (U_i1, W_i1) = NIFS.P( (U_i,W_i), (u_i,w_i))
|
||||
let U_i1 = CommittedInstanceVar::<C1>::new_input(cs.clone(), || {
|
||||
Ok(self.U_i1.unwrap_or(u_dummy_native.clone()))
|
||||
})?;
|
||||
let W_i1 = WitnessVar::<C1>::new_witness(cs.clone(), || {
|
||||
Ok(self.W_i1.unwrap_or(w_dummy_native.clone()))
|
||||
})?;
|
||||
|
||||
// allocate the inputs for the check 6
|
||||
let cs_c_W = FpVar::<CF1<C1>>::new_input(cs.clone(), || {
|
||||
Ok(self.cs_c_W.unwrap_or_else(CF1::<C1>::zero))
|
||||
})?;
|
||||
let cs_c_E = FpVar::<CF1<C1>>::new_input(cs.clone(), || {
|
||||
Ok(self.cs_c_E.unwrap_or_else(CF1::<C1>::zero))
|
||||
})?;
|
||||
let eval_W = FpVar::<CF1<C1>>::new_input(cs.clone(), || {
|
||||
Ok(self.eval_W.unwrap_or_else(CF1::<C1>::zero))
|
||||
})?;
|
||||
let eval_E = FpVar::<CF1<C1>>::new_input(cs.clone(), || {
|
||||
Ok(self.eval_E.unwrap_or_else(CF1::<C1>::zero))
|
||||
})?;
|
||||
|
||||
// `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();
|
||||
// notice that the `pp_hash` is absorbed inside the ChallengeGadget::get_challenge_gadget call
|
||||
|
||||
// 2. u_i.cmE==cm(0), u_i.u==1
|
||||
// Here zero is the x & y coordinates of the zero point affine representation.
|
||||
let zero = NonNativeUintVar::new_constant(cs.clone(), C1::BaseField::zero())?;
|
||||
u_i.cmE.x.enforce_equal_unaligned(&zero)?;
|
||||
u_i.cmE.y.enforce_equal_unaligned(&zero)?;
|
||||
(u_i.u.is_one()?).enforce_equal(&Boolean::TRUE)?;
|
||||
|
||||
// 3.a u_i.x[0] == H(i, z_0, z_i, U_i)
|
||||
let (u_i_x, U_i_vec) = U_i.clone().hash(&sponge, &pp_hash, &i, &z_0, &z_i)?;
|
||||
(u_i.x[0]).enforce_equal(&u_i_x)?;
|
||||
|
||||
// 3.b u_i.x[1] == H(cf_U_i)
|
||||
let cf_u_dummy_native =
|
||||
CycleFoldCommittedInstance::<C2>::dummy(NovaCycleFoldConfig::<C1>::IO_LEN);
|
||||
let cf_U_i = CycleFoldCommittedInstanceVar::<C2, GC2>::new_input(cs.clone(), || {
|
||||
Ok(self.cf_U_i.unwrap_or_else(|| cf_u_dummy_native.clone()))
|
||||
})?;
|
||||
let (cf_u_i_x, _) = cf_U_i.clone().hash(&sponge, pp_hash.clone())?;
|
||||
(u_i.x[1]).enforce_equal(&cf_u_i_x)?;
|
||||
|
||||
// 4. check RelaxedR1CS of U_{i+1}
|
||||
let z_U1: Vec<FpVar<CF1<C1>>> =
|
||||
[vec![U_i1.u.clone()], U_i1.x.to_vec(), W_i1.W.to_vec()].concat();
|
||||
RelaxedR1CSGadget::check_native(r1cs, W_i1.E.clone(), U_i1.u.clone(), z_U1)?;
|
||||
|
||||
// 1.1.a, 5.1 compute NIFS.V and Commitment Scheme challenges.
|
||||
// We need to ensure the order of challenge generation is the same as
|
||||
// the native counterpart, so we first compute the challenges here and
|
||||
// do the actual checks later.
|
||||
let cmT =
|
||||
NonNativeAffineVar::new_input(cs.clone(), || Ok(self.cmT.unwrap_or_else(C1::zero)))?;
|
||||
let r_bits = ChallengeGadget::<C1, CommittedInstance<C1>>::get_challenge_gadget(
|
||||
&mut transcript,
|
||||
pp_hash,
|
||||
U_i_vec,
|
||||
u_i.clone(),
|
||||
Some(cmT.clone()),
|
||||
)?;
|
||||
// 5.1.
|
||||
let (incircuit_c_W, incircuit_c_E) =
|
||||
KZGChallengesGadget::<C1>::get_challenges_gadget(&mut transcript, U_i1.clone())?;
|
||||
incircuit_c_W.enforce_equal(&cs_c_W)?;
|
||||
incircuit_c_E.enforce_equal(&cs_c_E)?;
|
||||
|
||||
// 5.2. check eval_W==p_W(c_W) and eval_E==p_E(c_E)
|
||||
let incircuit_eval_W = evaluate_gadget::<CF1<C1>>(W_i1.W, incircuit_c_W)?;
|
||||
let incircuit_eval_E = evaluate_gadget::<CF1<C1>>(W_i1.E, incircuit_c_E)?;
|
||||
incircuit_eval_W.enforce_equal(&eval_W)?;
|
||||
incircuit_eval_E.enforce_equal(&eval_E)?;
|
||||
|
||||
// 1.1.b check that the NIFS.V challenge matches the one from the public input (so we avoid
|
||||
// the verifier computing it)
|
||||
let r_Fr = Boolean::le_bits_to_fp_var(&r_bits)?;
|
||||
// check that the in-circuit computed r is equal to the inputted r
|
||||
let r =
|
||||
FpVar::<CF1<C1>>::new_input(cs.clone(), || Ok(self.r.unwrap_or_else(CF1::<C1>::zero)))?;
|
||||
r_Fr.enforce_equal(&r)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Circuit that implements part of the in-circuit checks needed for the offchain verification over
|
||||
/// the Curve1's BaseField (=Curve2's ScalarField).
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct DeciderCircuit2<C1, GC1, C2>
|
||||
where
|
||||
C1: CurveGroup,
|
||||
C2: CurveGroup,
|
||||
{
|
||||
_c1: PhantomData<C1>,
|
||||
_gc1: PhantomData<GC1>,
|
||||
_c2: PhantomData<C2>,
|
||||
pub type DeciderCircuit2<C2> = GenericOffchainDeciderCircuit2<C2>;
|
||||
|
||||
/// E vector's length of the CycleFold instance witness
|
||||
pub cf_E_len: usize,
|
||||
/// R1CS of the CycleFold circuit
|
||||
pub cf_r1cs: R1CS<C2::ScalarField>,
|
||||
pub poseidon_config: PoseidonConfig<CF1<C2>>,
|
||||
/// public params hash
|
||||
pub pp_hash: Option<C2::ScalarField>,
|
||||
|
||||
/// CycleFold running instance. Notice that here we use Nova's CommittedInstance (instead of
|
||||
/// CycleFoldCommittedInstance), since we are over C2::Fr, so that the CycleFold instances can
|
||||
/// be computed natively
|
||||
pub cf_U_i: Option<CommittedInstance<C2>>,
|
||||
pub cf_W_i: Option<CycleFoldWitness<C2>>,
|
||||
/// Commitment Scheme challenges
|
||||
pub cs_c_W: Option<C2::ScalarField>,
|
||||
pub cs_c_E: Option<C2::ScalarField>,
|
||||
/// Evaluations of the committed polynomials at the challenge
|
||||
pub eval_W: Option<C2::ScalarField>,
|
||||
pub eval_E: Option<C2::ScalarField>,
|
||||
}
|
||||
impl<C1, GC1, C2> DeciderCircuit2<C1, GC1, C2>
|
||||
where
|
||||
C1: CurveGroup,
|
||||
C2: CurveGroup,
|
||||
<C1 as CurveGroup>::BaseField: PrimeField,
|
||||
<C1 as Group>::ScalarField: Absorb,
|
||||
<C2 as CurveGroup>::BaseField: PrimeField,
|
||||
<C2 as Group>::ScalarField: Absorb,
|
||||
GC1: CurveVar<C1, CF2<C1>> + ToConstraintFieldGadget<CF2<C1>>,
|
||||
{
|
||||
pub fn from_nova<GC2, CS1, CS2, const H: bool, FC: FCircuit<C1::ScalarField>>(
|
||||
nova: Nova<C1, GC1, C2, GC2, FC, CS1, CS2, H>,
|
||||
) -> Result<Self, Error>
|
||||
where
|
||||
impl<
|
||||
C1: CurveGroup,
|
||||
GC1: CurveVar<C1, CF2<C1>> + ToConstraintFieldGadget<CF2<C1>>,
|
||||
C2: CurveGroup,
|
||||
GC2: CurveVar<C2, CF2<C2>> + ToConstraintFieldGadget<CF2<C2>>,
|
||||
FC: FCircuit<C1::ScalarField>,
|
||||
CS1: CommitmentScheme<C1, H>,
|
||||
CS2: CommitmentScheme<C2, H>,
|
||||
{
|
||||
const H: bool,
|
||||
> TryFrom<Nova<C1, GC1, C2, GC2, FC, CS1, CS2, H>> for DeciderCircuit2<C2>
|
||||
where
|
||||
CF1<C2>: Absorb,
|
||||
{
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(nova: Nova<C1, GC1, C2, GC2, FC, CS1, CS2, H>) -> Result<Self, Error> {
|
||||
// compute the Commitment Scheme challenges of the CycleFold instance commitments, used as
|
||||
// inputs in the circuit
|
||||
let poseidon_config =
|
||||
crate::transcript::poseidon::poseidon_canonical_config::<C2::ScalarField>();
|
||||
let poseidon_config = poseidon_canonical_config::<C2::ScalarField>();
|
||||
let mut transcript = PoseidonSponge::<C2::ScalarField>::new(&poseidon_config);
|
||||
let pp_hash_Fq =
|
||||
C2::ScalarField::from_le_bytes_mod_order(&nova.pp_hash.into_bigint().to_bytes_le());
|
||||
transcript.absorb(&pp_hash_Fq);
|
||||
|
||||
let (cs_challenge_W, cs_challenge_E) =
|
||||
KZGChallengesGadget::<C2>::get_challenges_native(&mut transcript, nova.cf_U_i.clone());
|
||||
// compute the KZG challenges used as inputs in the circuit
|
||||
let kzg_challenges =
|
||||
KZGChallengesGadget::get_challenges_native(&mut transcript, &nova.cf_U_i);
|
||||
|
||||
// get evals of the committed polynomials at the challenge
|
||||
let mut W = nova.cf_W_i.W.clone();
|
||||
W.extend(
|
||||
std::iter::repeat(C2::ScalarField::zero())
|
||||
.take(nova.cf_W_i.W.len().next_power_of_two() - nova.cf_W_i.W.len()),
|
||||
);
|
||||
let mut E = nova.cf_W_i.E.clone();
|
||||
E.extend(
|
||||
std::iter::repeat(C2::ScalarField::zero())
|
||||
.take(nova.cf_W_i.E.len().next_power_of_two() - nova.cf_W_i.E.len()),
|
||||
);
|
||||
let p_W = poly_from_vec(W.to_vec())?;
|
||||
let eval_W = p_W.evaluate(&cs_challenge_W);
|
||||
let p_E = poly_from_vec(E.to_vec())?;
|
||||
let eval_E = p_E.evaluate(&cs_challenge_E);
|
||||
// get KZG evals
|
||||
let kzg_evaluations = nova
|
||||
.cf_W_i
|
||||
.get_openings()
|
||||
.iter()
|
||||
.zip(&kzg_challenges)
|
||||
.map(|((v, _), &c)| EvalGadget::evaluate_native(v, c))
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
Ok(Self {
|
||||
_c1: PhantomData,
|
||||
_gc1: PhantomData,
|
||||
_c2: PhantomData,
|
||||
|
||||
cf_E_len: nova.cf_W_i.E.len(),
|
||||
cf_r1cs: nova.cf_r1cs,
|
||||
cf_arith: nova.cf_r1cs,
|
||||
poseidon_config,
|
||||
pp_hash: Some(pp_hash_Fq),
|
||||
|
||||
cf_U_i: Some(nova.cf_U_i),
|
||||
cf_W_i: Some(nova.cf_W_i),
|
||||
|
||||
// CycleFold instance commitments challenges
|
||||
cs_c_W: Some(cs_challenge_W),
|
||||
cs_c_E: Some(cs_challenge_E),
|
||||
eval_W: Some(eval_W),
|
||||
eval_E: Some(eval_E),
|
||||
pp_hash: pp_hash_Fq,
|
||||
cf_U_i: nova.cf_U_i,
|
||||
cf_W_i: nova.cf_W_i,
|
||||
kzg_challenges,
|
||||
kzg_evaluations,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<C1, GC1, C2> ConstraintSynthesizer<CF1<C2>> for DeciderCircuit2<C1, GC1, C2>
|
||||
where
|
||||
C1: CurveGroup,
|
||||
C2: CurveGroup,
|
||||
<C1 as CurveGroup>::BaseField: PrimeField,
|
||||
<C2 as CurveGroup>::BaseField: PrimeField,
|
||||
<C1 as Group>::ScalarField: Absorb,
|
||||
<C2 as Group>::ScalarField: Absorb,
|
||||
C1: CurveGroup<BaseField = C2::ScalarField, ScalarField = C2::BaseField>,
|
||||
GC1: CurveVar<C1, CF2<C1>> + ToConstraintFieldGadget<CF2<C1>>,
|
||||
for<'a> &'a GC1: GroupOpsBounds<'a, C1, GC1>,
|
||||
{
|
||||
fn generate_constraints(self, cs: ConstraintSystemRef<CF1<C2>>) -> Result<(), SynthesisError> {
|
||||
let pp_hash = FpVar::<CF1<C2>>::new_input(cs.clone(), || {
|
||||
Ok(self.pp_hash.unwrap_or_else(CF1::<C2>::zero))
|
||||
})?;
|
||||
|
||||
let cf_u_dummy_native = CommittedInstance::<C2>::dummy(&self.cf_r1cs);
|
||||
let w_dummy_native = Witness::<C2>::dummy(&self.cf_r1cs);
|
||||
let cf_U_i = CommittedInstanceVar::<C2>::new_input(cs.clone(), || {
|
||||
Ok(self.cf_U_i.unwrap_or_else(|| cf_u_dummy_native.clone()))
|
||||
})?;
|
||||
let cf_W_i = WitnessVar::<C2>::new_witness(cs.clone(), || {
|
||||
Ok(self.cf_W_i.unwrap_or(w_dummy_native.clone()))
|
||||
})?;
|
||||
|
||||
let cf_r1cs =
|
||||
R1CSVar::<C2::ScalarField, CF1<C2>, FpVar<CF1<C2>>>::new_witness(cs.clone(), || {
|
||||
Ok(self.cf_r1cs.clone())
|
||||
})?;
|
||||
|
||||
// 6. check RelaxedR1CS of cf_U_i
|
||||
let cf_z_U = [vec![cf_U_i.u.clone()], cf_U_i.x.to_vec(), cf_W_i.W.to_vec()].concat();
|
||||
RelaxedR1CSGadget::check_native(cf_r1cs, cf_W_i.E.clone(), cf_U_i.u.clone(), cf_z_U)?;
|
||||
|
||||
// `transcript` is for challenge generation.
|
||||
let mut transcript =
|
||||
PoseidonSpongeVar::<C2::ScalarField>::new(cs.clone(), &self.poseidon_config);
|
||||
transcript.absorb(&pp_hash)?;
|
||||
|
||||
// allocate the inputs for the check 7.1
|
||||
let cs_c_W = FpVar::<CF1<C2>>::new_input(cs.clone(), || {
|
||||
Ok(self.cs_c_W.unwrap_or_else(CF1::<C2>::zero))
|
||||
})?;
|
||||
let cs_c_E = FpVar::<CF1<C2>>::new_input(cs.clone(), || {
|
||||
Ok(self.cs_c_E.unwrap_or_else(CF1::<C2>::zero))
|
||||
})?;
|
||||
// allocate the inputs for the check 7.2
|
||||
let eval_W = FpVar::<CF1<C2>>::new_input(cs.clone(), || {
|
||||
Ok(self.eval_W.unwrap_or_else(CF1::<C2>::zero))
|
||||
})?;
|
||||
let eval_E = FpVar::<CF1<C2>>::new_input(cs.clone(), || {
|
||||
Ok(self.eval_E.unwrap_or_else(CF1::<C2>::zero))
|
||||
})?;
|
||||
|
||||
// 7.1. check the commitment scheme challenges correct computation
|
||||
let (incircuit_c_W, incircuit_c_E) =
|
||||
KZGChallengesGadget::<C2>::get_challenges_gadget(&mut transcript, cf_U_i.clone())?;
|
||||
incircuit_c_W.enforce_equal(&cs_c_W)?;
|
||||
incircuit_c_E.enforce_equal(&cs_c_E)?;
|
||||
|
||||
// 7.2. check eval_W==p_W(c_W) and eval_E==p_E(c_E)
|
||||
let incircuit_eval_W = evaluate_gadget::<CF1<C2>>(cf_W_i.W, incircuit_c_W)?;
|
||||
let incircuit_eval_E = evaluate_gadget::<CF1<C2>>(cf_W_i.E, incircuit_c_E)?;
|
||||
incircuit_eval_W.enforce_equal(&eval_W)?;
|
||||
incircuit_eval_E.enforce_equal(&eval_E)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use ark_pallas::{constraints::GVar, Fq, Fr, Projective};
|
||||
use ark_relations::r1cs::ConstraintSystem;
|
||||
use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystem};
|
||||
use ark_vesta::{constraints::GVar as GVar2, Projective as Projective2};
|
||||
|
||||
use super::*;
|
||||
@@ -530,9 +215,8 @@ pub mod tests {
|
||||
|
||||
// load the DeciderCircuit 1 & 2 from the Nova instance
|
||||
let decider_circuit1 =
|
||||
DeciderCircuit1::<Projective, Projective2, GVar2>::from_nova(nova.clone()).unwrap();
|
||||
let decider_circuit2 =
|
||||
DeciderCircuit2::<Projective, GVar, Projective2>::from_nova(nova).unwrap();
|
||||
DeciderCircuit1::<Projective, Projective2, GVar2>::try_from(nova.clone()).unwrap();
|
||||
let decider_circuit2 = DeciderCircuit2::<Projective2>::try_from(nova).unwrap();
|
||||
|
||||
// generate the constraints of both circuits and check that are satisfied by the inputs
|
||||
let cs1 = ConstraintSystem::<Fr>::new_ref();
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
/// More details can be found at the documentation page:
|
||||
/// https://privacy-scaling-explorations.github.io/sonobe-docs/design/nova-decider-onchain.html
|
||||
use ark_bn254::Bn254;
|
||||
use ark_crypto_primitives::sponge::{poseidon::PoseidonSponge, Absorb, CryptographicSponge};
|
||||
use ark_crypto_primitives::sponge::Absorb;
|
||||
use ark_ec::{AffineRepr, CurveGroup, Group};
|
||||
use ark_ff::{BigInteger, PrimeField};
|
||||
use ark_groth16::Groth16;
|
||||
use ark_r1cs_std::{groups::GroupOpsBounds, prelude::CurveVar, ToConstraintFieldGadget};
|
||||
use ark_r1cs_std::{prelude::CurveVar, ToConstraintFieldGadget};
|
||||
use ark_serialize::{CanonicalDeserialize, CanonicalSerialize};
|
||||
use ark_snark::SNARK;
|
||||
use ark_std::rand::{CryptoRng, RngCore};
|
||||
@@ -15,37 +15,36 @@ use ark_std::{One, Zero};
|
||||
use core::marker::PhantomData;
|
||||
|
||||
pub use super::decider_eth_circuit::DeciderEthCircuit;
|
||||
use super::{
|
||||
nifs::{nova::NIFS, NIFSTrait},
|
||||
CommittedInstance, Nova,
|
||||
};
|
||||
use super::decider_eth_circuit::DeciderNovaGadget;
|
||||
use super::{CommittedInstance, Nova};
|
||||
use crate::commitment::{
|
||||
kzg::{Proof as KZGProof, KZG},
|
||||
pedersen::Params as PedersenParams,
|
||||
CommitmentScheme,
|
||||
};
|
||||
use crate::folding::circuits::{nonnative::affine::NonNativeAffineVar, CF2};
|
||||
use crate::folding::nova::circuits::ChallengeGadget;
|
||||
use crate::folding::circuits::decider::DeciderEnabledNIFS;
|
||||
use crate::folding::circuits::CF2;
|
||||
use crate::folding::traits::{Inputize, WitnessOps};
|
||||
use crate::frontend::FCircuit;
|
||||
use crate::transcript::poseidon::poseidon_canonical_config;
|
||||
use crate::Error;
|
||||
use crate::{Decider as DeciderTrait, FoldingScheme};
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)]
|
||||
pub struct Proof<C1, CS1, S>
|
||||
pub struct Proof<C, CS, S>
|
||||
where
|
||||
C1: CurveGroup,
|
||||
CS1: CommitmentScheme<C1, ProverChallenge = C1::ScalarField, Challenge = C1::ScalarField>,
|
||||
S: SNARK<C1::ScalarField>,
|
||||
C: CurveGroup,
|
||||
CS: CommitmentScheme<C, ProverChallenge = C::ScalarField, Challenge = C::ScalarField>,
|
||||
S: SNARK<C::ScalarField>,
|
||||
{
|
||||
snark_proof: S::Proof,
|
||||
kzg_proofs: [CS1::Proof; 2],
|
||||
kzg_proofs: [CS::Proof; 2],
|
||||
// cmT and r are values for the last fold, U_{i+1}=NIFS.V(r, U_i, u_i, cmT), and they are
|
||||
// checked in-circuit
|
||||
cmT: C1,
|
||||
cmT: C,
|
||||
r: C::ScalarField,
|
||||
// the KZG challenges are provided by the prover, but in-circuit they are checked to match
|
||||
// the in-circuit computed computed ones.
|
||||
kzg_challenges: [C1::ScalarField; 2],
|
||||
kzg_challenges: [C::ScalarField; 2],
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)]
|
||||
@@ -98,8 +97,6 @@ where
|
||||
<C1 as Group>::ScalarField: Absorb,
|
||||
<C2 as Group>::ScalarField: Absorb,
|
||||
C1: CurveGroup<BaseField = C2::ScalarField, ScalarField = C2::BaseField>,
|
||||
for<'b> &'b GC1: GroupOpsBounds<'b, C1, GC1>,
|
||||
for<'b> &'b GC2: GroupOpsBounds<'b, C2, GC2>,
|
||||
// constrain FS into Nova, since this is a Decider specifically for Nova
|
||||
Nova<C1, GC1, C2, GC2, FC, CS1, CS2, false>: From<FS>,
|
||||
crate::folding::nova::ProverParams<C1, C2, CS1, CS2, false>:
|
||||
@@ -112,18 +109,18 @@ where
|
||||
type Proof = Proof<C1, CS1, S>;
|
||||
type VerifierParam = VerifierParam<C1, CS1::VerifierParams, S::VerifyingKey>;
|
||||
type PublicInput = Vec<C1::ScalarField>;
|
||||
type CommittedInstance = CommittedInstance<C1>;
|
||||
type CommittedInstance = Vec<C1>;
|
||||
|
||||
fn preprocess(
|
||||
mut rng: impl RngCore + CryptoRng,
|
||||
prep_param: Self::PreprocessorParam,
|
||||
fs: FS,
|
||||
) -> Result<(Self::ProverParam, Self::VerifierParam), Error> {
|
||||
let circuit =
|
||||
DeciderEthCircuit::<C1, GC1, C2, GC2, CS1, CS2>::from_nova::<FC>(fs.into()).unwrap();
|
||||
let circuit = DeciderEthCircuit::<C1, C2, GC2>::try_from(Nova::from(fs))?;
|
||||
|
||||
// get the Groth16 specific setup for the circuit
|
||||
let (g16_pk, g16_vk) = S::circuit_specific_setup(circuit, &mut rng).unwrap();
|
||||
let (g16_pk, g16_vk) = S::circuit_specific_setup(circuit, &mut rng)
|
||||
.map_err(|e| Error::SNARKSetupFail(e.to_string()))?;
|
||||
|
||||
// get the FoldingScheme prover & verifier params from Nova
|
||||
#[allow(clippy::type_complexity)]
|
||||
@@ -156,46 +153,39 @@ where
|
||||
) -> Result<Self::Proof, Error> {
|
||||
let (snark_pk, cs_pk): (S::ProvingKey, CS1::ProverParams) = pp;
|
||||
|
||||
let circuit = DeciderEthCircuit::<C1, GC1, C2, GC2, CS1, CS2>::from_nova::<FC>(
|
||||
folding_scheme.into(),
|
||||
)?;
|
||||
let circuit = DeciderEthCircuit::<C1, C2, GC2>::try_from(Nova::from(folding_scheme))?;
|
||||
|
||||
let snark_proof = S::prove(&snark_pk, circuit.clone(), &mut rng)
|
||||
.map_err(|e| Error::Other(e.to_string()))?;
|
||||
|
||||
let cmT = circuit.cmT.unwrap();
|
||||
let W_i1 = circuit.W_i1.unwrap();
|
||||
let cmT = circuit.proof;
|
||||
let r = circuit.randomness;
|
||||
|
||||
// get the challenges that have been already computed when preparing the circuit inputs in
|
||||
// the above `from_nova` call
|
||||
let challenge_W = circuit
|
||||
.kzg_c_W
|
||||
.ok_or(Error::MissingValue("kzg_c_W".to_string()))?;
|
||||
let challenge_E = circuit
|
||||
.kzg_c_E
|
||||
.ok_or(Error::MissingValue("kzg_c_E".to_string()))?;
|
||||
// the above `try_from` call
|
||||
let kzg_challenges = circuit.kzg_challenges.clone();
|
||||
|
||||
// generate KZG proofs
|
||||
let U_cmW_proof = CS1::prove_with_challenge(
|
||||
&cs_pk,
|
||||
challenge_W,
|
||||
&W_i1.W,
|
||||
&C1::ScalarField::zero(),
|
||||
None,
|
||||
)?;
|
||||
let U_cmE_proof = CS1::prove_with_challenge(
|
||||
&cs_pk,
|
||||
challenge_E,
|
||||
&W_i1.E,
|
||||
&C1::ScalarField::zero(),
|
||||
None,
|
||||
)?;
|
||||
let kzg_proofs = circuit
|
||||
.W_i1
|
||||
.get_openings()
|
||||
.iter()
|
||||
.zip(&kzg_challenges)
|
||||
.map(|((v, _), &c)| {
|
||||
CS1::prove_with_challenge(&cs_pk, c, v, &C1::ScalarField::zero(), None)
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
let snark_proof =
|
||||
S::prove(&snark_pk, circuit, &mut rng).map_err(|e| Error::Other(e.to_string()))?;
|
||||
|
||||
Ok(Self::Proof {
|
||||
snark_proof,
|
||||
kzg_proofs: [U_cmW_proof, U_cmE_proof],
|
||||
cmT,
|
||||
kzg_challenges: [challenge_W, challenge_E],
|
||||
r,
|
||||
kzg_proofs: kzg_proofs
|
||||
.try_into()
|
||||
.map_err(|e: Vec<_>| Error::NotExpectedLength(e.len(), 2))?,
|
||||
kzg_challenges: kzg_challenges
|
||||
.try_into()
|
||||
.map_err(|e: Vec<_>| Error::NotExpectedLength(e.len(), 2))?,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -204,71 +194,59 @@ where
|
||||
i: C1::ScalarField,
|
||||
z_0: Vec<C1::ScalarField>,
|
||||
z_i: Vec<C1::ScalarField>,
|
||||
running_instance: &Self::CommittedInstance,
|
||||
incoming_instance: &Self::CommittedInstance,
|
||||
// we don't use the instances at the verifier level, since we check them in-circuit
|
||||
running_commitments: &Self::CommittedInstance,
|
||||
incoming_commitments: &Self::CommittedInstance,
|
||||
proof: &Self::Proof,
|
||||
) -> Result<bool, Error> {
|
||||
if i <= C1::ScalarField::one() {
|
||||
return Err(Error::NotEnoughSteps);
|
||||
}
|
||||
|
||||
// compute U = U_{d+1}= NIFS.V(U_d, u_d, cmT)
|
||||
let poseidon_config = poseidon_canonical_config::<C1::ScalarField>();
|
||||
let mut transcript = PoseidonSponge::<C1::ScalarField>::new(&poseidon_config);
|
||||
let (U, r_bits) = NIFS::<C1, CS1, PoseidonSponge<C1::ScalarField>>::verify(
|
||||
&mut transcript,
|
||||
vp.pp_hash,
|
||||
running_instance,
|
||||
incoming_instance,
|
||||
&proof.cmT,
|
||||
let Self::VerifierParam {
|
||||
pp_hash,
|
||||
snark_vp,
|
||||
cs_vp,
|
||||
} = vp;
|
||||
|
||||
// 6.2. Fold the commitments
|
||||
let U_final_commitments = DeciderNovaGadget::fold_group_elements_native(
|
||||
running_commitments,
|
||||
incoming_commitments,
|
||||
Some(proof.cmT),
|
||||
proof.r,
|
||||
)?;
|
||||
let r = C1::ScalarField::from_bigint(BigInteger::from_bits_le(&r_bits))
|
||||
.ok_or(Error::OutOfBounds)?;
|
||||
|
||||
let (cmE_x, cmE_y) = NonNativeAffineVar::inputize(U.cmE)?;
|
||||
let (cmW_x, cmW_y) = NonNativeAffineVar::inputize(U.cmW)?;
|
||||
let (cmT_x, cmT_y) = NonNativeAffineVar::inputize(proof.cmT)?;
|
||||
|
||||
let public_input: Vec<C1::ScalarField> = [
|
||||
vec![vp.pp_hash, i],
|
||||
z_0,
|
||||
z_i,
|
||||
vec![U.u],
|
||||
U.x.clone(),
|
||||
cmE_x,
|
||||
cmE_y,
|
||||
cmW_x,
|
||||
cmW_y,
|
||||
proof.kzg_challenges.to_vec(),
|
||||
vec![
|
||||
proof.kzg_proofs[0].eval, // eval_W
|
||||
proof.kzg_proofs[1].eval, // eval_E
|
||||
],
|
||||
cmT_x,
|
||||
cmT_y,
|
||||
vec![r],
|
||||
let public_input = [
|
||||
&[pp_hash, i][..],
|
||||
&z_0,
|
||||
&z_i,
|
||||
&U_final_commitments
|
||||
.iter()
|
||||
.flat_map(|c| c.inputize())
|
||||
.collect::<Vec<_>>(),
|
||||
&proof.kzg_challenges,
|
||||
&proof.kzg_proofs.iter().map(|p| p.eval).collect::<Vec<_>>(),
|
||||
&proof.cmT.inputize(),
|
||||
&[proof.r],
|
||||
]
|
||||
.concat();
|
||||
|
||||
let snark_v = S::verify(&vp.snark_vp, &public_input, &proof.snark_proof)
|
||||
let snark_v = S::verify(&snark_vp, &public_input, &proof.snark_proof)
|
||||
.map_err(|e| Error::Other(e.to_string()))?;
|
||||
if !snark_v {
|
||||
return Err(Error::SNARKVerificationFail);
|
||||
}
|
||||
|
||||
// we're at the Ethereum EVM case, so the CS1 is KZG commitments
|
||||
CS1::verify_with_challenge(
|
||||
&vp.cs_vp,
|
||||
proof.kzg_challenges[0],
|
||||
&U.cmW,
|
||||
&proof.kzg_proofs[0],
|
||||
)?;
|
||||
CS1::verify_with_challenge(
|
||||
&vp.cs_vp,
|
||||
proof.kzg_challenges[1],
|
||||
&U.cmE,
|
||||
&proof.kzg_proofs[1],
|
||||
)?;
|
||||
// 7.3. Verify the KZG proofs
|
||||
for ((cm, &c), pi) in U_final_commitments
|
||||
.iter()
|
||||
.zip(&proof.kzg_challenges)
|
||||
.zip(&proof.kzg_proofs)
|
||||
{
|
||||
// we're at the Ethereum EVM case, so the CS1 is KZG commitments
|
||||
CS1::verify_with_challenge(&cs_vp, c, cm, pi)?;
|
||||
}
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
@@ -278,7 +256,6 @@ where
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn prepare_calldata(
|
||||
function_signature_check: [u8; 4],
|
||||
pp_hash: ark_bn254::Fr,
|
||||
i: ark_bn254::Fr,
|
||||
z_0: Vec<ark_bn254::Fr>,
|
||||
z_i: Vec<ark_bn254::Fr>,
|
||||
@@ -286,22 +263,6 @@ pub fn prepare_calldata(
|
||||
incoming_instance: &CommittedInstance<ark_bn254::G1Projective>,
|
||||
proof: Proof<ark_bn254::G1Projective, KZG<'static, Bn254>, Groth16<Bn254>>,
|
||||
) -> Result<Vec<u8>, Error> {
|
||||
// compute the challenge r
|
||||
let poseidon_config = poseidon_canonical_config::<ark_bn254::Fr>();
|
||||
let mut transcript = PoseidonSponge::<ark_bn254::Fr>::new(&poseidon_config);
|
||||
let r_bits = ChallengeGadget::<
|
||||
ark_bn254::G1Projective,
|
||||
CommittedInstance<ark_bn254::G1Projective>,
|
||||
>::get_challenge_native(
|
||||
&mut transcript,
|
||||
pp_hash,
|
||||
running_instance,
|
||||
incoming_instance,
|
||||
Some(&proof.cmT),
|
||||
);
|
||||
let r =
|
||||
ark_bn254::Fr::from_bigint(BigInteger::from_bits_le(&r_bits)).ok_or(Error::OutOfBounds)?;
|
||||
|
||||
Ok(vec![
|
||||
function_signature_check.to_vec(),
|
||||
i.into_bigint().to_bytes_be(), // i
|
||||
@@ -311,26 +272,14 @@ pub fn prepare_calldata(
|
||||
z_i.iter()
|
||||
.flat_map(|v| v.into_bigint().to_bytes_be())
|
||||
.collect::<Vec<u8>>(), // z_i
|
||||
point_to_eth_format(running_instance.cmW.into_affine())?, // U_i_cmW
|
||||
point_to_eth_format(running_instance.cmE.into_affine())?, // U_i_cmE
|
||||
running_instance.u.into_bigint().to_bytes_be(), // U_i_u
|
||||
incoming_instance.u.into_bigint().to_bytes_be(), // u_i_u
|
||||
r.into_bigint().to_bytes_be(), // r
|
||||
running_instance
|
||||
.x
|
||||
.iter()
|
||||
.flat_map(|v| v.into_bigint().to_bytes_be())
|
||||
.collect::<Vec<u8>>(), // U_i_x
|
||||
point_to_eth_format(incoming_instance.cmW.into_affine())?, // u_i_cmW
|
||||
incoming_instance
|
||||
.x
|
||||
.iter()
|
||||
.flat_map(|v| v.into_bigint().to_bytes_be())
|
||||
.collect::<Vec<u8>>(), // u_i_x
|
||||
point_to_eth_format(running_instance.cmW.into_affine())?,
|
||||
point_to_eth_format(running_instance.cmE.into_affine())?,
|
||||
point_to_eth_format(incoming_instance.cmW.into_affine())?,
|
||||
point_to_eth_format(proof.cmT.into_affine())?, // cmT
|
||||
point_to_eth_format(proof.snark_proof.a)?, // pA
|
||||
point2_to_eth_format(proof.snark_proof.b)?, // pB
|
||||
point_to_eth_format(proof.snark_proof.c)?, // pC
|
||||
proof.r.into_bigint().to_bytes_be(), // r
|
||||
point_to_eth_format(proof.snark_proof.a)?, // pA
|
||||
point2_to_eth_format(proof.snark_proof.b)?, // pB
|
||||
point_to_eth_format(proof.snark_proof.c)?, // pC
|
||||
proof.kzg_challenges[0].into_bigint().to_bytes_be(), // challenge_W
|
||||
proof.kzg_challenges[1].into_bigint().to_bytes_be(), // challenge_E
|
||||
proof.kzg_proofs[0].eval.into_bigint().to_bytes_be(), // eval W
|
||||
@@ -373,6 +322,7 @@ pub mod tests {
|
||||
use super::*;
|
||||
use crate::commitment::pedersen::Pedersen;
|
||||
use crate::folding::nova::{PreprocessorParam, ProverParams as NovaProverParams};
|
||||
use crate::folding::traits::CommittedInstanceOps;
|
||||
use crate::frontend::utils::CubicFCircuit;
|
||||
use crate::transcript::poseidon::poseidon_canonical_config;
|
||||
|
||||
@@ -434,8 +384,8 @@ pub mod tests {
|
||||
nova.i,
|
||||
nova.z_0.clone(),
|
||||
nova.z_i.clone(),
|
||||
&nova.U_i,
|
||||
&nova.u_i,
|
||||
&nova.U_i.get_commitments(),
|
||||
&nova.u_i.get_commitments(),
|
||||
&proof,
|
||||
)
|
||||
.unwrap();
|
||||
@@ -444,7 +394,13 @@ pub mod tests {
|
||||
|
||||
// decider proof verification using the deserialized data
|
||||
let verified = D::verify(
|
||||
decider_vp, nova.i, nova.z_0, nova.z_i, &nova.U_i, &nova.u_i, &proof,
|
||||
decider_vp,
|
||||
nova.i,
|
||||
nova.z_0,
|
||||
nova.z_i,
|
||||
&nova.U_i.get_commitments(),
|
||||
&nova.u_i.get_commitments(),
|
||||
&proof,
|
||||
)
|
||||
.unwrap();
|
||||
assert!(verified);
|
||||
@@ -550,8 +506,8 @@ pub mod tests {
|
||||
nova.i,
|
||||
nova.z_0.clone(),
|
||||
nova.z_i.clone(),
|
||||
&nova.U_i,
|
||||
&nova.u_i,
|
||||
&nova.U_i.get_commitments(),
|
||||
&nova.u_i.get_commitments(),
|
||||
&proof,
|
||||
)
|
||||
.unwrap();
|
||||
@@ -579,12 +535,6 @@ pub mod tests {
|
||||
nova.z_i
|
||||
.serialize_compressed(&mut public_inputs_serialized)
|
||||
.unwrap();
|
||||
nova.U_i
|
||||
.serialize_compressed(&mut public_inputs_serialized)
|
||||
.unwrap();
|
||||
nova.u_i
|
||||
.serialize_compressed(&mut public_inputs_serialized)
|
||||
.unwrap();
|
||||
|
||||
// deserialize back the verifier_params, proof and public inputs
|
||||
let decider_vp_deserialized =
|
||||
@@ -605,10 +555,6 @@ pub mod tests {
|
||||
let i_deserialized = Fr::deserialize_compressed(&mut reader).unwrap();
|
||||
let z_0_deserialized = Vec::<Fr>::deserialize_compressed(&mut reader).unwrap();
|
||||
let z_i_deserialized = Vec::<Fr>::deserialize_compressed(&mut reader).unwrap();
|
||||
let U_i_deserialized =
|
||||
CommittedInstance::<Projective>::deserialize_compressed(&mut reader).unwrap();
|
||||
let u_i_deserialized =
|
||||
CommittedInstance::<Projective>::deserialize_compressed(&mut reader).unwrap();
|
||||
|
||||
// decider proof verification using the deserialized data
|
||||
let verified = D::verify(
|
||||
@@ -616,8 +562,8 @@ pub mod tests {
|
||||
i_deserialized,
|
||||
z_0_deserialized,
|
||||
z_i_deserialized,
|
||||
&U_i_deserialized,
|
||||
&u_i_deserialized,
|
||||
&nova.U_i.get_commitments(),
|
||||
&nova.u_i.get_commitments(),
|
||||
&proof_deserialized,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -4,130 +4,42 @@
|
||||
/// https://privacy-scaling-explorations.github.io/sonobe-docs/design/nova-decider-onchain.html
|
||||
use ark_crypto_primitives::sponge::{
|
||||
constraints::CryptographicSpongeVar,
|
||||
poseidon::{constraints::PoseidonSpongeVar, PoseidonConfig, PoseidonSponge},
|
||||
poseidon::{constraints::PoseidonSpongeVar, PoseidonSponge},
|
||||
Absorb, CryptographicSponge,
|
||||
};
|
||||
use ark_ec::{CurveGroup, Group};
|
||||
use ark_ec::CurveGroup;
|
||||
use ark_ff::{BigInteger, PrimeField};
|
||||
use ark_poly::Polynomial;
|
||||
use ark_r1cs_std::{
|
||||
alloc::{AllocVar, AllocationMode},
|
||||
boolean::Boolean,
|
||||
eq::EqGadget,
|
||||
fields::{fp::FpVar, FieldVar},
|
||||
groups::GroupOpsBounds,
|
||||
poly::{domain::Radix2DomainVar, evaluations::univariate::EvaluationsVar},
|
||||
fields::fp::FpVar,
|
||||
prelude::CurveVar,
|
||||
ToConstraintFieldGadget,
|
||||
};
|
||||
use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystemRef, Namespace, SynthesisError};
|
||||
use ark_std::{log2, Zero};
|
||||
use core::{borrow::Borrow, marker::PhantomData};
|
||||
use ark_relations::r1cs::{Namespace, SynthesisError};
|
||||
use ark_std::{borrow::Borrow, marker::PhantomData};
|
||||
|
||||
use super::{
|
||||
circuits::{ChallengeGadget, CommittedInstanceVar},
|
||||
circuits::{ChallengeGadget, CommittedInstanceVar, NIFSGadget},
|
||||
nifs::{nova::NIFS, NIFSTrait},
|
||||
CommittedInstance, Nova, Witness,
|
||||
};
|
||||
use crate::commitment::{pedersen::Params as PedersenParams, CommitmentScheme};
|
||||
use crate::folding::circuits::{
|
||||
cyclefold::{CycleFoldCommittedInstance, CycleFoldWitness},
|
||||
nonnative::{affine::NonNativeAffineVar, uint::NonNativeUintVar},
|
||||
CF1, CF2,
|
||||
use crate::folding::{
|
||||
circuits::{
|
||||
decider::on_chain::GenericOnchainDeciderCircuit, nonnative::affine::NonNativeAffineVar,
|
||||
CF1, CF2,
|
||||
},
|
||||
traits::{WitnessOps, WitnessVarOps},
|
||||
};
|
||||
use crate::frontend::FCircuit;
|
||||
use crate::transcript::{Transcript, TranscriptVar};
|
||||
use crate::utils::{
|
||||
gadgets::{MatrixGadget, SparseMatrixVar, VectorGadget},
|
||||
vec::poly_from_vec,
|
||||
};
|
||||
use crate::Error;
|
||||
use crate::{
|
||||
arith::r1cs::R1CS,
|
||||
folding::traits::{CommittedInstanceVarOps, Dummy, WitnessVarOps},
|
||||
arith::r1cs::{circuits::R1CSMatricesVar, R1CS},
|
||||
folding::circuits::decider::{DeciderEnabledNIFS, EvalGadget, KZGChallengesGadget},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RelaxedR1CSGadget {}
|
||||
impl RelaxedR1CSGadget {
|
||||
/// performs the RelaxedR1CS check for native variables (Az∘Bz==uCz+E)
|
||||
pub fn check_native<F: PrimeField>(
|
||||
r1cs: R1CSVar<F, F, FpVar<F>>,
|
||||
E: Vec<FpVar<F>>,
|
||||
u: FpVar<F>,
|
||||
z: Vec<FpVar<F>>,
|
||||
) -> Result<(), SynthesisError> {
|
||||
let Az = r1cs.A.mul_vector(&z)?;
|
||||
let Bz = r1cs.B.mul_vector(&z)?;
|
||||
let Cz = r1cs.C.mul_vector(&z)?;
|
||||
let uCzE = Cz.mul_scalar(&u)?.add(&E)?;
|
||||
let AzBz = Az.hadamard(&Bz)?;
|
||||
AzBz.enforce_equal(&uCzE)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// performs the RelaxedR1CS check for non-native variables (Az∘Bz==uCz+E)
|
||||
pub fn check_nonnative<F: PrimeField, CF: PrimeField>(
|
||||
r1cs: R1CSVar<F, CF, NonNativeUintVar<CF>>,
|
||||
E: Vec<NonNativeUintVar<CF>>,
|
||||
u: NonNativeUintVar<CF>,
|
||||
z: Vec<NonNativeUintVar<CF>>,
|
||||
) -> Result<(), SynthesisError> {
|
||||
// First we do addition and multiplication without mod F's order
|
||||
let Az = r1cs.A.mul_vector(&z)?;
|
||||
let Bz = r1cs.B.mul_vector(&z)?;
|
||||
let Cz = r1cs.C.mul_vector(&z)?;
|
||||
let uCzE = Cz.mul_scalar(&u)?.add(&E)?;
|
||||
let AzBz = Az.hadamard(&Bz)?;
|
||||
|
||||
// Then we compare the results by checking if they are congruent
|
||||
// modulo the field order
|
||||
AzBz.into_iter()
|
||||
.zip(uCzE)
|
||||
.try_for_each(|(a, b)| a.enforce_congruent::<F>(&b))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct R1CSVar<F: PrimeField, CF: PrimeField, FV: AllocVar<F, CF>> {
|
||||
_f: PhantomData<F>,
|
||||
_cf: PhantomData<CF>,
|
||||
_fv: PhantomData<FV>,
|
||||
pub A: SparseMatrixVar<F, CF, FV>,
|
||||
pub B: SparseMatrixVar<F, CF, FV>,
|
||||
pub C: SparseMatrixVar<F, CF, FV>,
|
||||
}
|
||||
|
||||
impl<F, CF, FV> AllocVar<R1CS<F>, CF> for R1CSVar<F, CF, FV>
|
||||
where
|
||||
F: PrimeField,
|
||||
CF: PrimeField,
|
||||
FV: AllocVar<F, CF>,
|
||||
{
|
||||
fn new_variable<T: Borrow<R1CS<F>>>(
|
||||
cs: impl Into<Namespace<CF>>,
|
||||
f: impl FnOnce() -> Result<T, SynthesisError>,
|
||||
_mode: AllocationMode,
|
||||
) -> Result<Self, SynthesisError> {
|
||||
f().and_then(|val| {
|
||||
let cs = cs.into();
|
||||
|
||||
let A = SparseMatrixVar::<F, CF, FV>::new_constant(cs.clone(), &val.borrow().A)?;
|
||||
let B = SparseMatrixVar::<F, CF, FV>::new_constant(cs.clone(), &val.borrow().B)?;
|
||||
let C = SparseMatrixVar::<F, CF, FV>::new_constant(cs.clone(), &val.borrow().C)?;
|
||||
|
||||
Ok(Self {
|
||||
_f: PhantomData,
|
||||
_cf: PhantomData,
|
||||
_fv: PhantomData,
|
||||
A,
|
||||
B,
|
||||
C,
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// In-circuit representation of the Witness associated to the CommittedInstance.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct WitnessVar<C: CurveGroup> {
|
||||
@@ -166,82 +78,40 @@ where
|
||||
|
||||
impl<C: CurveGroup> WitnessVarOps<C::ScalarField> for WitnessVar<C> {
|
||||
fn get_openings(&self) -> Vec<(&[FpVar<C::ScalarField>], FpVar<C::ScalarField>)> {
|
||||
vec![(&self.E, self.rE.clone()), (&self.W, self.rW.clone())]
|
||||
vec![(&self.W, self.rW.clone()), (&self.E, self.rE.clone())]
|
||||
}
|
||||
}
|
||||
|
||||
/// Circuit that implements the in-circuit checks needed for the onchain (Ethereum's EVM)
|
||||
/// verification.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct DeciderEthCircuit<C1, GC1, C2, GC2, CS1, CS2, const H: bool = false>
|
||||
where
|
||||
C1: CurveGroup,
|
||||
GC1: CurveVar<C1, CF2<C1>>,
|
||||
C2: CurveGroup,
|
||||
GC2: CurveVar<C2, CF2<C2>>,
|
||||
CS1: CommitmentScheme<C1, H>,
|
||||
CS2: CommitmentScheme<C2, H>,
|
||||
{
|
||||
_c1: PhantomData<C1>,
|
||||
_gc1: PhantomData<GC1>,
|
||||
_c2: PhantomData<C2>,
|
||||
_gc2: PhantomData<GC2>,
|
||||
_cs1: PhantomData<CS1>,
|
||||
_cs2: PhantomData<CS2>,
|
||||
pub type DeciderEthCircuit<C1, C2, GC2> = GenericOnchainDeciderCircuit<
|
||||
C1,
|
||||
C2,
|
||||
GC2,
|
||||
CommittedInstance<C1>,
|
||||
CommittedInstance<C1>,
|
||||
Witness<C1>,
|
||||
R1CS<CF1<C1>>,
|
||||
R1CSMatricesVar<CF1<C1>, FpVar<CF1<C1>>>,
|
||||
DeciderNovaGadget,
|
||||
>;
|
||||
|
||||
/// E vector's length of the Nova instance witness
|
||||
pub E_len: usize,
|
||||
/// E vector's length of the CycleFold instance witness
|
||||
pub cf_E_len: usize,
|
||||
/// R1CS of the Augmented Function circuit
|
||||
pub r1cs: R1CS<C1::ScalarField>,
|
||||
/// R1CS of the CycleFold circuit
|
||||
pub cf_r1cs: R1CS<C2::ScalarField>,
|
||||
/// CycleFold PedersenParams over C2
|
||||
pub cf_pedersen_params: PedersenParams<C2>,
|
||||
pub poseidon_config: PoseidonConfig<CF1<C1>>,
|
||||
/// public params hash
|
||||
pub pp_hash: Option<C1::ScalarField>,
|
||||
pub i: Option<CF1<C1>>,
|
||||
/// initial state
|
||||
pub z_0: Option<Vec<C1::ScalarField>>,
|
||||
/// current i-th state
|
||||
pub z_i: Option<Vec<C1::ScalarField>>,
|
||||
/// Nova instances
|
||||
pub u_i: Option<CommittedInstance<C1>>,
|
||||
pub w_i: Option<Witness<C1>>,
|
||||
pub U_i: Option<CommittedInstance<C1>>,
|
||||
pub W_i: Option<Witness<C1>>,
|
||||
pub U_i1: Option<CommittedInstance<C1>>,
|
||||
pub W_i1: Option<Witness<C1>>,
|
||||
pub cmT: Option<C1>,
|
||||
pub r: Option<C1::ScalarField>,
|
||||
/// CycleFold running instance
|
||||
pub cf_U_i: Option<CycleFoldCommittedInstance<C2>>,
|
||||
pub cf_W_i: Option<CycleFoldWitness<C2>>,
|
||||
|
||||
/// KZG challenges
|
||||
pub kzg_c_W: Option<C1::ScalarField>,
|
||||
pub kzg_c_E: Option<C1::ScalarField>,
|
||||
pub eval_W: Option<C1::ScalarField>,
|
||||
pub eval_E: Option<C1::ScalarField>,
|
||||
}
|
||||
impl<C1, GC1, C2, GC2, CS1, CS2, const H: bool> DeciderEthCircuit<C1, GC1, C2, GC2, CS1, CS2, H>
|
||||
/// returns an instance of the DeciderEthCircuit from the given Nova struct
|
||||
impl<
|
||||
C1: CurveGroup,
|
||||
GC1: CurveVar<C1, CF2<C1>> + ToConstraintFieldGadget<CF2<C1>>,
|
||||
C2: CurveGroup,
|
||||
GC2: CurveVar<C2, CF2<C2>> + ToConstraintFieldGadget<CF2<C2>>,
|
||||
FC: FCircuit<C1::ScalarField>,
|
||||
CS1: CommitmentScheme<C1, H>,
|
||||
// enforce that the CS2 is Pedersen commitment scheme, since we're at Ethereum's EVM decider
|
||||
CS2: CommitmentScheme<C2, H, ProverParams = PedersenParams<C2>>,
|
||||
const H: bool,
|
||||
> TryFrom<Nova<C1, GC1, C2, GC2, FC, CS1, CS2, H>> for DeciderEthCircuit<C1, C2, GC2>
|
||||
where
|
||||
C1: CurveGroup,
|
||||
C2: CurveGroup,
|
||||
GC1: CurveVar<C1, CF2<C1>> + ToConstraintFieldGadget<CF2<C1>>,
|
||||
GC2: CurveVar<C2, CF2<C2>> + ToConstraintFieldGadget<CF2<C2>>,
|
||||
CS1: CommitmentScheme<C1, H>,
|
||||
// enforce that the CS2 is Pedersen commitment scheme, since we're at Ethereum's EVM decider
|
||||
CS2: CommitmentScheme<C2, H, ProverParams = PedersenParams<C2>>,
|
||||
<C1 as Group>::ScalarField: Absorb,
|
||||
<C1 as CurveGroup>::BaseField: PrimeField,
|
||||
CF1<C1>: Absorb,
|
||||
{
|
||||
/// returns an instance of the DeciderEthCircuit from the given Nova struct
|
||||
pub fn from_nova<FC: FCircuit<C1::ScalarField>>(
|
||||
nova: Nova<C1, GC1, C2, GC2, FC, CS1, CS2, H>,
|
||||
) -> Result<Self, Error> {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(nova: Nova<C1, GC1, C2, GC2, FC, CS1, CS2, H>) -> Result<Self, Error> {
|
||||
let mut transcript = PoseidonSponge::<C1::ScalarField>::new(&nova.poseidon_config);
|
||||
|
||||
// compute the U_{i+1}, W_{i+1}
|
||||
@@ -259,517 +129,116 @@ where
|
||||
.ok_or(Error::OutOfBounds)?;
|
||||
|
||||
// compute the KZG challenges used as inputs in the circuit
|
||||
let (kzg_challenge_W, kzg_challenge_E) =
|
||||
KZGChallengesGadget::<C1>::get_challenges_native(&mut transcript, U_i1.clone());
|
||||
let kzg_challenges = KZGChallengesGadget::get_challenges_native(&mut transcript, &U_i1);
|
||||
|
||||
// get KZG evals
|
||||
let mut W = W_i1.W.clone();
|
||||
W.extend(
|
||||
std::iter::repeat(C1::ScalarField::zero())
|
||||
.take(W_i1.W.len().next_power_of_two() - W_i1.W.len()),
|
||||
);
|
||||
let mut E = W_i1.E.clone();
|
||||
E.extend(
|
||||
std::iter::repeat(C1::ScalarField::zero())
|
||||
.take(W_i1.E.len().next_power_of_two() - W_i1.E.len()),
|
||||
);
|
||||
let p_W = poly_from_vec(W.to_vec())?;
|
||||
let eval_W = p_W.evaluate(&kzg_challenge_W);
|
||||
let p_E = poly_from_vec(E.to_vec())?;
|
||||
let eval_E = p_E.evaluate(&kzg_challenge_E);
|
||||
let kzg_evaluations = W_i1
|
||||
.get_openings()
|
||||
.iter()
|
||||
.zip(&kzg_challenges)
|
||||
.map(|((v, _), &c)| EvalGadget::evaluate_native(v, c))
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
Ok(Self {
|
||||
_c1: PhantomData,
|
||||
_gc1: PhantomData,
|
||||
_c2: PhantomData,
|
||||
_gc2: PhantomData,
|
||||
_cs1: PhantomData,
|
||||
_cs2: PhantomData,
|
||||
|
||||
E_len: nova.W_i.E.len(),
|
||||
cf_E_len: nova.cf_W_i.E.len(),
|
||||
r1cs: nova.r1cs,
|
||||
cf_r1cs: nova.cf_r1cs,
|
||||
_avar: PhantomData,
|
||||
arith: nova.r1cs,
|
||||
cf_arith: nova.cf_r1cs,
|
||||
cf_pedersen_params: nova.cf_cs_pp,
|
||||
poseidon_config: nova.poseidon_config,
|
||||
pp_hash: Some(nova.pp_hash),
|
||||
i: Some(nova.i),
|
||||
z_0: Some(nova.z_0),
|
||||
z_i: Some(nova.z_i),
|
||||
u_i: Some(nova.u_i),
|
||||
w_i: Some(nova.w_i),
|
||||
U_i: Some(nova.U_i),
|
||||
W_i: Some(nova.W_i),
|
||||
U_i1: Some(U_i1),
|
||||
W_i1: Some(W_i1),
|
||||
cmT: Some(cmT),
|
||||
r: Some(r_Fr),
|
||||
cf_U_i: Some(nova.cf_U_i),
|
||||
cf_W_i: Some(nova.cf_W_i),
|
||||
kzg_c_W: Some(kzg_challenge_W),
|
||||
kzg_c_E: Some(kzg_challenge_E),
|
||||
eval_W: Some(eval_W),
|
||||
eval_E: Some(eval_E),
|
||||
pp_hash: nova.pp_hash,
|
||||
i: nova.i,
|
||||
z_0: nova.z_0,
|
||||
z_i: nova.z_i,
|
||||
U_i: nova.U_i,
|
||||
W_i: nova.W_i,
|
||||
u_i: nova.u_i,
|
||||
w_i: nova.w_i,
|
||||
U_i1,
|
||||
W_i1,
|
||||
proof: cmT,
|
||||
randomness: r_Fr,
|
||||
cf_U_i: nova.cf_U_i,
|
||||
cf_W_i: nova.cf_W_i,
|
||||
kzg_challenges,
|
||||
kzg_evaluations,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<C1, GC1, C2, GC2, CS1, CS2> ConstraintSynthesizer<CF1<C1>>
|
||||
for DeciderEthCircuit<C1, GC1, C2, GC2, CS1, CS2>
|
||||
pub struct DeciderNovaGadget;
|
||||
|
||||
impl<C: CurveGroup>
|
||||
DeciderEnabledNIFS<C, CommittedInstance<C>, CommittedInstance<C>, Witness<C>, R1CS<CF1<C>>>
|
||||
for DeciderNovaGadget
|
||||
where
|
||||
C1: CurveGroup,
|
||||
C2: CurveGroup,
|
||||
GC1: CurveVar<C1, CF2<C1>>,
|
||||
GC2: CurveVar<C2, CF2<C2>> + ToConstraintFieldGadget<CF2<C2>>,
|
||||
CS1: CommitmentScheme<C1>,
|
||||
CS2: CommitmentScheme<C2>,
|
||||
<C1 as CurveGroup>::BaseField: PrimeField,
|
||||
<C2 as CurveGroup>::BaseField: PrimeField,
|
||||
<C1 as Group>::ScalarField: Absorb,
|
||||
<C2 as Group>::ScalarField: Absorb,
|
||||
C1: CurveGroup<BaseField = C2::ScalarField, ScalarField = C2::BaseField>,
|
||||
for<'b> &'b GC2: GroupOpsBounds<'b, C2, GC2>,
|
||||
CF1<C>: Absorb,
|
||||
{
|
||||
fn generate_constraints(self, cs: ConstraintSystemRef<CF1<C1>>) -> Result<(), SynthesisError> {
|
||||
let r1cs =
|
||||
R1CSVar::<C1::ScalarField, CF1<C1>, FpVar<CF1<C1>>>::new_witness(cs.clone(), || {
|
||||
Ok(self.r1cs.clone())
|
||||
})?;
|
||||
type ProofDummyCfg = ();
|
||||
type Proof = C;
|
||||
type RandomnessDummyCfg = ();
|
||||
type Randomness = CF1<C>;
|
||||
|
||||
let pp_hash = FpVar::<CF1<C1>>::new_input(cs.clone(), || {
|
||||
Ok(self.pp_hash.unwrap_or_else(CF1::<C1>::zero))
|
||||
})?;
|
||||
let i =
|
||||
FpVar::<CF1<C1>>::new_input(cs.clone(), || Ok(self.i.unwrap_or_else(CF1::<C1>::zero)))?;
|
||||
let z_0 = Vec::<FpVar<CF1<C1>>>::new_input(cs.clone(), || {
|
||||
Ok(self.z_0.unwrap_or(vec![CF1::<C1>::zero()]))
|
||||
})?;
|
||||
let z_i = Vec::<FpVar<CF1<C1>>>::new_input(cs.clone(), || {
|
||||
Ok(self.z_i.unwrap_or(vec![CF1::<C1>::zero()]))
|
||||
})?;
|
||||
|
||||
let u_dummy_native = CommittedInstance::<C1>::dummy(&self.r1cs);
|
||||
let w_dummy_native = Witness::<C1>::dummy(&self.r1cs);
|
||||
|
||||
let u_i = CommittedInstanceVar::<C1>::new_witness(cs.clone(), || {
|
||||
Ok(self.u_i.unwrap_or(u_dummy_native.clone()))
|
||||
})?;
|
||||
let U_i = CommittedInstanceVar::<C1>::new_witness(cs.clone(), || {
|
||||
Ok(self.U_i.unwrap_or(u_dummy_native.clone()))
|
||||
})?;
|
||||
// here (U_i1, W_i1) = NIFS.P( (U_i,W_i), (u_i,w_i))
|
||||
let U_i1 = CommittedInstanceVar::<C1>::new_input(cs.clone(), || {
|
||||
Ok(self.U_i1.unwrap_or(u_dummy_native.clone()))
|
||||
})?;
|
||||
let W_i1 = WitnessVar::<C1>::new_witness(cs.clone(), || {
|
||||
Ok(self.W_i1.unwrap_or(w_dummy_native.clone()))
|
||||
})?;
|
||||
|
||||
// allocate the inputs for the check 5.1
|
||||
let kzg_c_W = FpVar::<CF1<C1>>::new_input(cs.clone(), || {
|
||||
Ok(self.kzg_c_W.unwrap_or_else(CF1::<C1>::zero))
|
||||
})?;
|
||||
let kzg_c_E = FpVar::<CF1<C1>>::new_input(cs.clone(), || {
|
||||
Ok(self.kzg_c_E.unwrap_or_else(CF1::<C1>::zero))
|
||||
})?;
|
||||
// allocate the inputs for the check 5.2
|
||||
let eval_W = FpVar::<CF1<C1>>::new_input(cs.clone(), || {
|
||||
Ok(self.eval_W.unwrap_or_else(CF1::<C1>::zero))
|
||||
})?;
|
||||
let eval_E = FpVar::<CF1<C1>>::new_input(cs.clone(), || {
|
||||
Ok(self.eval_E.unwrap_or_else(CF1::<C1>::zero))
|
||||
})?;
|
||||
|
||||
// `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();
|
||||
|
||||
// The following enumeration of the steps matches the one used at the documentation page
|
||||
// https://privacy-scaling-explorations.github.io/sonobe-docs/design/nova-decider-onchain.html
|
||||
|
||||
// 2. u_i.cmE==cm(0), u_i.u==1
|
||||
// Here zero is the x & y coordinates of the zero point affine representation.
|
||||
let zero = NonNativeUintVar::new_constant(cs.clone(), C1::BaseField::zero())?;
|
||||
u_i.cmE.x.enforce_equal_unaligned(&zero)?;
|
||||
u_i.cmE.y.enforce_equal_unaligned(&zero)?;
|
||||
(u_i.u.is_one()?).enforce_equal(&Boolean::TRUE)?;
|
||||
|
||||
// 3.a u_i.x[0] == H(i, z_0, z_i, U_i)
|
||||
let (u_i_x, U_i_vec) = U_i.clone().hash(&sponge, &pp_hash, &i, &z_0, &z_i)?;
|
||||
(u_i.x[0]).enforce_equal(&u_i_x)?;
|
||||
|
||||
// 4. check RelaxedR1CS of U_{i+1}
|
||||
let z_U1: Vec<FpVar<CF1<C1>>> =
|
||||
[vec![U_i1.u.clone()], U_i1.x.to_vec(), W_i1.W.to_vec()].concat();
|
||||
RelaxedR1CSGadget::check_native(r1cs, W_i1.E.clone(), U_i1.u.clone(), z_U1)?;
|
||||
|
||||
#[cfg(feature = "light-test")]
|
||||
log::warn!("[WARNING]: Running with the 'light-test' feature, skipping the big part of the DeciderEthCircuit.\n Only for testing purposes.");
|
||||
|
||||
// The following two checks (and their respective allocations) are disabled for normal
|
||||
// tests since they take several millions of constraints and would take several minutes
|
||||
// (and RAM) to run the test. It is active by default, and not active only when
|
||||
// 'light-test' feature is used.
|
||||
#[cfg(not(feature = "light-test"))]
|
||||
{
|
||||
// imports here instead of at the top of the file, so we avoid having multiple
|
||||
// `#[cfg(not(test))]`
|
||||
use crate::commitment::pedersen::PedersenGadget;
|
||||
use crate::folding::{
|
||||
circuits::cyclefold::{
|
||||
CycleFoldCommittedInstanceVar, CycleFoldConfig, CycleFoldWitnessVar,
|
||||
},
|
||||
nova::NovaCycleFoldConfig,
|
||||
};
|
||||
use ark_r1cs_std::ToBitsGadget;
|
||||
|
||||
let cf_u_dummy_native =
|
||||
CycleFoldCommittedInstance::<C2>::dummy(NovaCycleFoldConfig::<C1>::IO_LEN);
|
||||
let w_dummy_native = CycleFoldWitness::<C2>::dummy(&self.cf_r1cs);
|
||||
let cf_U_i = CycleFoldCommittedInstanceVar::<C2, GC2>::new_witness(cs.clone(), || {
|
||||
Ok(self.cf_U_i.unwrap_or_else(|| cf_u_dummy_native.clone()))
|
||||
})?;
|
||||
let cf_W_i = CycleFoldWitnessVar::<C2>::new_witness(cs.clone(), || {
|
||||
Ok(self.cf_W_i.unwrap_or(w_dummy_native.clone()))
|
||||
})?;
|
||||
|
||||
// 3.b u_i.x[1] == H(cf_U_i)
|
||||
let (cf_u_i_x, _) = cf_U_i.clone().hash(&sponge, pp_hash.clone())?;
|
||||
(u_i.x[1]).enforce_equal(&cf_u_i_x)?;
|
||||
|
||||
// 7. check Pedersen commitments of cf_U_i.{cmE, cmW}
|
||||
let H = GC2::new_constant(cs.clone(), self.cf_pedersen_params.h)?;
|
||||
let G = Vec::<GC2>::new_constant(cs.clone(), self.cf_pedersen_params.generators)?;
|
||||
let cf_W_i_E_bits: Result<Vec<Vec<Boolean<CF1<C1>>>>, SynthesisError> =
|
||||
cf_W_i.E.iter().map(|E_i| E_i.to_bits_le()).collect();
|
||||
let cf_W_i_W_bits: Result<Vec<Vec<Boolean<CF1<C1>>>>, SynthesisError> =
|
||||
cf_W_i.W.iter().map(|W_i| W_i.to_bits_le()).collect();
|
||||
|
||||
let computed_cmE = PedersenGadget::<C2, GC2>::commit(
|
||||
H.clone(),
|
||||
G.clone(),
|
||||
cf_W_i_E_bits?,
|
||||
cf_W_i.rE.to_bits_le()?,
|
||||
)?;
|
||||
cf_U_i.cmE.enforce_equal(&computed_cmE)?;
|
||||
let computed_cmW =
|
||||
PedersenGadget::<C2, GC2>::commit(H, G, cf_W_i_W_bits?, cf_W_i.rW.to_bits_le()?)?;
|
||||
cf_U_i.cmW.enforce_equal(&computed_cmW)?;
|
||||
|
||||
let cf_r1cs =
|
||||
R1CSVar::<C1::BaseField, CF1<C1>, NonNativeUintVar<CF1<C1>>>::new_witness(
|
||||
cs.clone(),
|
||||
|| Ok(self.cf_r1cs.clone()),
|
||||
)?;
|
||||
|
||||
// 6. check RelaxedR1CS of cf_U_i (CycleFold instance)
|
||||
let cf_z_U = [vec![cf_U_i.u.clone()], cf_U_i.x.to_vec(), cf_W_i.W.to_vec()].concat();
|
||||
RelaxedR1CSGadget::check_nonnative(cf_r1cs, cf_W_i.E, cf_U_i.u.clone(), cf_z_U)?;
|
||||
}
|
||||
|
||||
// 1.1.a, 5.1. compute NIFS.V and KZG challenges.
|
||||
// We need to ensure the order of challenge generation is the same as
|
||||
// the native counterpart, so we first compute the challenges here and
|
||||
// do the actual checks later.
|
||||
let cmT =
|
||||
NonNativeAffineVar::new_input(cs.clone(), || Ok(self.cmT.unwrap_or_else(C1::zero)))?;
|
||||
// 1.1.a
|
||||
let r_bits = ChallengeGadget::<C1, CommittedInstance<C1>>::get_challenge_gadget(
|
||||
&mut transcript,
|
||||
fn fold_field_elements_gadget(
|
||||
_arith: &R1CS<CF1<C>>,
|
||||
transcript: &mut PoseidonSpongeVar<CF1<C>>,
|
||||
pp_hash: FpVar<CF1<C>>,
|
||||
U: CommittedInstanceVar<C>,
|
||||
U_vec: Vec<FpVar<CF1<C>>>,
|
||||
u: CommittedInstanceVar<C>,
|
||||
proof: C,
|
||||
randomness: CF1<C>,
|
||||
) -> Result<CommittedInstanceVar<C>, SynthesisError> {
|
||||
let cs = transcript.cs();
|
||||
let cmT = NonNativeAffineVar::new_input(cs.clone(), || Ok(proof))?;
|
||||
let r = FpVar::new_input(cs.clone(), || Ok(randomness))?;
|
||||
let r_bits = ChallengeGadget::<C, CommittedInstance<C>>::get_challenge_gadget(
|
||||
transcript,
|
||||
pp_hash,
|
||||
U_i_vec,
|
||||
u_i.clone(),
|
||||
U_vec,
|
||||
u.clone(),
|
||||
Some(cmT),
|
||||
)?;
|
||||
// 5.1.
|
||||
let (incircuit_c_W, incircuit_c_E) =
|
||||
KZGChallengesGadget::<C1>::get_challenges_gadget(&mut transcript, U_i1.clone())?;
|
||||
incircuit_c_W.enforce_equal(&kzg_c_W)?;
|
||||
incircuit_c_E.enforce_equal(&kzg_c_E)?;
|
||||
Boolean::le_bits_to_fp_var(&r_bits)?.enforce_equal(&r)?;
|
||||
|
||||
// 5.2. check eval_W==p_W(c_W) and eval_E==p_E(c_E)
|
||||
let incircuit_eval_W = evaluate_gadget::<CF1<C1>>(W_i1.W, incircuit_c_W)?;
|
||||
let incircuit_eval_E = evaluate_gadget::<CF1<C1>>(W_i1.E, incircuit_c_E)?;
|
||||
incircuit_eval_W.enforce_equal(&eval_W)?;
|
||||
incircuit_eval_E.enforce_equal(&eval_E)?;
|
||||
|
||||
// 1.1.b check that the NIFS.V challenge matches the one from the public input (so we avoid
|
||||
// the verifier computing it)
|
||||
let r_Fr = Boolean::le_bits_to_fp_var(&r_bits)?;
|
||||
// check that the in-circuit computed r is equal to the inputted r
|
||||
let r =
|
||||
FpVar::<CF1<C1>>::new_input(cs.clone(), || Ok(self.r.unwrap_or_else(CF1::<C1>::zero)))?;
|
||||
r_Fr.enforce_equal(&r)?;
|
||||
|
||||
Ok(())
|
||||
NIFSGadget::<C>::fold_committed_instance(r, U, u)
|
||||
}
|
||||
}
|
||||
|
||||
/// Interpolates the polynomial from the given vector, and then returns it's evaluation at the
|
||||
/// given point.
|
||||
#[allow(unused)] // unused while check 7 is disabled
|
||||
pub fn evaluate_gadget<F: PrimeField>(
|
||||
mut v: Vec<FpVar<F>>,
|
||||
point: FpVar<F>,
|
||||
) -> Result<FpVar<F>, SynthesisError> {
|
||||
v.resize(v.len().next_power_of_two(), FpVar::zero());
|
||||
let n = v.len() as u64;
|
||||
let gen = F::get_root_of_unity(n).unwrap();
|
||||
let domain = Radix2DomainVar::new(gen, log2(v.len()) as u64, FpVar::one()).unwrap();
|
||||
|
||||
let evaluations_var = EvaluationsVar::from_vec_and_domain(v, domain, true);
|
||||
evaluations_var.interpolate_and_evaluate(&point)
|
||||
}
|
||||
|
||||
/// Gadget that computes the KZG challenges, also offers the rust native implementation compatible
|
||||
/// with the gadget.
|
||||
pub struct KZGChallengesGadget<C: CurveGroup> {
|
||||
_c: PhantomData<C>,
|
||||
}
|
||||
#[allow(clippy::type_complexity)]
|
||||
impl<C> KZGChallengesGadget<C>
|
||||
where
|
||||
C: CurveGroup,
|
||||
C::ScalarField: PrimeField,
|
||||
<C as CurveGroup>::BaseField: PrimeField,
|
||||
C::ScalarField: Absorb,
|
||||
{
|
||||
pub fn get_challenges_native<T: Transcript<C::ScalarField>>(
|
||||
transcript: &mut T,
|
||||
U_i: CommittedInstance<C>,
|
||||
) -> (C::ScalarField, C::ScalarField) {
|
||||
// compute the KZG challenges, which are computed in-circuit and checked that it matches
|
||||
// the inputted one
|
||||
transcript.absorb_nonnative(&U_i.cmW);
|
||||
let challenge_W = transcript.get_challenge();
|
||||
transcript.absorb_nonnative(&U_i.cmE);
|
||||
let challenge_E = transcript.get_challenge();
|
||||
|
||||
(challenge_W, challenge_E)
|
||||
}
|
||||
// compatible with the native get_challenges_native
|
||||
pub fn get_challenges_gadget<S: CryptographicSponge, T: TranscriptVar<CF1<C>, S>>(
|
||||
transcript: &mut T,
|
||||
U_i: CommittedInstanceVar<C>,
|
||||
) -> Result<(FpVar<C::ScalarField>, FpVar<C::ScalarField>), SynthesisError> {
|
||||
transcript.absorb(&U_i.cmW.to_constraint_field()?)?;
|
||||
let challenge_W = transcript.get_challenge()?;
|
||||
|
||||
transcript.absorb(&U_i.cmE.to_constraint_field()?)?;
|
||||
let challenge_E = transcript.get_challenge()?;
|
||||
|
||||
Ok((challenge_W, challenge_E))
|
||||
fn fold_group_elements_native(
|
||||
U_commitments: &[C],
|
||||
u_commitments: &[C],
|
||||
cmT: Option<Self::Proof>,
|
||||
r: Self::Randomness,
|
||||
) -> Result<Vec<C>, Error> {
|
||||
let cmT = cmT.ok_or(Error::Empty)?;
|
||||
let U_cmW = U_commitments[0];
|
||||
let U_cmE = U_commitments[1];
|
||||
let u_cmW = u_commitments[0];
|
||||
let u_cmE = u_commitments[1];
|
||||
if !u_cmE.is_zero() {
|
||||
return Err(Error::NotIncomingCommittedInstance);
|
||||
}
|
||||
let cmW = U_cmW + u_cmW.mul(r);
|
||||
let cmE = U_cmE + cmT.mul(r);
|
||||
Ok(vec![cmW, cmE])
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use std::cmp::max;
|
||||
|
||||
use ark_crypto_primitives::crh::{
|
||||
sha256::{
|
||||
constraints::{Sha256Gadget, UnitVar},
|
||||
Sha256,
|
||||
},
|
||||
CRHScheme, CRHSchemeGadget,
|
||||
};
|
||||
use ark_pallas::{constraints::GVar, Fq, Fr, Projective};
|
||||
use ark_r1cs_std::bits::uint8::UInt8;
|
||||
use ark_relations::r1cs::ConstraintSystem;
|
||||
use ark_std::{
|
||||
rand::{thread_rng, Rng},
|
||||
UniformRand,
|
||||
};
|
||||
use ark_pallas::{constraints::GVar, Fr, Projective};
|
||||
use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystem};
|
||||
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},
|
||||
},
|
||||
Arith,
|
||||
};
|
||||
use crate::commitment::pedersen::Pedersen;
|
||||
use crate::folding::nova::PreprocessorParam;
|
||||
use crate::frontend::utils::{CubicFCircuit, CustomFCircuit, WrapperCircuit};
|
||||
use crate::frontend::utils::CubicFCircuit;
|
||||
use crate::transcript::poseidon::poseidon_canonical_config;
|
||||
use crate::FoldingScheme;
|
||||
|
||||
// Convert `z` to a witness-instance pair for the relaxed R1CS
|
||||
fn prepare_relaxed_witness_instance<C: CurveGroup, CS: CommitmentScheme<C>, R: Rng>(
|
||||
mut rng: R,
|
||||
r1cs: &R1CS<C::ScalarField>,
|
||||
z: &[C::ScalarField],
|
||||
) -> (Witness<C>, CommittedInstance<C>) {
|
||||
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_at_z(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 mut z = get_test_z(3);
|
||||
z[0] = Fr::rand(rng); // Randomize `z[0]` (i.e. `u.u`) to test the relaxed R1CS
|
||||
let (w, u) = prepare_relaxed_witness_instance::<_, 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(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();
|
||||
assert!(cs.is_satisfied().unwrap());
|
||||
}
|
||||
|
||||
// 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();
|
||||
cs.finalize();
|
||||
assert!(cs.is_satisfied().unwrap());
|
||||
|
||||
let cs = cs.into_inner().unwrap();
|
||||
|
||||
let r1cs = extract_r1cs::<Fr>(&cs);
|
||||
let (w, x) = extract_w_x::<Fr>(&cs);
|
||||
r1cs.check_relation(&w, &x).unwrap();
|
||||
|
||||
let z = [vec![Fr::rand(rng)], x, w].concat();
|
||||
let (w, u) = prepare_relaxed_witness_instance::<_, Pedersen<Projective>, _>(rng, &r1cs, &z);
|
||||
r1cs.check_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(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();
|
||||
assert!(cs.is_satisfied().unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_relaxed_r1cs_small_gadget_arkworks() {
|
||||
let z_i = vec![Fr::from(3_u32)];
|
||||
let cubic_circuit = CubicFCircuit::<Fr>::new(()).unwrap();
|
||||
let circuit = WrapperCircuit::<Fr, CubicFCircuit<Fr>> {
|
||||
FC: cubic_circuit,
|
||||
z_i: Some(z_i.clone()),
|
||||
z_i1: Some(cubic_circuit.step_native(0, z_i, vec![]).unwrap()),
|
||||
};
|
||||
|
||||
test_relaxed_r1cs_gadget(circuit);
|
||||
}
|
||||
|
||||
struct Sha256TestCircuit<F: PrimeField> {
|
||||
_f: PhantomData<F>,
|
||||
pub x: Vec<u8>,
|
||||
pub y: Vec<u8>,
|
||||
}
|
||||
impl<F: PrimeField> ConstraintSynthesizer<F> for Sha256TestCircuit<F> {
|
||||
fn generate_constraints(self, cs: ConstraintSystemRef<F>) -> Result<(), SynthesisError> {
|
||||
let x = Vec::<UInt8<F>>::new_witness(cs.clone(), || Ok(self.x))?;
|
||||
let y = Vec::<UInt8<F>>::new_input(cs.clone(), || Ok(self.y))?;
|
||||
|
||||
let unitVar = UnitVar::default();
|
||||
let comp_y = <Sha256Gadget<F> as CRHSchemeGadget<Sha256, F>>::evaluate(&unitVar, &x)?;
|
||||
comp_y.0.enforce_equal(&y)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
fn test_relaxed_r1cs_medium_gadget_arkworks() {
|
||||
let x = Fr::from(5_u32).into_bigint().to_bytes_le();
|
||||
let y = <Sha256 as CRHScheme>::evaluate(&(), x.clone()).unwrap();
|
||||
|
||||
let circuit = Sha256TestCircuit::<Fr> {
|
||||
_f: PhantomData,
|
||||
x,
|
||||
y,
|
||||
};
|
||||
test_relaxed_r1cs_gadget(circuit);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_relaxed_r1cs_custom_circuit() {
|
||||
let n_constraints = 10_000;
|
||||
let custom_circuit = CustomFCircuit::<Fr>::new(n_constraints).unwrap();
|
||||
let z_i = vec![Fr::from(5_u32)];
|
||||
let circuit = WrapperCircuit::<Fr, CustomFCircuit<Fr>> {
|
||||
FC: custom_circuit,
|
||||
z_i: Some(z_i.clone()),
|
||||
z_i1: Some(custom_circuit.step_native(0, z_i, vec![]).unwrap()),
|
||||
};
|
||||
test_relaxed_r1cs_gadget(circuit);
|
||||
}
|
||||
|
||||
#[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
|
||||
// custom circuit.
|
||||
let custom_circuit = CustomFCircuit::<Fq>::new(10).unwrap();
|
||||
let z_i = vec![Fq::from(5_u32)];
|
||||
let circuit = WrapperCircuit::<Fq, CustomFCircuit<Fq>> {
|
||||
FC: custom_circuit,
|
||||
z_i: Some(z_i.clone()),
|
||||
z_i1: Some(custom_circuit.step_native(0, z_i, vec![]).unwrap()),
|
||||
};
|
||||
circuit.generate_constraints(cs.clone()).unwrap();
|
||||
cs.finalize();
|
||||
let cs = cs.into_inner().unwrap();
|
||||
let r1cs = extract_r1cs::<Fq>(&cs);
|
||||
let (w, x) = extract_w_x::<Fq>(&cs);
|
||||
|
||||
let z = [vec![Fq::rand(rng)], x, w].concat();
|
||||
let (w, u) =
|
||||
prepare_relaxed_witness_instance::<_, 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(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();
|
||||
|
||||
// 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(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();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decider_eth_circuit() {
|
||||
fn test_decider_circuit() {
|
||||
let mut rng = ark_std::test_rng();
|
||||
let poseidon_config = poseidon_canonical_config::<Fr>();
|
||||
|
||||
@@ -803,84 +272,14 @@ pub mod tests {
|
||||
let ivc_proof = nova.ivc_proof();
|
||||
N::verify(nova_params.1, ivc_proof).unwrap();
|
||||
|
||||
// load the DeciderEthCircuit from the Nova instance
|
||||
let decider_eth_circuit = DeciderEthCircuit::<
|
||||
Projective,
|
||||
GVar,
|
||||
Projective2,
|
||||
GVar2,
|
||||
Pedersen<Projective>,
|
||||
Pedersen<Projective2>,
|
||||
>::from_nova(nova)
|
||||
.unwrap();
|
||||
// load the DeciderEthCircuit from the generated Nova instance
|
||||
let decider_circuit =
|
||||
DeciderEthCircuit::<Projective, Projective2, GVar2>::try_from(nova).unwrap();
|
||||
|
||||
let cs = ConstraintSystem::<Fr>::new_ref();
|
||||
|
||||
// generate the constraints and check that are satisfied by the inputs
|
||||
decider_eth_circuit
|
||||
.generate_constraints(cs.clone())
|
||||
.unwrap();
|
||||
assert!(cs.is_satisfied().unwrap());
|
||||
}
|
||||
|
||||
// checks that the gadget and native implementations of the challenge computation match
|
||||
#[test]
|
||||
fn test_kzg_challenge_gadget() {
|
||||
let mut rng = ark_std::test_rng();
|
||||
let poseidon_config = poseidon_canonical_config::<Fr>();
|
||||
let mut transcript = PoseidonSponge::<Fr>::new(&poseidon_config);
|
||||
|
||||
let U_i = CommittedInstance::<Projective> {
|
||||
cmE: Projective::rand(&mut rng),
|
||||
u: Fr::rand(&mut rng),
|
||||
cmW: Projective::rand(&mut rng),
|
||||
x: vec![Fr::rand(&mut rng); 1],
|
||||
};
|
||||
|
||||
// compute the challenge natively
|
||||
let (challenge_W, challenge_E) =
|
||||
KZGChallengesGadget::<Projective>::get_challenges_native(&mut transcript, U_i.clone());
|
||||
|
||||
let cs = ConstraintSystem::<Fr>::new_ref();
|
||||
let U_iVar =
|
||||
CommittedInstanceVar::<Projective>::new_witness(cs.clone(), || Ok(U_i.clone()))
|
||||
.unwrap();
|
||||
let mut transcript_var = PoseidonSpongeVar::<Fr>::new(cs.clone(), &poseidon_config);
|
||||
|
||||
let (challenge_W_Var, challenge_E_Var) =
|
||||
KZGChallengesGadget::<Projective>::get_challenges_gadget(&mut transcript_var, U_iVar)
|
||||
.unwrap();
|
||||
assert!(cs.is_satisfied().unwrap());
|
||||
|
||||
// check that the natively computed and in-circuit computed hashes match
|
||||
use ark_r1cs_std::R1CSVar;
|
||||
assert_eq!(challenge_W_Var.value().unwrap(), challenge_W);
|
||||
assert_eq!(challenge_E_Var.value().unwrap(), challenge_E);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_polynomial_interpolation() {
|
||||
let mut rng = ark_std::test_rng();
|
||||
let n = 12;
|
||||
let l = 1 << n;
|
||||
|
||||
let v: Vec<Fr> = std::iter::repeat_with(|| Fr::rand(&mut rng))
|
||||
.take(l)
|
||||
.collect();
|
||||
let challenge = Fr::rand(&mut rng);
|
||||
|
||||
use ark_poly::Polynomial;
|
||||
let polynomial = poly_from_vec(v.to_vec()).unwrap();
|
||||
let eval = polynomial.evaluate(&challenge);
|
||||
|
||||
let cs = ConstraintSystem::<Fr>::new_ref();
|
||||
let vVar = Vec::<FpVar<Fr>>::new_witness(cs.clone(), || Ok(v)).unwrap();
|
||||
let challengeVar = FpVar::<Fr>::new_witness(cs.clone(), || Ok(challenge)).unwrap();
|
||||
|
||||
let evalVar = evaluate_gadget::<Fr>(vVar, challengeVar).unwrap();
|
||||
|
||||
use ark_r1cs_std::R1CSVar;
|
||||
assert_eq!(evalVar.value().unwrap(), eval);
|
||||
decider_circuit.generate_constraints(cs.clone()).unwrap();
|
||||
assert!(cs.is_satisfied().unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ use ark_crypto_primitives::sponge::{
|
||||
};
|
||||
use ark_ec::{CurveGroup, Group};
|
||||
use ark_ff::{BigInteger, PrimeField};
|
||||
use ark_r1cs_std::{groups::GroupOpsBounds, prelude::CurveVar, ToConstraintFieldGadget};
|
||||
use ark_r1cs_std::{prelude::CurveVar, ToConstraintFieldGadget};
|
||||
use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystem};
|
||||
use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, Valid};
|
||||
use ark_std::fmt::Debug;
|
||||
@@ -56,7 +56,7 @@ pub mod decider_circuits;
|
||||
pub mod decider_eth;
|
||||
pub mod decider_eth_circuit;
|
||||
|
||||
use super::traits::{CommittedInstanceOps, WitnessOps};
|
||||
use super::traits::{CommittedInstanceOps, Inputize, WitnessOps};
|
||||
|
||||
/// Configuration for Nova's CycleFold circuit
|
||||
pub struct NovaCycleFoldConfig<C: CurveGroup> {
|
||||
@@ -137,6 +137,18 @@ impl<C: CurveGroup> CommittedInstanceOps<C> for CommittedInstance<C> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: CurveGroup> Inputize<C::ScalarField, CommittedInstanceVar<C>> for CommittedInstance<C> {
|
||||
fn inputize(&self) -> Vec<C::ScalarField> {
|
||||
[
|
||||
&[self.u][..],
|
||||
&self.x,
|
||||
&self.cmE.inputize(),
|
||||
&self.cmW.inputize(),
|
||||
]
|
||||
.concat()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)]
|
||||
pub struct Witness<C: CurveGroup> {
|
||||
pub E: Vec<C::ScalarField>,
|
||||
@@ -477,8 +489,6 @@ where
|
||||
<C1 as Group>::ScalarField: Absorb,
|
||||
<C2 as Group>::ScalarField: Absorb,
|
||||
C1: CurveGroup<BaseField = C2::ScalarField, ScalarField = C2::BaseField>,
|
||||
for<'a> &'a GC1: GroupOpsBounds<'a, C1, GC1>,
|
||||
for<'a> &'a GC2: GroupOpsBounds<'a, C2, GC2>,
|
||||
{
|
||||
type PreprocessorParam = PreprocessorParam<C1, C2, FC, CS1, CS2, H>;
|
||||
type ProverParam = ProverParams<C1, C2, CS1, CS2, H>;
|
||||
@@ -518,7 +528,7 @@ where
|
||||
augmented_F_circuit.generate_constraints(cs.clone())?;
|
||||
cs.finalize();
|
||||
let cs = cs.into_inner().ok_or(Error::NoInnerConstraintSystem)?;
|
||||
let r1cs = extract_r1cs::<C1::ScalarField>(&cs);
|
||||
let r1cs = extract_r1cs::<C1::ScalarField>(&cs)?;
|
||||
|
||||
// CycleFold circuit R1CS
|
||||
let cs2 = ConstraintSystem::<C1::BaseField>::new_ref();
|
||||
@@ -526,7 +536,7 @@ where
|
||||
cf_circuit.generate_constraints(cs2.clone())?;
|
||||
cs2.finalize();
|
||||
let cs2 = cs2.into_inner().ok_or(Error::NoInnerConstraintSystem)?;
|
||||
let cf_r1cs = extract_r1cs::<C1::BaseField>(&cs2);
|
||||
let cf_r1cs = extract_r1cs::<C1::BaseField>(&cs2)?;
|
||||
|
||||
let cs_vp = CS1::VerifierParams::deserialize_with_mode(&mut reader, compress, validate)?;
|
||||
let cf_cs_vp = CS2::VerifierParams::deserialize_with_mode(&mut reader, compress, validate)?;
|
||||
@@ -548,23 +558,14 @@ where
|
||||
get_r1cs::<C1, GC1, C2, GC2, FC>(&prep_param.poseidon_config, prep_param.F.clone())?;
|
||||
|
||||
// if cs params exist, use them, if not, generate new ones
|
||||
let cs_pp: CS1::ProverParams;
|
||||
let cs_vp: CS1::VerifierParams;
|
||||
let cf_cs_pp: CS2::ProverParams;
|
||||
let cf_cs_vp: CS2::VerifierParams;
|
||||
if prep_param.cs_pp.is_some()
|
||||
&& prep_param.cf_cs_pp.is_some()
|
||||
&& prep_param.cs_vp.is_some()
|
||||
&& prep_param.cf_cs_vp.is_some()
|
||||
{
|
||||
cs_pp = prep_param.clone().cs_pp.unwrap();
|
||||
cs_vp = prep_param.clone().cs_vp.unwrap();
|
||||
cf_cs_pp = prep_param.clone().cf_cs_pp.unwrap();
|
||||
cf_cs_vp = prep_param.clone().cf_cs_vp.unwrap();
|
||||
} else {
|
||||
(cs_pp, cs_vp) = CS1::setup(&mut rng, r1cs.A.n_rows)?;
|
||||
(cf_cs_pp, cf_cs_vp) = CS2::setup(&mut rng, cf_r1cs.A.n_rows)?;
|
||||
}
|
||||
let (cs_pp, cs_vp) = match (&prep_param.cs_pp, &prep_param.cs_vp) {
|
||||
(Some(cs_pp), Some(cs_vp)) => (cs_pp.clone(), cs_vp.clone()),
|
||||
_ => CS1::setup(&mut rng, r1cs.A.n_rows)?,
|
||||
};
|
||||
let (cf_cs_pp, cf_cs_vp) = match (&prep_param.cf_cs_pp, &prep_param.cf_cs_vp) {
|
||||
(Some(cf_cs_pp), Some(cf_cs_vp)) => (cf_cs_pp.clone(), cf_cs_vp.clone()),
|
||||
_ => CS2::setup(&mut rng, cf_r1cs.A.n_rows)?,
|
||||
};
|
||||
|
||||
let prover_params = ProverParams::<C1, C2, CS1, CS2, H> {
|
||||
poseidon_config: prep_param.poseidon_config.clone(),
|
||||
@@ -601,12 +602,12 @@ where
|
||||
augmented_F_circuit.generate_constraints(cs.clone())?;
|
||||
cs.finalize();
|
||||
let cs = cs.into_inner().ok_or(Error::NoInnerConstraintSystem)?;
|
||||
let r1cs = extract_r1cs::<C1::ScalarField>(&cs);
|
||||
let r1cs = extract_r1cs::<C1::ScalarField>(&cs)?;
|
||||
|
||||
cf_circuit.generate_constraints(cs2.clone())?;
|
||||
cs2.finalize();
|
||||
let cs2 = cs2.into_inner().ok_or(Error::NoInnerConstraintSystem)?;
|
||||
let cf_r1cs = extract_r1cs::<C1::BaseField>(&cs2);
|
||||
let cf_r1cs = extract_r1cs::<C1::BaseField>(&cs2)?;
|
||||
|
||||
// compute the public params hash
|
||||
let pp_hash = vp.pp_hash()?;
|
||||
@@ -946,7 +947,7 @@ where
|
||||
} = ivc_proof;
|
||||
let (pp, vp) = params;
|
||||
|
||||
let f_circuit = FC::new(fcircuit_params).unwrap();
|
||||
let f_circuit = FC::new(fcircuit_params)?;
|
||||
let cs = ConstraintSystem::<C1::ScalarField>::new_ref();
|
||||
let cs2 = ConstraintSystem::<C1::BaseField>::new_ref();
|
||||
let augmented_F_circuit =
|
||||
@@ -956,12 +957,12 @@ where
|
||||
augmented_F_circuit.generate_constraints(cs.clone())?;
|
||||
cs.finalize();
|
||||
let cs = cs.into_inner().ok_or(Error::NoInnerConstraintSystem)?;
|
||||
let r1cs = extract_r1cs::<C1::ScalarField>(&cs);
|
||||
let r1cs = extract_r1cs::<C1::ScalarField>(&cs)?;
|
||||
|
||||
cf_circuit.generate_constraints(cs2.clone())?;
|
||||
cs2.finalize();
|
||||
let cs2 = cs2.into_inner().ok_or(Error::NoInnerConstraintSystem)?;
|
||||
let cf_r1cs = extract_r1cs::<C1::BaseField>(&cs2);
|
||||
let cf_r1cs = extract_r1cs::<C1::BaseField>(&cs2)?;
|
||||
|
||||
Ok(Self {
|
||||
_gc1: PhantomData,
|
||||
@@ -1056,8 +1057,6 @@ where
|
||||
<C1 as Group>::ScalarField: Absorb,
|
||||
<C2 as Group>::ScalarField: Absorb,
|
||||
C1: CurveGroup<BaseField = C2::ScalarField, ScalarField = C2::BaseField>,
|
||||
for<'a> &'a GC1: GroupOpsBounds<'a, C1, GC1>,
|
||||
for<'a> &'a GC2: GroupOpsBounds<'a, C2, GC2>,
|
||||
{
|
||||
// folds the given cyclefold circuit and its instances
|
||||
#[allow(clippy::type_complexity)]
|
||||
@@ -1102,7 +1101,7 @@ pub fn get_r1cs_from_cs<F: PrimeField>(
|
||||
circuit.generate_constraints(cs.clone())?;
|
||||
cs.finalize();
|
||||
let cs = cs.into_inner().ok_or(Error::NoInnerConstraintSystem)?;
|
||||
let r1cs = extract_r1cs::<F>(&cs);
|
||||
let r1cs = extract_r1cs::<F>(&cs)?;
|
||||
Ok(r1cs)
|
||||
}
|
||||
|
||||
@@ -1123,8 +1122,6 @@ where
|
||||
<C1 as Group>::ScalarField: Absorb,
|
||||
<C2 as Group>::ScalarField: Absorb,
|
||||
C1: CurveGroup<BaseField = C2::ScalarField, ScalarField = C2::BaseField>,
|
||||
for<'a> &'a GC1: GroupOpsBounds<'a, C1, GC1>,
|
||||
for<'a> &'a GC2: GroupOpsBounds<'a, C2, GC2>,
|
||||
{
|
||||
let augmented_F_circuit =
|
||||
AugmentedFCircuit::<C1, C2, GC2, FC>::empty(poseidon_config, F_circuit);
|
||||
@@ -1151,8 +1148,6 @@ where
|
||||
<C1 as Group>::ScalarField: Absorb,
|
||||
<C2 as Group>::ScalarField: Absorb,
|
||||
C1: CurveGroup<BaseField = C2::ScalarField, ScalarField = C2::BaseField>,
|
||||
for<'a> &'a GC1: GroupOpsBounds<'a, C1, GC1>,
|
||||
for<'a> &'a GC2: GroupOpsBounds<'a, C2, GC2>,
|
||||
{
|
||||
let (r1cs, cf_r1cs) = get_r1cs::<C1, GC1, C2, GC2, FC>(poseidon_config, F_circuit)?;
|
||||
Ok((r1cs.A.n_rows, cf_r1cs.A.n_rows))
|
||||
|
||||
@@ -35,8 +35,6 @@ impl<C: CurveGroup, CS: CommitmentScheme<C, H>, T: Transcript<C::ScalarField>, c
|
||||
NIFSTrait<C, CS, T, H> for NIFS<C, CS, T, H>
|
||||
where
|
||||
<C as Group>::ScalarField: Absorb,
|
||||
<C as CurveGroup>::BaseField: PrimeField,
|
||||
<C as Group>::ScalarField: PrimeField,
|
||||
{
|
||||
type CommittedInstance = CommittedInstance<C>;
|
||||
type Witness = Witness<C>;
|
||||
@@ -150,7 +148,6 @@ impl<C: CurveGroup, CS: CommitmentScheme<C, H>, T: Transcript<C::ScalarField>, c
|
||||
NIFS<C, CS, T, H>
|
||||
where
|
||||
<C as Group>::ScalarField: Absorb,
|
||||
<C as CurveGroup>::BaseField: PrimeField,
|
||||
{
|
||||
/// compute_T: compute cross-terms T
|
||||
pub fn compute_T(
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
use ark_ec::CurveGroup;
|
||||
use ark_r1cs_std::fields::fp::FpVar;
|
||||
use ark_relations::r1cs::SynthesisError;
|
||||
use ark_std::{rand::RngCore, UniformRand};
|
||||
|
||||
use super::circuits::CommittedInstanceVar;
|
||||
use super::decider_eth_circuit::WitnessVar;
|
||||
use super::{CommittedInstance, Witness};
|
||||
use crate::arith::ArithSampler;
|
||||
use crate::arith::{r1cs::R1CS, Arith};
|
||||
use crate::arith::{
|
||||
r1cs::{circuits::R1CSMatricesVar, R1CS},
|
||||
Arith, ArithGadget, ArithSampler,
|
||||
};
|
||||
use crate::commitment::CommitmentScheme;
|
||||
use crate::folding::circuits::CF1;
|
||||
use crate::utils::gadgets::{EquivalenceGadget, VectorGadget};
|
||||
use crate::Error;
|
||||
|
||||
/// Implements `Arith` for R1CS, where the witness is of type [`Witness`], and
|
||||
@@ -95,3 +102,25 @@ impl<C: CurveGroup> ArithSampler<C, Witness<C>, CommittedInstance<C>> for R1CS<C
|
||||
Ok((witness, cm_witness))
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: CurveGroup> ArithGadget<WitnessVar<C>, CommittedInstanceVar<C>>
|
||||
for R1CSMatricesVar<C::ScalarField, FpVar<C::ScalarField>>
|
||||
{
|
||||
type Evaluation = (Vec<FpVar<C::ScalarField>>, Vec<FpVar<C::ScalarField>>);
|
||||
|
||||
fn eval_relation(
|
||||
&self,
|
||||
w: &WitnessVar<C>,
|
||||
u: &CommittedInstanceVar<C>,
|
||||
) -> Result<Self::Evaluation, SynthesisError> {
|
||||
self.eval_at_z(&[&[u.u.clone()][..], &u.x, &w.W].concat())
|
||||
}
|
||||
|
||||
fn enforce_evaluation(
|
||||
w: &WitnessVar<C>,
|
||||
_u: &CommittedInstanceVar<C>,
|
||||
(AzBz, uCz): Self::Evaluation,
|
||||
) -> Result<(), SynthesisError> {
|
||||
EquivalenceGadget::<C::ScalarField>::enforce_equivalent(&AzBz[..], &uCz.add(&w.E)?[..])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,10 +43,7 @@ use ark_crypto_primitives::sponge::{
|
||||
Absorb, CryptographicSponge,
|
||||
};
|
||||
use ark_ec::{CurveGroup, Group};
|
||||
use ark_r1cs_std::{
|
||||
groups::{CurveVar, GroupOpsBounds},
|
||||
ToConstraintFieldGadget,
|
||||
};
|
||||
use ark_r1cs_std::{groups::CurveVar, ToConstraintFieldGadget};
|
||||
|
||||
use crate::{commitment::CommitmentScheme, folding::circuits::CF2, frontend::FCircuit, Error};
|
||||
|
||||
@@ -90,7 +87,6 @@ where
|
||||
<C2 as Group>::ScalarField: PrimeField,
|
||||
<C2 as CurveGroup>::BaseField: PrimeField,
|
||||
<C2 as CurveGroup>::BaseField: Absorb,
|
||||
for<'a> &'a GC2: GroupOpsBounds<'a, C2, GC2>,
|
||||
GC2: ToConstraintFieldGadget<<C2 as CurveGroup>::BaseField>,
|
||||
C1: CurveGroup<BaseField = C2::ScalarField, ScalarField = C2::BaseField>,
|
||||
{
|
||||
@@ -161,7 +157,6 @@ where
|
||||
<C2 as Group>::ScalarField: Absorb,
|
||||
<C2 as CurveGroup>::BaseField: PrimeField,
|
||||
<C2 as CurveGroup>::BaseField: Absorb,
|
||||
for<'a> &'a GC2: GroupOpsBounds<'a, C2, GC2>,
|
||||
GC2: ToConstraintFieldGadget<<C2 as CurveGroup>::BaseField>,
|
||||
C1: CurveGroup<BaseField = C2::ScalarField, ScalarField = C2::BaseField>,
|
||||
{
|
||||
|
||||
@@ -11,7 +11,7 @@ use ark_r1cs_std::{
|
||||
boolean::Boolean,
|
||||
eq::EqGadget,
|
||||
fields::{fp::FpVar, FieldVar},
|
||||
groups::{CurveVar, GroupOpsBounds},
|
||||
groups::CurveVar,
|
||||
poly::polynomial::univariate::dense::DensePolynomialVar,
|
||||
R1CSVar, ToBitsGadget, ToConstraintFieldGadget,
|
||||
};
|
||||
@@ -77,7 +77,8 @@ impl FoldingGadget {
|
||||
let betas_star = betas_star_var(&instance.betas, &deltas, &alpha);
|
||||
|
||||
let k = vec_instances.len();
|
||||
let H = GeneralEvaluationDomain::new(k + 1).unwrap();
|
||||
let H =
|
||||
GeneralEvaluationDomain::new(k + 1).ok_or(SynthesisError::PolynomialDegreeTooLarge)?;
|
||||
let L_X = lagrange_polys(H)
|
||||
.into_iter()
|
||||
.map(|poly| {
|
||||
@@ -183,7 +184,6 @@ impl AugmentationGadget {
|
||||
) -> 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());
|
||||
@@ -268,8 +268,6 @@ pub struct AugmentedFCircuit<
|
||||
|
||||
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>>,
|
||||
@@ -317,7 +315,6 @@ where
|
||||
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))?;
|
||||
@@ -498,7 +495,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 (_, _, proof, _) = Folding::<Projective>::prove(
|
||||
&mut transcript_p,
|
||||
&r1cs,
|
||||
&instance,
|
||||
@@ -507,20 +504,15 @@ mod tests {
|
||||
&witnesses,
|
||||
)?;
|
||||
|
||||
let folded_instance = Folding::<Projective>::verify(
|
||||
&mut transcript_v,
|
||||
&instance,
|
||||
&instances,
|
||||
F_coeffs.clone(),
|
||||
K_coeffs.clone(),
|
||||
)?;
|
||||
let folded_instance =
|
||||
Folding::<Projective>::verify(&mut transcript_v, &instance, &instances, proof.clone())?;
|
||||
|
||||
let cs = ConstraintSystem::new_ref();
|
||||
let mut transcript_var = PoseidonSpongeVar::new(cs.clone(), &poseidon_config);
|
||||
let instance_var = CommittedInstanceVar::new_witness(cs.clone(), || Ok(instance))?;
|
||||
let instances_var = Vec::new_witness(cs.clone(), || Ok(instances))?;
|
||||
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 F_coeffs_var = Vec::new_witness(cs.clone(), || Ok(proof.F_coeffs))?;
|
||||
let K_coeffs_var = Vec::new_witness(cs.clone(), || Ok(proof.K_coeffs))?;
|
||||
|
||||
let (folded_instance_var, _) = FoldingGadget::fold_committed_instance(
|
||||
&mut transcript_var,
|
||||
|
||||
252
folding-schemes/src/folding/protogalaxy/decider_eth_circuit.rs
Normal file
252
folding-schemes/src/folding/protogalaxy/decider_eth_circuit.rs
Normal file
@@ -0,0 +1,252 @@
|
||||
/// This file implements the onchain (Ethereum's EVM) decider circuit. For non-ethereum use cases,
|
||||
/// other more efficient approaches can be used.
|
||||
use ark_crypto_primitives::sponge::{
|
||||
constraints::CryptographicSpongeVar,
|
||||
poseidon::{constraints::PoseidonSpongeVar, PoseidonSponge},
|
||||
Absorb, CryptographicSponge,
|
||||
};
|
||||
use ark_ec::CurveGroup;
|
||||
use ark_ff::PrimeField;
|
||||
use ark_r1cs_std::{
|
||||
alloc::{AllocVar, AllocationMode},
|
||||
eq::EqGadget,
|
||||
fields::fp::FpVar,
|
||||
groups::CurveVar,
|
||||
ToConstraintFieldGadget,
|
||||
};
|
||||
use ark_relations::r1cs::{Namespace, SynthesisError};
|
||||
use ark_std::{borrow::Borrow, marker::PhantomData};
|
||||
|
||||
use crate::{
|
||||
arith::r1cs::{circuits::R1CSMatricesVar, R1CS},
|
||||
commitment::{pedersen::Params as PedersenParams, CommitmentScheme},
|
||||
folding::{
|
||||
circuits::{
|
||||
decider::{
|
||||
on_chain::GenericOnchainDeciderCircuit, DeciderEnabledNIFS, EvalGadget,
|
||||
KZGChallengesGadget,
|
||||
},
|
||||
CF1, CF2,
|
||||
},
|
||||
traits::{WitnessOps, WitnessVarOps},
|
||||
},
|
||||
frontend::FCircuit,
|
||||
Error,
|
||||
};
|
||||
|
||||
use super::{
|
||||
circuits::FoldingGadget,
|
||||
constants::{INCOMING, RUNNING},
|
||||
folding::{Folding, ProtoGalaxyProof},
|
||||
CommittedInstance, CommittedInstanceVar, ProtoGalaxy, Witness,
|
||||
};
|
||||
|
||||
/// In-circuit representation of the Witness associated to the CommittedInstance.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct WitnessVar<F: PrimeField> {
|
||||
pub W: Vec<FpVar<F>>,
|
||||
pub rW: FpVar<F>,
|
||||
}
|
||||
|
||||
impl<F: PrimeField> AllocVar<Witness<F>, F> for WitnessVar<F> {
|
||||
fn new_variable<T: Borrow<Witness<F>>>(
|
||||
cs: impl Into<Namespace<F>>,
|
||||
f: impl FnOnce() -> Result<T, SynthesisError>,
|
||||
mode: AllocationMode,
|
||||
) -> Result<Self, SynthesisError> {
|
||||
f().and_then(|val| {
|
||||
let cs = cs.into();
|
||||
|
||||
let W = Vec::new_variable(cs.clone(), || Ok(val.borrow().w.to_vec()), mode)?;
|
||||
let rW = FpVar::new_variable(cs.clone(), || Ok(val.borrow().r_w), mode)?;
|
||||
|
||||
Ok(Self { W, rW })
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: PrimeField> WitnessVarOps<F> for WitnessVar<F> {
|
||||
fn get_openings(&self) -> Vec<(&[FpVar<F>], FpVar<F>)> {
|
||||
vec![(&self.W, self.rW.clone())]
|
||||
}
|
||||
}
|
||||
|
||||
pub type DeciderEthCircuit<C1, C2, GC2> = GenericOnchainDeciderCircuit<
|
||||
C1,
|
||||
C2,
|
||||
GC2,
|
||||
CommittedInstance<C1, RUNNING>,
|
||||
CommittedInstance<C1, INCOMING>,
|
||||
Witness<CF1<C1>>,
|
||||
R1CS<CF1<C1>>,
|
||||
R1CSMatricesVar<CF1<C1>, FpVar<CF1<C1>>>,
|
||||
DeciderProtoGalaxyGadget,
|
||||
>;
|
||||
|
||||
/// returns an instance of the DeciderEthCircuit from the given ProtoGalaxy struct
|
||||
impl<
|
||||
C1: CurveGroup,
|
||||
GC1: CurveVar<C1, CF2<C1>> + ToConstraintFieldGadget<CF2<C1>>,
|
||||
C2: CurveGroup,
|
||||
GC2: CurveVar<C2, CF2<C2>> + ToConstraintFieldGadget<CF2<C2>>,
|
||||
FC: FCircuit<C1::ScalarField>,
|
||||
CS1: CommitmentScheme<C1, false>,
|
||||
// enforce that the CS2 is Pedersen commitment scheme, since we're at Ethereum's EVM decider
|
||||
CS2: CommitmentScheme<C2, false, ProverParams = PedersenParams<C2>>,
|
||||
> TryFrom<ProtoGalaxy<C1, GC1, C2, GC2, FC, CS1, CS2>> for DeciderEthCircuit<C1, C2, GC2>
|
||||
where
|
||||
CF1<C1>: Absorb,
|
||||
{
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(protogalaxy: ProtoGalaxy<C1, GC1, C2, GC2, FC, CS1, CS2>) -> Result<Self, Error> {
|
||||
let mut transcript = PoseidonSponge::<C1::ScalarField>::new(&protogalaxy.poseidon_config);
|
||||
|
||||
let (U_i1, W_i1, proof, aux) = Folding::prove(
|
||||
&mut transcript,
|
||||
&protogalaxy.r1cs,
|
||||
&protogalaxy.U_i,
|
||||
&protogalaxy.W_i,
|
||||
&[protogalaxy.u_i.clone()],
|
||||
&[protogalaxy.w_i.clone()],
|
||||
)?;
|
||||
|
||||
// compute the KZG challenges used as inputs in the circuit
|
||||
let kzg_challenges = KZGChallengesGadget::get_challenges_native(&mut transcript, &U_i1);
|
||||
|
||||
// get KZG evals
|
||||
let kzg_evaluations = W_i1
|
||||
.get_openings()
|
||||
.iter()
|
||||
.zip(&kzg_challenges)
|
||||
.map(|((v, _), &c)| EvalGadget::evaluate_native(v, c))
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
Ok(Self {
|
||||
_gc2: PhantomData,
|
||||
_avar: PhantomData,
|
||||
arith: protogalaxy.r1cs,
|
||||
cf_arith: protogalaxy.cf_r1cs,
|
||||
cf_pedersen_params: protogalaxy.cf_cs_params,
|
||||
poseidon_config: protogalaxy.poseidon_config,
|
||||
pp_hash: protogalaxy.pp_hash,
|
||||
i: protogalaxy.i,
|
||||
z_0: protogalaxy.z_0,
|
||||
z_i: protogalaxy.z_i,
|
||||
U_i: protogalaxy.U_i,
|
||||
W_i: protogalaxy.W_i,
|
||||
u_i: protogalaxy.u_i,
|
||||
w_i: protogalaxy.w_i,
|
||||
U_i1,
|
||||
W_i1,
|
||||
proof,
|
||||
randomness: aux.L_X_evals,
|
||||
cf_U_i: protogalaxy.cf_U_i,
|
||||
cf_W_i: protogalaxy.cf_W_i,
|
||||
kzg_challenges,
|
||||
kzg_evaluations,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DeciderProtoGalaxyGadget;
|
||||
|
||||
impl<C: CurveGroup>
|
||||
DeciderEnabledNIFS<
|
||||
C,
|
||||
CommittedInstance<C, RUNNING>,
|
||||
CommittedInstance<C, INCOMING>,
|
||||
Witness<CF1<C>>,
|
||||
R1CS<CF1<C>>,
|
||||
> for DeciderProtoGalaxyGadget
|
||||
{
|
||||
type Proof = ProtoGalaxyProof<CF1<C>>;
|
||||
type ProofDummyCfg = (usize, usize, usize);
|
||||
type Randomness = Vec<CF1<C>>;
|
||||
type RandomnessDummyCfg = usize;
|
||||
|
||||
fn fold_field_elements_gadget(
|
||||
_arith: &R1CS<CF1<C>>,
|
||||
transcript: &mut PoseidonSpongeVar<CF1<C>>,
|
||||
_pp_hash: FpVar<CF1<C>>,
|
||||
U: CommittedInstanceVar<C, RUNNING>,
|
||||
_U_vec: Vec<FpVar<CF1<C>>>,
|
||||
u: CommittedInstanceVar<C, INCOMING>,
|
||||
proof: Self::Proof,
|
||||
randomness: Self::Randomness,
|
||||
) -> Result<CommittedInstanceVar<C, RUNNING>, SynthesisError> {
|
||||
let cs = transcript.cs();
|
||||
let F_coeffs = Vec::new_witness(cs.clone(), || Ok(&proof.F_coeffs[..]))?;
|
||||
let K_coeffs = Vec::new_witness(cs.clone(), || Ok(&proof.K_coeffs[..]))?;
|
||||
let randomness = Vec::new_input(cs.clone(), || Ok(randomness))?;
|
||||
|
||||
let (U_next, L_X_evals) =
|
||||
FoldingGadget::fold_committed_instance(transcript, &U, &[u], F_coeffs, K_coeffs)?;
|
||||
L_X_evals.enforce_equal(&randomness)?;
|
||||
|
||||
Ok(U_next)
|
||||
}
|
||||
|
||||
fn fold_group_elements_native(
|
||||
U_commitments: &[C],
|
||||
u_commitments: &[C],
|
||||
_: Option<Self::Proof>,
|
||||
L_X_evals: Self::Randomness,
|
||||
) -> Result<Vec<C>, Error> {
|
||||
let U_phi = U_commitments[0];
|
||||
let u_phi = u_commitments[0];
|
||||
Ok(vec![U_phi * L_X_evals[0] + u_phi * L_X_evals[1]])
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use ark_bn254::{constraints::GVar, Fr, G1Projective as Projective};
|
||||
use ark_grumpkin::{constraints::GVar as GVar2, Projective as Projective2};
|
||||
use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystem};
|
||||
|
||||
use super::*;
|
||||
use crate::commitment::pedersen::Pedersen;
|
||||
use crate::folding::protogalaxy::ProtoGalaxy;
|
||||
use crate::frontend::{utils::CubicFCircuit, FCircuit};
|
||||
use crate::transcript::poseidon::poseidon_canonical_config;
|
||||
use crate::FoldingScheme;
|
||||
|
||||
#[test]
|
||||
fn test_decider_circuit() {
|
||||
let mut rng = ark_std::test_rng();
|
||||
let poseidon_config = poseidon_canonical_config::<Fr>();
|
||||
|
||||
let F_circuit = CubicFCircuit::<Fr>::new(()).unwrap();
|
||||
let z_0 = vec![Fr::from(3_u32)];
|
||||
|
||||
type PG = ProtoGalaxy<
|
||||
Projective,
|
||||
GVar,
|
||||
Projective2,
|
||||
GVar2,
|
||||
CubicFCircuit<Fr>,
|
||||
Pedersen<Projective>,
|
||||
Pedersen<Projective2>,
|
||||
>;
|
||||
let pg_params = PG::preprocess(&mut rng, &(poseidon_config, F_circuit)).unwrap();
|
||||
|
||||
// generate a Nova instance and do a step of it
|
||||
let mut protogalaxy = PG::init(&pg_params, F_circuit, z_0.clone()).unwrap();
|
||||
protogalaxy.prove_step(&mut rng, vec![], None).unwrap();
|
||||
|
||||
let ivc_proof = protogalaxy.ivc_proof();
|
||||
PG::verify(pg_params.1, ivc_proof).unwrap();
|
||||
|
||||
// load the DeciderEthCircuit from the generated Nova instance
|
||||
let decider_circuit =
|
||||
DeciderEthCircuit::<Projective, Projective2, GVar2>::try_from(protogalaxy).unwrap();
|
||||
|
||||
let cs = ConstraintSystem::<Fr>::new_ref();
|
||||
|
||||
// generate the constraints and check that are satisfied by the inputs
|
||||
decider_circuit.generate_constraints(cs.clone()).unwrap();
|
||||
assert!(cs.is_satisfied().unwrap());
|
||||
dbg!(cs.num_constraints());
|
||||
}
|
||||
}
|
||||
@@ -15,10 +15,32 @@ use super::ProtoGalaxyError;
|
||||
use super::{CommittedInstance, Witness};
|
||||
|
||||
use crate::arith::r1cs::R1CS;
|
||||
use crate::folding::traits::Dummy;
|
||||
use crate::transcript::Transcript;
|
||||
use crate::utils::vec::*;
|
||||
use crate::Error;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ProtoGalaxyProof<F: PrimeField> {
|
||||
pub F_coeffs: Vec<F>,
|
||||
pub K_coeffs: Vec<F>,
|
||||
}
|
||||
|
||||
impl<F: PrimeField> Dummy<(usize, usize, usize)> for ProtoGalaxyProof<F> {
|
||||
fn dummy((t, d, k): (usize, usize, usize)) -> Self {
|
||||
Self {
|
||||
F_coeffs: vec![F::zero(); t],
|
||||
K_coeffs: vec![F::zero(); d * k + 1],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ProtoGalaxyAux<C: CurveGroup> {
|
||||
pub L_X_evals: Vec<C::ScalarField>,
|
||||
pub phi_stars: Vec<C>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
/// Implements the protocol described in section 4 of
|
||||
/// [ProtoGalaxy](https://eprint.iacr.org/2023/1106.pdf)
|
||||
@@ -28,7 +50,6 @@ pub struct Folding<C: CurveGroup> {
|
||||
impl<C: CurveGroup> Folding<C>
|
||||
where
|
||||
<C as Group>::ScalarField: Absorb,
|
||||
<C as CurveGroup>::BaseField: Absorb,
|
||||
{
|
||||
#![allow(clippy::type_complexity)]
|
||||
/// implements the non-interactive Prover from the folding scheme described in section 4
|
||||
@@ -45,10 +66,8 @@ where
|
||||
(
|
||||
CommittedInstance<C, true>,
|
||||
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
|
||||
ProtoGalaxyProof<C::ScalarField>,
|
||||
ProtoGalaxyAux<C>,
|
||||
),
|
||||
Error,
|
||||
> {
|
||||
@@ -243,10 +262,11 @@ where
|
||||
w: w_star,
|
||||
r_w: r_w_star,
|
||||
},
|
||||
F_coeffs,
|
||||
K_coeffs,
|
||||
L_X_evals,
|
||||
phi_stars,
|
||||
ProtoGalaxyProof { F_coeffs, K_coeffs },
|
||||
ProtoGalaxyAux {
|
||||
L_X_evals,
|
||||
phi_stars,
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
@@ -258,8 +278,7 @@ where
|
||||
// incoming instances
|
||||
vec_instances: &[CommittedInstance<C, false>],
|
||||
// polys from P
|
||||
F_coeffs: Vec<C::ScalarField>,
|
||||
K_coeffs: Vec<C::ScalarField>,
|
||||
proof: ProtoGalaxyProof<C::ScalarField>,
|
||||
) -> Result<CommittedInstance<C, true>, Error> {
|
||||
let t = instance.betas.len();
|
||||
|
||||
@@ -270,20 +289,20 @@ where
|
||||
let delta = transcript.get_challenge();
|
||||
let deltas = exponential_powers(delta, t);
|
||||
|
||||
transcript.absorb(&F_coeffs);
|
||||
transcript.absorb(&proof.F_coeffs);
|
||||
|
||||
let alpha = transcript.get_challenge();
|
||||
let alphas = all_powers(alpha, t);
|
||||
|
||||
// F(alpha) = e + \sum_t F_i * alpha^i
|
||||
let mut F_alpha = instance.e;
|
||||
for (i, F_i) in F_coeffs.iter().skip(1).enumerate() {
|
||||
for (i, F_i) in proof.F_coeffs.iter().skip(1).enumerate() {
|
||||
F_alpha += *F_i * alphas[i + 1];
|
||||
}
|
||||
|
||||
let betas_star = betas_star(&instance.betas, &deltas, alpha);
|
||||
|
||||
transcript.absorb(&K_coeffs);
|
||||
transcript.absorb(&proof.K_coeffs);
|
||||
|
||||
let k = vec_instances.len();
|
||||
let H =
|
||||
@@ -291,7 +310,7 @@ where
|
||||
let L_X: Vec<DensePolynomial<C::ScalarField>> = lagrange_polys(H);
|
||||
let Z_X: DensePolynomial<C::ScalarField> = H.vanishing_polynomial().into();
|
||||
let K_X: DensePolynomial<C::ScalarField> =
|
||||
DensePolynomial::<C::ScalarField>::from_coefficients_vec(K_coeffs);
|
||||
DensePolynomial::<C::ScalarField>::from_coefficients_vec(proof.K_coeffs);
|
||||
|
||||
let gamma = transcript.get_challenge();
|
||||
|
||||
@@ -483,27 +502,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();
|
||||
|
||||
// verifier
|
||||
let folded_instance_v = Folding::<Projective>::verify(
|
||||
&mut transcript_v,
|
||||
let (folded_instance, folded_witness, proof, _) = Folding::<Projective>::prove(
|
||||
&mut transcript_p,
|
||||
&r1cs,
|
||||
&instance,
|
||||
&witness,
|
||||
&instances,
|
||||
F_coeffs,
|
||||
K_coeffs,
|
||||
&witnesses,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// verifier
|
||||
let folded_instance_v =
|
||||
Folding::<Projective>::verify(&mut transcript_v, &instance, &instances, proof).unwrap();
|
||||
|
||||
// check that prover & verifier folded instances are the same values
|
||||
assert_eq!(folded_instance.phi, folded_instance_v.phi);
|
||||
assert_eq!(folded_instance.betas, folded_instance_v.betas);
|
||||
@@ -533,24 +545,22 @@ pub mod tests {
|
||||
// generate the instances to be fold
|
||||
let (_, _, witnesses, instances) = prepare_inputs(k);
|
||||
|
||||
let (folded_instance, folded_witness, F_coeffs, K_coeffs, _, _) =
|
||||
Folding::<Projective>::prove(
|
||||
&mut transcript_p,
|
||||
&r1cs,
|
||||
&running_instance,
|
||||
&running_witness,
|
||||
&instances,
|
||||
&witnesses,
|
||||
)
|
||||
.unwrap();
|
||||
let (folded_instance, folded_witness, proof, _) = Folding::<Projective>::prove(
|
||||
&mut transcript_p,
|
||||
&r1cs,
|
||||
&running_instance,
|
||||
&running_witness,
|
||||
&instances,
|
||||
&witnesses,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// verifier
|
||||
let folded_instance_v = Folding::<Projective>::verify(
|
||||
&mut transcript_v,
|
||||
&running_instance,
|
||||
&instances,
|
||||
F_coeffs,
|
||||
K_coeffs,
|
||||
proof,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ use ark_r1cs_std::{
|
||||
alloc::{AllocVar, AllocationMode},
|
||||
eq::EqGadget,
|
||||
fields::{fp::FpVar, FieldVar},
|
||||
groups::{CurveVar, GroupOpsBounds},
|
||||
groups::CurveVar,
|
||||
R1CSVar, ToConstraintFieldGadget,
|
||||
};
|
||||
use ark_relations::r1cs::{
|
||||
@@ -44,6 +44,7 @@ use crate::{
|
||||
|
||||
pub mod circuits;
|
||||
pub mod constants;
|
||||
pub mod decider_eth_circuit;
|
||||
pub mod folding;
|
||||
pub mod traits;
|
||||
pub(crate) mod utils;
|
||||
@@ -52,7 +53,7 @@ use circuits::AugmentedFCircuit;
|
||||
use folding::Folding;
|
||||
|
||||
use super::traits::{
|
||||
CommittedInstanceOps, CommittedInstanceVarOps, Dummy, WitnessOps, WitnessVarOps,
|
||||
CommittedInstanceOps, CommittedInstanceVarOps, Dummy, Inputize, WitnessOps, WitnessVarOps,
|
||||
};
|
||||
|
||||
/// Configuration for ProtoGalaxy's CycleFold circuit
|
||||
@@ -121,6 +122,14 @@ impl<C: CurveGroup, const TYPE: bool> CommittedInstanceOps<C> for CommittedInsta
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: CurveGroup, const TYPE: bool> Inputize<C::ScalarField, CommittedInstanceVar<C, TYPE>>
|
||||
for CommittedInstance<C, TYPE>
|
||||
{
|
||||
fn inputize(&self) -> Vec<C::ScalarField> {
|
||||
[&self.phi.inputize(), &self.betas, &[self.e][..], &self.x].concat()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct CommittedInstanceVar<C: CurveGroup, const TYPE: bool> {
|
||||
phi: NonNativeAffineVar<C>,
|
||||
@@ -534,8 +543,6 @@ where
|
||||
C1::ScalarField: Absorb,
|
||||
C2::ScalarField: Absorb,
|
||||
C1: CurveGroup<BaseField = C2::ScalarField, ScalarField = C2::BaseField>,
|
||||
for<'a> &'a GC1: GroupOpsBounds<'a, C1, GC1>,
|
||||
for<'a> &'a GC2: GroupOpsBounds<'a, C2, GC2>,
|
||||
{
|
||||
/// This method computes the parameter `t` in ProtoGalaxy for folding `F'`,
|
||||
/// the augmented circuit of `F`
|
||||
@@ -544,7 +551,7 @@ where
|
||||
F: &FC,
|
||||
d: usize,
|
||||
k: usize,
|
||||
) -> Result<usize, SynthesisError> {
|
||||
) -> Result<usize, Error> {
|
||||
// In ProtoGalaxy, prover and verifier are parameterized by `t = log(n)`
|
||||
// where `n` is the number of constraints in the circuit (known as the
|
||||
// mapping `f` in the paper).
|
||||
@@ -581,7 +588,7 @@ where
|
||||
// Create a dummy circuit with the same state length and external inputs
|
||||
// length as `F`, which replaces `F` in the augmented circuit `F'`.
|
||||
let dummy_circuit: DummyCircuit =
|
||||
FCircuit::<C1::ScalarField>::new((state_len, external_inputs_len)).unwrap();
|
||||
FCircuit::<C1::ScalarField>::new((state_len, external_inputs_len))?;
|
||||
|
||||
// Compute `augmentation_constraints`, the size of `F'` without `F`.
|
||||
let cs = ConstraintSystem::<C1::ScalarField>::new_ref();
|
||||
@@ -640,8 +647,6 @@ where
|
||||
C1::ScalarField: Absorb,
|
||||
C2::ScalarField: Absorb,
|
||||
C1: CurveGroup<BaseField = C2::ScalarField, ScalarField = C2::BaseField>,
|
||||
for<'a> &'a GC1: GroupOpsBounds<'a, C1, GC1>,
|
||||
for<'a> &'a GC2: GroupOpsBounds<'a, C2, GC2>,
|
||||
{
|
||||
type PreprocessorParam = (PoseidonConfig<CF1<C1>>, FC);
|
||||
type ProverParam = ProverParams<C1, C2, CS1, CS2>;
|
||||
@@ -692,7 +697,7 @@ where
|
||||
augmented_F_circuit.generate_constraints(cs.clone())?;
|
||||
cs.finalize();
|
||||
let cs = cs.into_inner().ok_or(Error::NoInnerConstraintSystem)?;
|
||||
let r1cs = extract_r1cs::<C1::ScalarField>(&cs);
|
||||
let r1cs = extract_r1cs::<C1::ScalarField>(&cs)?;
|
||||
|
||||
// CycleFold circuit R1CS
|
||||
let cs2 = ConstraintSystem::<C1::BaseField>::new_ref();
|
||||
@@ -700,7 +705,7 @@ where
|
||||
cf_circuit.generate_constraints(cs2.clone())?;
|
||||
cs2.finalize();
|
||||
let cs2 = cs2.into_inner().ok_or(Error::NoInnerConstraintSystem)?;
|
||||
let cf_r1cs = extract_r1cs::<C1::BaseField>(&cs2);
|
||||
let cf_r1cs = extract_r1cs::<C1::BaseField>(&cs2)?;
|
||||
|
||||
let cs_vp = CS1::VerifierParams::deserialize_with_mode(&mut reader, compress, validate)?;
|
||||
let cf_cs_vp = CS2::VerifierParams::deserialize_with_mode(&mut reader, compress, validate)?;
|
||||
@@ -740,12 +745,12 @@ where
|
||||
augmented_F_circuit.generate_constraints(cs.clone())?;
|
||||
cs.finalize();
|
||||
let cs = cs.into_inner().ok_or(Error::NoInnerConstraintSystem)?;
|
||||
let r1cs = extract_r1cs::<C1::ScalarField>(&cs);
|
||||
let r1cs = extract_r1cs::<C1::ScalarField>(&cs)?;
|
||||
|
||||
cf_circuit.generate_constraints(cs2.clone())?;
|
||||
cs2.finalize();
|
||||
let cs2 = cs2.into_inner().ok_or(Error::NoInnerConstraintSystem)?;
|
||||
let cf_r1cs = extract_r1cs::<C1::BaseField>(&cs2);
|
||||
let cf_r1cs = extract_r1cs::<C1::BaseField>(&cs2)?;
|
||||
|
||||
let (cs_pp, cs_vp) = CS1::setup(&mut rng, r1cs.A.n_rows)?;
|
||||
let (cf_cs_pp, cf_cs_vp) = CS2::setup(&mut rng, max(cf_r1cs.A.n_rows, cf_r1cs.A.n_cols))?;
|
||||
@@ -898,7 +903,7 @@ where
|
||||
} else {
|
||||
// Primary part:
|
||||
// Compute `U_{i+1}` by folding `u_i` into `U_i`.
|
||||
let (U_i1, W_i1, F_coeffs, K_coeffs, L_evals, phi_stars) = Folding::prove(
|
||||
let (U_i1, W_i1, proof, aux) = Folding::prove(
|
||||
&mut transcript_prover,
|
||||
&self.r1cs,
|
||||
&self.U_i,
|
||||
@@ -909,8 +914,8 @@ where
|
||||
|
||||
// CycleFold part:
|
||||
// get the vector used as public inputs 'x' in the CycleFold circuit
|
||||
let mut r0_bits = L_evals[0].into_bigint().to_bits_le();
|
||||
let mut r1_bits = L_evals[1].into_bigint().to_bits_le();
|
||||
let mut r0_bits = aux.L_X_evals[0].into_bigint().to_bits_le();
|
||||
let mut r1_bits = aux.L_X_evals[1].into_bigint().to_bits_le();
|
||||
r0_bits.resize(C1::ScalarField::MODULUS_BIT_SIZE as usize, false);
|
||||
r1_bits.resize(C1::ScalarField::MODULUS_BIT_SIZE as usize, false);
|
||||
|
||||
@@ -919,13 +924,12 @@ where
|
||||
let cf1_u_i_x = [
|
||||
r0_bits
|
||||
.chunks(C1::BaseField::MODULUS_BIT_SIZE as usize - 1)
|
||||
.map(BigInteger::from_bits_le)
|
||||
.map(C1::BaseField::from_bigint)
|
||||
.collect::<Option<Vec<_>>>()
|
||||
.unwrap(),
|
||||
.map(<C1::BaseField as PrimeField>::BigInt::from_bits_le)
|
||||
.map(C1::BaseField::from)
|
||||
.collect::<Vec<_>>(),
|
||||
get_cm_coordinates(&C1::zero()),
|
||||
get_cm_coordinates(&self.U_i.phi),
|
||||
get_cm_coordinates(&phi_stars[0]),
|
||||
get_cm_coordinates(&aux.phi_stars[0]),
|
||||
]
|
||||
.concat();
|
||||
let cf1_circuit = ProtoGalaxyCycleFoldCircuit::<C1, GC1> {
|
||||
@@ -941,11 +945,10 @@ where
|
||||
let cf2_u_i_x = [
|
||||
r1_bits
|
||||
.chunks(C1::BaseField::MODULUS_BIT_SIZE as usize - 1)
|
||||
.map(BigInteger::from_bits_le)
|
||||
.map(C1::BaseField::from_bigint)
|
||||
.collect::<Option<Vec<_>>>()
|
||||
.unwrap(),
|
||||
get_cm_coordinates(&phi_stars[0]),
|
||||
.map(<C1::BaseField as PrimeField>::BigInt::from_bits_le)
|
||||
.map(C1::BaseField::from)
|
||||
.collect::<Vec<_>>(),
|
||||
get_cm_coordinates(&aux.phi_stars[0]),
|
||||
get_cm_coordinates(&self.u_i.phi),
|
||||
get_cm_coordinates(&U_i1.phi),
|
||||
]
|
||||
@@ -953,7 +956,7 @@ where
|
||||
let cf2_circuit = ProtoGalaxyCycleFoldCircuit::<C1, GC1> {
|
||||
_gc: PhantomData,
|
||||
r_bits: Some(r1_bits),
|
||||
points: Some(vec![phi_stars[0], self.u_i.phi]),
|
||||
points: Some(vec![aux.phi_stars[0], self.u_i.phi]),
|
||||
x: Some(cf2_u_i_x.clone()),
|
||||
};
|
||||
|
||||
@@ -998,9 +1001,9 @@ where
|
||||
u_i_phi: self.u_i.phi,
|
||||
U_i: self.U_i.clone(),
|
||||
U_i1_phi: U_i1.phi,
|
||||
F_coeffs: F_coeffs.clone(),
|
||||
K_coeffs: K_coeffs.clone(),
|
||||
phi_stars,
|
||||
F_coeffs: proof.F_coeffs.clone(),
|
||||
K_coeffs: proof.K_coeffs.clone(),
|
||||
phi_stars: aux.phi_stars,
|
||||
F: self.F.clone(),
|
||||
x: Some(u_i1_x),
|
||||
// cyclefold values
|
||||
@@ -1020,8 +1023,7 @@ where
|
||||
&mut transcript_verifier,
|
||||
&self.U_i,
|
||||
&[self.u_i.clone()],
|
||||
F_coeffs,
|
||||
K_coeffs
|
||||
proof
|
||||
)?,
|
||||
U_i1
|
||||
);
|
||||
@@ -1108,7 +1110,7 @@ where
|
||||
} = ivc_proof;
|
||||
let (pp, vp) = params;
|
||||
|
||||
let f_circuit = FC::new(fcircuit_params).unwrap();
|
||||
let f_circuit = FC::new(fcircuit_params)?;
|
||||
|
||||
Ok(Self {
|
||||
_gc1: PhantomData,
|
||||
@@ -1195,8 +1197,6 @@ where
|
||||
<C1 as Group>::ScalarField: Absorb,
|
||||
<C2 as Group>::ScalarField: Absorb,
|
||||
C1: CurveGroup<BaseField = C2::ScalarField, ScalarField = C2::BaseField>,
|
||||
for<'a> &'a GC1: GroupOpsBounds<'a, C1, GC1>,
|
||||
for<'a> &'a GC2: GroupOpsBounds<'a, C2, GC2>,
|
||||
{
|
||||
// folds the given cyclefold circuit and its instances
|
||||
#[allow(clippy::type_complexity)]
|
||||
|
||||
@@ -1,14 +1,26 @@
|
||||
use ark_crypto_primitives::sponge::{constraints::AbsorbGadget, Absorb};
|
||||
use ark_ec::CurveGroup;
|
||||
use ark_ff::PrimeField;
|
||||
use ark_r1cs_std::{fields::fp::FpVar, uint8::UInt8, ToConstraintFieldGadget};
|
||||
use ark_r1cs_std::{
|
||||
eq::EqGadget,
|
||||
fields::{fp::FpVar, FieldVar},
|
||||
uint8::UInt8,
|
||||
ToConstraintFieldGadget,
|
||||
};
|
||||
use ark_relations::r1cs::SynthesisError;
|
||||
use ark_std::{cfg_into_iter, log2, One};
|
||||
use rayon::prelude::*;
|
||||
|
||||
use super::{constants::RUNNING, utils::pow_i, CommittedInstance, CommittedInstanceVar, Witness};
|
||||
use super::{
|
||||
constants::RUNNING,
|
||||
utils::{pow_i, pow_i_var},
|
||||
CommittedInstance, CommittedInstanceVar, Witness, WitnessVar,
|
||||
};
|
||||
use crate::{
|
||||
arith::{r1cs::R1CS, Arith},
|
||||
arith::{
|
||||
r1cs::{circuits::R1CSMatricesVar, R1CS},
|
||||
Arith, ArithGadget,
|
||||
},
|
||||
folding::circuits::CF1,
|
||||
transcript::AbsorbNonNative,
|
||||
utils::vec::is_zero_vec,
|
||||
@@ -99,6 +111,37 @@ impl<C: CurveGroup, const TYPE: bool> Arith<Witness<CF1<C>>, CommittedInstance<C
|
||||
}
|
||||
}
|
||||
|
||||
/// Unlike its native counterpart, we only need to support running instances in
|
||||
/// circuit, as the decider circuit only checks running instance satisfiability.
|
||||
impl<C: CurveGroup> ArithGadget<WitnessVar<CF1<C>>, CommittedInstanceVar<C, RUNNING>>
|
||||
for R1CSMatricesVar<CF1<C>, FpVar<CF1<C>>>
|
||||
{
|
||||
type Evaluation = (Vec<FpVar<CF1<C>>>, Vec<FpVar<CF1<C>>>);
|
||||
|
||||
fn eval_relation(
|
||||
&self,
|
||||
w: &WitnessVar<CF1<C>>,
|
||||
u: &CommittedInstanceVar<C, RUNNING>,
|
||||
) -> Result<Self::Evaluation, SynthesisError> {
|
||||
self.eval_at_z(&[&[FpVar::one()][..], &u.x, &w.W].concat())
|
||||
}
|
||||
|
||||
fn enforce_evaluation(
|
||||
_w: &WitnessVar<C::ScalarField>,
|
||||
u: &CommittedInstanceVar<C, RUNNING>,
|
||||
(AzBz, uCz): Self::Evaluation,
|
||||
) -> Result<(), SynthesisError> {
|
||||
let mut e = vec![];
|
||||
for (i, (l, r)) in AzBz.iter().zip(uCz).enumerate() {
|
||||
e.push(pow_i_var(i, &u.betas) * (l - r));
|
||||
}
|
||||
// Call `sum` on a vector instead of computing the sum in the above loop
|
||||
// to avoid stack overflow (the cause of this is similar to issue #80
|
||||
// https://github.com/privacy-scaling-explorations/sonobe/issues/80)
|
||||
e.iter().sum::<FpVar<_>>().enforce_equal(&u.e)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -12,7 +12,7 @@ use crate::{transcript::Transcript, Error};
|
||||
|
||||
use super::circuits::CF1;
|
||||
|
||||
pub trait CommittedInstanceOps<C: CurveGroup> {
|
||||
pub trait CommittedInstanceOps<C: CurveGroup>: Inputize<CF1<C>, Self::Var> {
|
||||
/// The in-circuit representation of the committed instance.
|
||||
type Var: AllocVar<Self, CF1<C>> + CommittedInstanceVarOps<C>;
|
||||
/// `hash` implements the committed instance hash compatible with the
|
||||
@@ -85,7 +85,11 @@ pub trait CommittedInstanceVarOps<C: CurveGroup> {
|
||||
sponge.absorb(&z_0)?;
|
||||
sponge.absorb(&z_i)?;
|
||||
sponge.absorb(&U_vec)?;
|
||||
Ok((sponge.squeeze_field_elements(1)?.pop().unwrap(), U_vec))
|
||||
Ok((
|
||||
// `unwrap` is safe because the sponge is guaranteed to return a single element
|
||||
sponge.squeeze_field_elements(1)?.pop().unwrap(),
|
||||
U_vec,
|
||||
))
|
||||
}
|
||||
|
||||
/// Returns the commitments contained in the committed instance.
|
||||
@@ -129,3 +133,16 @@ impl<T: Default + Clone> Dummy<usize> for Vec<T> {
|
||||
vec![Default::default(); cfg]
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Default> Dummy<()> for T {
|
||||
fn dummy(_: ()) -> Self {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts a value `self` into a vector of field elements, ordered in the same
|
||||
/// way as how a variable of type `Var` would be represented in the circuit.
|
||||
/// This is useful for the verifier to compute the public inputs.
|
||||
pub trait Inputize<F, Var> {
|
||||
fn inputize(&self) -> Vec<F>;
|
||||
}
|
||||
|
||||
@@ -38,6 +38,8 @@ pub enum Error {
|
||||
// Relation errors
|
||||
#[error("Relation not satisfied")]
|
||||
NotSatisfied,
|
||||
#[error("SNARK setup failed: {0}")]
|
||||
SNARKSetupFail(String),
|
||||
#[error("SNARK verification failed")]
|
||||
SNARKVerificationFail,
|
||||
#[error("IVC verification failed")]
|
||||
@@ -102,8 +104,8 @@ pub enum Error {
|
||||
MaxStep,
|
||||
#[error("Witness calculation error: {0}")]
|
||||
WitnessCalculationError(String),
|
||||
#[error("BigInt to PrimeField conversion error: {0}")]
|
||||
BigIntConversionError(String),
|
||||
#[error("Failed to convert {0} into {1}: {2}")]
|
||||
ConversionError(String, String, String),
|
||||
#[error("Failed to serde: {0}")]
|
||||
JSONSerdeError(String),
|
||||
#[error("Multi instances folding not supported in this scheme")]
|
||||
|
||||
@@ -1,14 +1,36 @@
|
||||
use ark_ff::PrimeField;
|
||||
use ark_r1cs_std::{
|
||||
alloc::{AllocVar, AllocationMode},
|
||||
eq::EqGadget,
|
||||
fields::{fp::FpVar, FieldVar},
|
||||
R1CSVar,
|
||||
};
|
||||
use ark_relations::r1cs::{Namespace, SynthesisError};
|
||||
use core::{borrow::Borrow, marker::PhantomData};
|
||||
use core::borrow::Borrow;
|
||||
|
||||
use crate::utils::vec::SparseMatrix;
|
||||
|
||||
/// `EquivalenceGadget` enforces that two in-circuit variables are equivalent,
|
||||
/// where the equivalence relation is parameterized by `M`:
|
||||
/// - For `FpVar`, it is simply a equality relation, and `M` is unused.
|
||||
/// - For `NonNativeUintVar`, we consider equivalence as a congruence relation,
|
||||
/// in terms of modular arithmetic, so `M` specifies the modulus.
|
||||
pub trait EquivalenceGadget<M> {
|
||||
fn enforce_equivalent(&self, other: &Self) -> Result<(), SynthesisError>;
|
||||
}
|
||||
impl<M, F: PrimeField> EquivalenceGadget<M> for FpVar<F> {
|
||||
fn enforce_equivalent(&self, other: &Self) -> Result<(), SynthesisError> {
|
||||
self.enforce_equal(other)
|
||||
}
|
||||
}
|
||||
impl<M, T: EquivalenceGadget<M>> EquivalenceGadget<M> for [T] {
|
||||
fn enforce_equivalent(&self, other: &Self) -> Result<(), SynthesisError> {
|
||||
self.iter()
|
||||
.zip(other)
|
||||
.try_for_each(|(a, b)| a.enforce_equivalent(b))
|
||||
}
|
||||
}
|
||||
|
||||
pub trait MatrixGadget<FV> {
|
||||
fn mul_vector(&self, v: &[FV]) -> Result<Vec<FV>, SynthesisError>;
|
||||
}
|
||||
@@ -42,17 +64,14 @@ impl<F: PrimeField> VectorGadget<FpVar<F>> for [FpVar<F>] {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SparseMatrixVar<F: PrimeField, CF: PrimeField, FV: AllocVar<F, CF>> {
|
||||
_f: PhantomData<F>,
|
||||
_cf: PhantomData<CF>,
|
||||
_fv: PhantomData<FV>,
|
||||
pub struct SparseMatrixVar<FV> {
|
||||
pub n_rows: usize,
|
||||
pub n_cols: usize,
|
||||
// same format as the native SparseMatrix (which follows ark_relations::r1cs::Matrix format
|
||||
pub coeffs: Vec<Vec<(FV, usize)>>,
|
||||
}
|
||||
|
||||
impl<F, CF, FV> AllocVar<SparseMatrix<F>, CF> for SparseMatrixVar<F, CF, FV>
|
||||
impl<F, CF, FV> AllocVar<SparseMatrix<F>, CF> for SparseMatrixVar<FV>
|
||||
where
|
||||
F: PrimeField,
|
||||
CF: PrimeField,
|
||||
@@ -77,9 +96,6 @@ where
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
_f: PhantomData,
|
||||
_cf: PhantomData,
|
||||
_fv: PhantomData,
|
||||
n_rows: val.borrow().n_rows,
|
||||
n_cols: val.borrow().n_cols,
|
||||
coeffs,
|
||||
@@ -88,7 +104,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: PrimeField> MatrixGadget<FpVar<F>> for SparseMatrixVar<F, F, FpVar<F>> {
|
||||
impl<F: PrimeField> MatrixGadget<FpVar<F>> for SparseMatrixVar<FpVar<F>> {
|
||||
fn mul_vector(&self, v: &[FpVar<F>]) -> Result<Vec<FpVar<F>>, SynthesisError> {
|
||||
Ok(self
|
||||
.coeffs
|
||||
|
||||
@@ -108,11 +108,19 @@ impl<F: PrimeField> CircomWrapper<F> {
|
||||
|
||||
// Converts a num_bigint::BigInt to a PrimeField::BigInt.
|
||||
pub fn num_bigint_to_ark_bigint(&self, value: &BigInt) -> Result<F::BigInt, Error> {
|
||||
let big_uint = value
|
||||
.to_biguint()
|
||||
.ok_or_else(|| Error::BigIntConversionError("BigInt is negative".to_string()))?;
|
||||
let big_uint = value.to_biguint().ok_or_else(|| {
|
||||
Error::ConversionError(
|
||||
"BigInt".into(),
|
||||
"BigUint".into(),
|
||||
"BigInt is negative".into(),
|
||||
)
|
||||
})?;
|
||||
F::BigInt::try_from(big_uint).map_err(|_| {
|
||||
Error::BigIntConversionError("Failed to convert to PrimeField::BigInt".to_string())
|
||||
Error::ConversionError(
|
||||
"BigUint".into(),
|
||||
"PrimeField::BigInt".into(),
|
||||
"BigUint is too large to fit into PrimeField::BigInt".into(),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ use ark_relations::r1cs::{ConstraintSystemRef, SynthesisError};
|
||||
use folding_schemes::{frontend::FCircuit, utils::PathOrBin, Error};
|
||||
use noir_arkworks_backend::{
|
||||
read_program_from_binary, read_program_from_file, sonobe_bridge::AcirCircuitSonobe,
|
||||
FilesystemError,
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
@@ -216,10 +217,12 @@ impl<F: PrimeField> FCircuit<F> for NoirFCircuit<F> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_noir_circuit<F: PrimeField>(path: String) -> Circuit<GenericFieldElement<F>> {
|
||||
let program: Program<GenericFieldElement<F>> = read_program_from_file(path).unwrap();
|
||||
pub fn load_noir_circuit<F: PrimeField>(
|
||||
path: String,
|
||||
) -> Result<Circuit<GenericFieldElement<F>>, FilesystemError> {
|
||||
let program: Program<GenericFieldElement<F>> = read_program_from_file(path)?;
|
||||
let circuit: Circuit<GenericFieldElement<F>> = program.functions[0].clone();
|
||||
circuit
|
||||
Ok(circuit)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -241,7 +244,7 @@ mod tests {
|
||||
"{}/src/noir/test_folder/test_circuit/target/test_circuit.json",
|
||||
cur_path.to_str().unwrap()
|
||||
);
|
||||
let circuit = load_noir_circuit(circuit_path);
|
||||
let circuit = load_noir_circuit(circuit_path).unwrap();
|
||||
let noirfcircuit = NoirFCircuit {
|
||||
circuit,
|
||||
state_len: 2,
|
||||
@@ -261,7 +264,7 @@ mod tests {
|
||||
"{}/src/noir/test_folder/test_circuit/target/test_circuit.json",
|
||||
cur_path.to_str().unwrap()
|
||||
);
|
||||
let circuit = load_noir_circuit(circuit_path);
|
||||
let circuit = load_noir_circuit(circuit_path).unwrap();
|
||||
let noirfcircuit = NoirFCircuit {
|
||||
circuit,
|
||||
state_len: 2,
|
||||
@@ -285,7 +288,7 @@ mod tests {
|
||||
"{}/src/noir/test_folder/test_no_external_inputs/target/test_no_external_inputs.json",
|
||||
cur_path.to_str().unwrap()
|
||||
);
|
||||
let circuit = load_noir_circuit(circuit_path);
|
||||
let circuit = load_noir_circuit(circuit_path).unwrap();
|
||||
let noirfcircuit = NoirFCircuit {
|
||||
circuit,
|
||||
state_len: 2,
|
||||
|
||||
@@ -22,7 +22,7 @@ pub fn get_function_selector_for_nova_cyclefold_verifier(
|
||||
first_param_array_length: usize,
|
||||
) -> [u8; 4] {
|
||||
let mut hasher = Sha3::keccak256();
|
||||
let fn_sig = format!("verifyNovaProof(uint256[{}],uint256[4],uint256[3],uint256[4],uint256[4],uint256[2],uint256[2][2],uint256[2],uint256[4],uint256[2][2])", first_param_array_length);
|
||||
let fn_sig = format!("verifyNovaProof(uint256[{}],uint256[4],uint256[2],uint256[3],uint256[2],uint256[2][2],uint256[2],uint256[4],uint256[2][2])", first_param_array_length);
|
||||
hasher.input_str(&fn_sig);
|
||||
let hash = &mut [0u8; 32];
|
||||
hasher.result(hash);
|
||||
|
||||
@@ -153,9 +153,12 @@ mod tests {
|
||||
|
||||
use folding_schemes::{
|
||||
commitment::{kzg::KZG, pedersen::Pedersen},
|
||||
folding::nova::{
|
||||
decider_eth::{prepare_calldata, Decider as DeciderEth},
|
||||
Nova, PreprocessorParam,
|
||||
folding::{
|
||||
nova::{
|
||||
decider_eth::{prepare_calldata, Decider as DeciderEth},
|
||||
Nova, PreprocessorParam,
|
||||
},
|
||||
traits::CommittedInstanceOps,
|
||||
},
|
||||
frontend::FCircuit,
|
||||
transcript::poseidon::poseidon_canonical_config,
|
||||
@@ -366,7 +369,6 @@ mod tests {
|
||||
n_steps: usize,
|
||||
) {
|
||||
let (decider_pp, decider_vp) = decider_params;
|
||||
let pp_hash = fs_params.1.pp_hash().unwrap();
|
||||
|
||||
let f_circuit = FC::new(()).unwrap();
|
||||
|
||||
@@ -389,8 +391,8 @@ mod tests {
|
||||
nova.i,
|
||||
nova.z_0.clone(),
|
||||
nova.z_i.clone(),
|
||||
&nova.U_i,
|
||||
&nova.u_i,
|
||||
&nova.U_i.get_commitments(),
|
||||
&nova.u_i.get_commitments(),
|
||||
&proof,
|
||||
)
|
||||
.unwrap();
|
||||
@@ -401,7 +403,6 @@ mod tests {
|
||||
|
||||
let calldata: Vec<u8> = prepare_calldata(
|
||||
function_selector,
|
||||
pp_hash,
|
||||
nova.i,
|
||||
nova.z_0,
|
||||
nova.z_i,
|
||||
|
||||
@@ -63,9 +63,8 @@ contract NovaDecider is Groth16Verifier, KZG10Verifier {
|
||||
// inputs are grouped to prevent errors due stack too deep
|
||||
uint256[{{ 1 + z_len * 2 }}] calldata i_z0_zi, // [i, z0, zi] where |z0| == |zi|
|
||||
uint256[4] calldata U_i_cmW_U_i_cmE, // [U_i_cmW[2], U_i_cmE[2]]
|
||||
uint256[3] calldata U_i_u_u_i_u_r, // [U_i_u, u_i_u, r]
|
||||
uint256[4] calldata U_i_x_u_i_cmW, // [U_i_x[2], u_i_cmW[2]]
|
||||
uint256[4] calldata u_i_x_cmT, // [u_i_x[2], cmT[2]]
|
||||
uint256[2] calldata u_i_cmW, // [u_i_cmW[2]]
|
||||
uint256[3] calldata cmT_r, // [cmT[2], r]
|
||||
uint256[2] calldata pA, // groth16
|
||||
uint256[2][2] calldata pB, // groth16
|
||||
uint256[2] calldata pC, // groth16
|
||||
@@ -85,75 +84,63 @@ contract NovaDecider is Groth16Verifier, KZG10Verifier {
|
||||
public_inputs[2 + i] = i_z0_zi[1 + i];
|
||||
}
|
||||
|
||||
{
|
||||
// U_i.u + r * u_i.u
|
||||
uint256 u = rlc(U_i_u_u_i_u_r[0], U_i_u_u_i_u_r[2], U_i_u_u_i_u_r[1]);
|
||||
// U_i.x + r * u_i.x
|
||||
uint256 x0 = rlc(U_i_x_u_i_cmW[0], U_i_u_u_i_u_r[2], u_i_x_cmT[0]);
|
||||
uint256 x1 = rlc(U_i_x_u_i_cmW[1], U_i_u_u_i_u_r[2], u_i_x_cmT[1]);
|
||||
|
||||
public_inputs[{{ z_len * 2 + 2 }}] = u;
|
||||
public_inputs[{{ z_len * 2 + 3 }}] = x0;
|
||||
public_inputs[{{ z_len * 2 + 4 }}] = x1;
|
||||
}
|
||||
|
||||
{
|
||||
// U_i.cmE + r * u_i.cmT
|
||||
uint256[2] memory mulScalarPoint = super.mulScalar([u_i_x_cmT[2], u_i_x_cmT[3]], U_i_u_u_i_u_r[2]);
|
||||
uint256[2] memory cmE = super.add([U_i_cmW_U_i_cmE[2], U_i_cmW_U_i_cmE[3]], mulScalarPoint);
|
||||
|
||||
{
|
||||
uint256[{{num_limbs}}] memory cmE_x_limbs = LimbsDecomposition.decompose(cmE[0]);
|
||||
uint256[{{num_limbs}}] memory cmE_y_limbs = LimbsDecomposition.decompose(cmE[1]);
|
||||
|
||||
for (uint8 k = 0; k < {{num_limbs}}; k++) {
|
||||
public_inputs[{{ z_len * 2 + 5 }} + k] = cmE_x_limbs[k];
|
||||
public_inputs[{{ z_len * 2 + 5 + num_limbs }} + k] = cmE_y_limbs[k];
|
||||
}
|
||||
}
|
||||
|
||||
require(this.check(cmE, kzg_proof[1], challenge_W_challenge_E_kzg_evals[1], challenge_W_challenge_E_kzg_evals[3]), "KZG: verifying proof for challenge E failed");
|
||||
}
|
||||
|
||||
{
|
||||
// U_i.cmW + r * u_i.cmW
|
||||
uint256[2] memory mulScalarPoint = super.mulScalar([U_i_x_u_i_cmW[2], U_i_x_u_i_cmW[3]], U_i_u_u_i_u_r[2]);
|
||||
uint256[2] memory mulScalarPoint = super.mulScalar([u_i_cmW[0], u_i_cmW[1]], cmT_r[2]);
|
||||
uint256[2] memory cmW = super.add([U_i_cmW_U_i_cmE[0], U_i_cmW_U_i_cmE[1]], mulScalarPoint);
|
||||
|
||||
|
||||
{
|
||||
uint256[{{num_limbs}}] memory cmW_x_limbs = LimbsDecomposition.decompose(cmW[0]);
|
||||
uint256[{{num_limbs}}] memory cmW_y_limbs = LimbsDecomposition.decompose(cmW[1]);
|
||||
|
||||
for (uint8 k = 0; k < {{num_limbs}}; k++) {
|
||||
public_inputs[{{ z_len * 2 + 5 + num_limbs * 2 }} + k] = cmW_x_limbs[k];
|
||||
public_inputs[{{ z_len * 2 + 5 + num_limbs * 3 }} + k] = cmW_y_limbs[k];
|
||||
public_inputs[{{ z_len * 2 + 2 }} + k] = cmW_x_limbs[k];
|
||||
public_inputs[{{ z_len * 2 + 2 + num_limbs }} + k] = cmW_y_limbs[k];
|
||||
}
|
||||
}
|
||||
|
||||
require(this.check(cmW, kzg_proof[0], challenge_W_challenge_E_kzg_evals[0], challenge_W_challenge_E_kzg_evals[2]), "KZG: verifying proof for challenge W failed");
|
||||
}
|
||||
|
||||
{
|
||||
// U_i.cmE + r * cmT
|
||||
uint256[2] memory mulScalarPoint = super.mulScalar([cmT_r[0], cmT_r[1]], cmT_r[2]);
|
||||
uint256[2] memory cmE = super.add([U_i_cmW_U_i_cmE[2], U_i_cmW_U_i_cmE[3]], mulScalarPoint);
|
||||
|
||||
{
|
||||
uint256[{{num_limbs}}] memory cmE_x_limbs = LimbsDecomposition.decompose(cmE[0]);
|
||||
uint256[{{num_limbs}}] memory cmE_y_limbs = LimbsDecomposition.decompose(cmE[1]);
|
||||
|
||||
for (uint8 k = 0; k < {{num_limbs}}; k++) {
|
||||
public_inputs[{{ z_len * 2 + 2 + num_limbs * 2 }} + k] = cmE_x_limbs[k];
|
||||
public_inputs[{{ z_len * 2 + 2 + num_limbs * 3 }} + k] = cmE_y_limbs[k];
|
||||
}
|
||||
}
|
||||
|
||||
require(this.check(cmE, kzg_proof[1], challenge_W_challenge_E_kzg_evals[1], challenge_W_challenge_E_kzg_evals[3]), "KZG: verifying proof for challenge E failed");
|
||||
}
|
||||
|
||||
{
|
||||
// add challenges
|
||||
public_inputs[{{ z_len * 2 + 5 + num_limbs * 4 }}] = challenge_W_challenge_E_kzg_evals[0];
|
||||
public_inputs[{{ z_len * 2 + 5 + num_limbs * 4 + 1 }}] = challenge_W_challenge_E_kzg_evals[1];
|
||||
public_inputs[{{ z_len * 2 + 5 + num_limbs * 4 + 2 }}] = challenge_W_challenge_E_kzg_evals[2];
|
||||
public_inputs[{{ z_len * 2 + 5 + num_limbs * 4 + 3 }}] = challenge_W_challenge_E_kzg_evals[3];
|
||||
|
||||
public_inputs[{{ z_len * 2 + 2 + num_limbs * 4 }}] = challenge_W_challenge_E_kzg_evals[0];
|
||||
public_inputs[{{ z_len * 2 + 2 + num_limbs * 4 + 1 }}] = challenge_W_challenge_E_kzg_evals[1];
|
||||
public_inputs[{{ z_len * 2 + 2 + num_limbs * 4 + 2 }}] = challenge_W_challenge_E_kzg_evals[2];
|
||||
public_inputs[{{ z_len * 2 + 2 + num_limbs * 4 + 3 }}] = challenge_W_challenge_E_kzg_evals[3];
|
||||
|
||||
uint256[{{num_limbs}}] memory cmT_x_limbs;
|
||||
uint256[{{num_limbs}}] memory cmT_y_limbs;
|
||||
|
||||
cmT_x_limbs = LimbsDecomposition.decompose(u_i_x_cmT[2]);
|
||||
cmT_y_limbs = LimbsDecomposition.decompose(u_i_x_cmT[3]);
|
||||
cmT_x_limbs = LimbsDecomposition.decompose(cmT_r[0]);
|
||||
cmT_y_limbs = LimbsDecomposition.decompose(cmT_r[1]);
|
||||
|
||||
for (uint8 k = 0; k < {{num_limbs}}; k++) {
|
||||
public_inputs[{{ z_len * 2 + 5 + num_limbs * 4 }} + 4 + k] = cmT_x_limbs[k];
|
||||
public_inputs[{{ z_len * 2 + 5 + num_limbs * 5}} + 4 + k] = cmT_y_limbs[k];
|
||||
public_inputs[{{ z_len * 2 + 2 + num_limbs * 4 }} + 4 + k] = cmT_x_limbs[k];
|
||||
public_inputs[{{ z_len * 2 + 2 + num_limbs * 5 }} + 4 + k] = cmT_y_limbs[k];
|
||||
}
|
||||
|
||||
// last element of the groth16 proof's public inputs is `r`
|
||||
public_inputs[{{ public_inputs_len - 2 }}] = U_i_u_u_i_u_r[2];
|
||||
|
||||
public_inputs[{{ public_inputs_len - 2 }}] = cmT_r[2];
|
||||
|
||||
bool success_g16 = this.verifyProof(pA, pB, pC, public_inputs);
|
||||
require(success_g16 == true, "Groth16: verifying proof failed");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user