Skip to content

Commit

Permalink
feat/circom prover removes ark-circom dep. (#312)
Browse files Browse the repository at this point in the history
* migrate circom-compact

* test: complete testing

* chore: adding some notes

* chore: update deps.

* move to_ethereum_proof/inputs to mopro-ffi

* remove unnecessary export

* chore: typo fix
  • Loading branch information
KimiWu123 authored Feb 6, 2025
1 parent 63fbe64 commit 0b53402
Show file tree
Hide file tree
Showing 19 changed files with 2,377 additions and 447 deletions.
514 changes: 206 additions & 308 deletions Cargo.lock

Large diffs are not rendered by default.

35 changes: 26 additions & 9 deletions circom-prover/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ edition = "2021"
name = "circom_prover"

[features]
default = ["rustwitness", "arkworks", "witnesscalc"]
default = ["rustwitness", "arkworks", "ethereum"]

# Witness Generation
rustwitness = [
Expand All @@ -19,7 +19,6 @@ witnesscalc = [

# Proof Generation
arkworks = [
"ark-circom",
"ark-serialize",
"ark-ec",
"ark-crypto-primitives",
Expand All @@ -29,9 +28,17 @@ arkworks = [
"ark-relations",
"ark-ff",
"ark-bls12-381",
"ark-poly",
"parallel",
]
rapidsnark = []

ethereum = ["ethers-core"]

# We need this feature for `ark_circom`
parallel = ["rayon"]


[dependencies]
num = { version = "0.4.0" }
num-traits = { version = "0.2.15", default-features = false }
Expand All @@ -41,16 +48,26 @@ num-bigint = { version = "0.4.3", default-features = false, features = [
anyhow = "1.0.95"
rust-witness = { version = "0.1.2", optional = true }
witnesscalc-adapter = { git = "https://github.com/zkmopro/witnesscalc_adapter.git", package = "witnesscalc-adapter", optional = true }
byteorder = { version = "1.0.0" }
uuid = { version = "1.9.1", features = ["v4"] }

# arkworks
ark-ec = { version = "=0.4.1", default-features = false, features = ["parallel"], optional = true }
ark-crypto-primitives = { version = "=0.4.0", optional = true }
ark-ff = { version = "=0.4.1", default-features = false, features = ["parallel", "asm"], optional = true }
ark-std = { version = "=0.4.0", default-features = false, features = ["parallel"], optional = true }
ark-bn254 = { version = "=0.4.0", optional = true }
ark-groth16 = { version = "=0.4.0", default-features = false, features = ["parallel"], optional = true }
ark-crypto-primitives = { version = "=0.4.0", optional = true }
ark-relations = { version = "0.4", default-features = false, optional = true }
uuid = { version = "1.9.1", features = ["v4"] }
byteorder = { version = "1.0.0", optional = true }
ark-ff = { version = "0.4.0", optional = true }
ark-bls12-381 = { version = "0.4.0", optional = true }
ark-circom = { git = "https://github.com/zkmopro/circom-compat.git", version = "0.1.0", branch = "wasm-delete", optional = true }
ark-bn254 = { version = "=0.4.0", optional = true }
ark-serialize = { version = "=0.4.1", features = ["derive"], optional = true }
ark-groth16 = { version = "=0.4.0", default-features = false, features = ["parallel"], optional = true }
ark-poly = { version = "=0.4.1", default-features = false, features = ["parallel"], optional = true}

# ethereum
ethers-core = { version = "=2.0.14", default-features = false, optional = true }
rayon = { version = "1.10.0", optional = true }

[dev-dependencies]
serde_json = "1.0.94"
hex-literal = "0.4.1"

23 changes: 2 additions & 21 deletions circom-prover/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,29 +11,10 @@ use witness::WitnessFn;
#[cfg(feature = "witnesscalc")]
pub use witnesscalc_adapter;

#[derive(Debug, Clone, Default)]
pub struct G1 {
pub x: String,
pub y: String,
}

#[derive(Debug, Clone, Default)]
pub struct G2 {
pub x: Vec<String>,
pub y: Vec<String>,
}

#[derive(Debug, Clone, Default)]
pub struct ProofCalldata {
pub a: G1,
pub b: G2,
pub c: G1,
}

#[derive(Debug, Clone)]
pub struct CircomPorver {}
pub struct CircomProver {}

impl CircomPorver {
impl CircomProver {
pub fn prove(
proof_lib: ProofLib,
wit_fn: WitnessFn,
Expand Down
4 changes: 4 additions & 0 deletions circom-prover/src/prover.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@ use anyhow::Result;
use num::BigUint;
use std::thread::JoinHandle;

#[cfg(feature = "arkworks")]
pub mod ark_circom;
#[cfg(feature = "arkworks")]
pub mod arkworks;
#[cfg(feature = "ethereum")]
pub mod ethereum;
#[cfg(feature = "arkworks")]
pub mod serialization;

Expand Down
15 changes: 15 additions & 0 deletions circom-prover/src/prover/ark_circom.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
mod zkey;
use ark_ec::pairing::Pairing;
pub use zkey::{read_proving_key, read_zkey, FieldSerialization, ZkeyHeaderReader};

mod qap;
pub use qap::CircomReduction;

mod circuit;
pub use circuit::CircomCircuit;

mod r1cs_reader;
pub use r1cs_reader::R1CSFile;

pub type Constraints<E> = (ConstraintVec<E>, ConstraintVec<E>, ConstraintVec<E>);
pub type ConstraintVec<E> = Vec<(usize, <E as Pairing>::ScalarField)>;
88 changes: 88 additions & 0 deletions circom-prover/src/prover/ark_circom/circuit.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// This file is copied from https://github.dev/zkmopro/circom-compat/tree/wasm-delete

use anyhow::Result;
use ark_ec::pairing::Pairing;
use ark_relations::r1cs::{
ConstraintSynthesizer, ConstraintSystemRef, LinearCombination, SynthesisError, Variable,
};

use super::r1cs_reader::R1CS;

#[derive(Clone, Debug)]
pub struct CircomCircuit<E: Pairing> {
pub r1cs: R1CS<E>,
pub witness: Option<Vec<E::ScalarField>>,
}

impl<E: Pairing> CircomCircuit<E> {
pub fn get_public_inputs(&self) -> Option<Vec<E::ScalarField>> {
match &self.witness {
None => None,
Some(w) => match &self.r1cs.wire_mapping {
None => Some(w[1..self.r1cs.num_inputs].to_vec()),
Some(m) => Some(m[1..self.r1cs.num_inputs].iter().map(|i| w[*i]).collect()),
},
}
}
}

impl<E: Pairing> ConstraintSynthesizer<E::ScalarField> for CircomCircuit<E> {
fn generate_constraints(
self,
cs: ConstraintSystemRef<E::ScalarField>,
) -> Result<(), SynthesisError> {
let witness = &self.witness;
let wire_mapping = &self.r1cs.wire_mapping;

// Start from 1 because Arkworks implicitly allocates One for the first input
for i in 1..self.r1cs.num_inputs {
cs.new_input_variable(|| {
Ok(match witness {
None => E::ScalarField::from(1u32),
Some(w) => match wire_mapping {
Some(m) => w[m[i]],
None => w[i],
},
})
})?;
}

for i in 0..self.r1cs.num_aux {
cs.new_witness_variable(|| {
Ok(match witness {
None => E::ScalarField::from(1u32),
Some(w) => match wire_mapping {
Some(m) => w[m[i + self.r1cs.num_inputs]],
None => w[i + self.r1cs.num_inputs],
},
})
})?;
}

let make_index = |index| {
if index < self.r1cs.num_inputs {
Variable::Instance(index)
} else {
Variable::Witness(index - self.r1cs.num_inputs)
}
};
let make_lc = |lc_data: &[(usize, E::ScalarField)]| {
lc_data.iter().fold(
LinearCombination::<E::ScalarField>::zero(),
|lc: LinearCombination<E::ScalarField>, (index, coeff)| {
lc + (*coeff, make_index(*index))
},
)
};

for constraint in &self.r1cs.constraints {
cs.enforce_constraint(
make_lc(&constraint.0),
make_lc(&constraint.1),
make_lc(&constraint.2),
)?;
}

Ok(())
}
}
112 changes: 112 additions & 0 deletions circom-prover/src/prover/ark_circom/qap.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
// This file is copied from https://github.dev/zkmopro/circom-compat/tree/wasm-delete

use ark_ff::PrimeField;
use ark_groth16::r1cs_to_qap::{evaluate_constraint, LibsnarkReduction, R1CSToQAP};
use ark_poly::EvaluationDomain;
use ark_relations::r1cs::{ConstraintMatrices, ConstraintSystemRef, SynthesisError};
use ark_std::{cfg_into_iter, cfg_iter, cfg_iter_mut, vec};

// We need this for `cfg_iter_mut`.
#[cfg(feature = "parallel")]
use rayon::prelude::*;

/// Implements the witness map used by snarkjs. The arkworks witness map calculates the
/// coefficients of H through computing (AB-C)/Z in the evaluation domain and going back to the
/// coefficients domain. snarkjs instead precomputes the Lagrange form of the powers of tau bases
/// in a domain twice as large and the witness map is computed as the odd coefficients of (AB-C)
/// in that domain. This serves as HZ when computing the C proof element.
pub struct CircomReduction;

impl R1CSToQAP for CircomReduction {
#[allow(clippy::type_complexity)]
fn instance_map_with_evaluation<F: PrimeField, D: EvaluationDomain<F>>(
cs: ConstraintSystemRef<F>,
t: &F,
) -> Result<(Vec<F>, Vec<F>, Vec<F>, F, usize, usize), SynthesisError> {
LibsnarkReduction::instance_map_with_evaluation::<F, D>(cs, t)
}

fn witness_map_from_matrices<F: PrimeField, D: EvaluationDomain<F>>(
matrices: &ConstraintMatrices<F>,
num_inputs: usize,
num_constraints: usize,
full_assignment: &[F],
) -> Result<Vec<F>, SynthesisError> {
let zero = F::zero();
let domain =
D::new(num_constraints + num_inputs).ok_or(SynthesisError::PolynomialDegreeTooLarge)?;
let domain_size = domain.size();

let mut a = vec![zero; domain_size];
let mut b = vec![zero; domain_size];

cfg_iter_mut!(a[..num_constraints])
.zip(cfg_iter_mut!(b[..num_constraints]))
.zip(cfg_iter!(&matrices.a))
.zip(cfg_iter!(&matrices.b))
.for_each(|(((a, b), at_i), bt_i)| {
*a = evaluate_constraint(at_i, full_assignment);
*b = evaluate_constraint(bt_i, full_assignment);
});

{
let start = num_constraints;
let end = start + num_inputs;
a[start..end].clone_from_slice(&full_assignment[..num_inputs]);
}

let mut c = vec![zero; domain_size];
cfg_iter_mut!(c[..num_constraints])
.zip(&a)
.zip(&b)
.for_each(|((c_i, &a), &b)| {
*c_i = a * b;
});

domain.ifft_in_place(&mut a);
domain.ifft_in_place(&mut b);

let root_of_unity = {
let domain_size_double = 2 * domain_size;
let domain_double =
D::new(domain_size_double).ok_or(SynthesisError::PolynomialDegreeTooLarge)?;
domain_double.element(1)
};
D::distribute_powers_and_mul_by_const(&mut a, root_of_unity, F::one());
D::distribute_powers_and_mul_by_const(&mut b, root_of_unity, F::one());

domain.fft_in_place(&mut a);
domain.fft_in_place(&mut b);

let mut ab = domain.mul_polynomials_in_evaluation_domain(&a, &b);
drop(a);
drop(b);

domain.ifft_in_place(&mut c);
D::distribute_powers_and_mul_by_const(&mut c, root_of_unity, F::one());
domain.fft_in_place(&mut c);

cfg_iter_mut!(ab)
.zip(c)
.for_each(|(ab_i, c_i)| *ab_i -= &c_i);

Ok(ab)
}

fn h_query_scalars<F: PrimeField, D: EvaluationDomain<F>>(
max_power: usize,
t: F,
_: F,
delta_inverse: F,
) -> Result<Vec<F>, SynthesisError> {
// the usual H query has domain-1 powers. Z has domain powers. So HZ has 2*domain-1 powers.
let mut scalars = cfg_into_iter!(0..2 * max_power + 1)
.map(|i| delta_inverse * t.pow([i as u64]))
.collect::<Vec<_>>();
let domain_size = scalars.len();
let domain = D::new(domain_size).ok_or(SynthesisError::PolynomialDegreeTooLarge)?;
// generate the lagrange coefficients
domain.ifft_in_place(&mut scalars);
Ok(cfg_into_iter!(scalars).skip(1).step_by(2).collect())
}
}
Loading

0 comments on commit 0b53402

Please sign in to comment.