Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions circom-prover/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,21 @@ mod tests {
assert!(verify_proof(proof, ProofLib::Arkworks));
}

#[cfg(all(feature = "rustwitness", feature = "arkworks"))]
#[test]
fn test_rustwitness_arkworks_bls12_381_prove_and_verify() {
rust_witness::witness!(mux);
let inputs = HashMap::from([("in".to_string(), vec!["42".to_string()])]);
let zkey_path = "./test-vectors/mux.zkey";
let input_str = serde_json::to_string(&inputs).unwrap();
let proof_lib = ProofLib::Arkworks;
let witness_fn = WitnessFn::RustWitness(mux_witness);
let proof =
CircomProver::prove(proof_lib, witness_fn, input_str, zkey_path.to_string()).unwrap();
let valid = CircomProver::verify(proof_lib, proof, zkey_path.to_string()).unwrap();
assert!(valid);
}

#[cfg(all(feature = "witnesscalc", feature = "arkworks"))]
#[test]
fn test_witnesscalc_arkworks_prove_and_verify() {
Expand Down
142 changes: 122 additions & 20 deletions circom-prover/src/prover/ark_circom/qap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,32 +61,76 @@ impl R1CSToQAP for CircomReduction {
*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)
let s = F::TWO_ADICITY as u64;
let power = ark_std::log2(domain_size) as u64;
let k: u64 = if s == 32 { 5 } else { 1 };

// Get snarkjs-compatible generator for this domain size
let snarkjs_omega = {
let exp = 1u64 << (s - power);
F::TWO_ADIC_ROOT_OF_UNITY.pow([k]).pow([exp])
};
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());
let snarkjs_omega_inv = snarkjs_omega.inverse().unwrap();

domain.fft_in_place(&mut a);
domain.fft_in_place(&mut b);
// Coset shift = snarkjs w[power+1]
let inc = {
let exp = 1u64 << (s - power - 1);
F::TWO_ADIC_ROOT_OF_UNITY.pow([k]).pow([exp])
};

if k != 1 {
// Use manual DFT with snarkjs's roots
let n_inv = F::from(domain_size as u64).inverse().unwrap();

manual_ifft(&mut a, snarkjs_omega_inv, n_inv, domain_size);
manual_ifft(&mut b, snarkjs_omega_inv, n_inv, domain_size);

apply_powers(&mut a, inc);
apply_powers(&mut b, inc);

manual_fft(&mut a, snarkjs_omega, domain_size);
manual_fft(&mut b, snarkjs_omega, domain_size);

let mut ab: Vec<F> = a.iter().zip(&b).map(|(&ai, &bi)| ai * bi).collect();

manual_ifft(&mut c, snarkjs_omega_inv, n_inv, domain_size);
apply_powers(&mut c, inc);
manual_fft(&mut c, snarkjs_omega, domain_size);

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

let mut ab = domain.mul_polynomials_in_evaluation_domain(&a, &b);
Ok(ab)
} else {
// BN254: use arkworks FFT as before (original CircomReduction logic)
domain.ifft_in_place(&mut a);
domain.ifft_in_place(&mut 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);
let root_of_unity = {
let domain_double =
D::new(2 * domain_size).ok_or(SynthesisError::PolynomialDegreeTooLarge)?;
domain_double.element(1)
};

ab.par_iter_mut()
.zip(c)
.for_each(|(ab_i, c_i)| *ab_i -= &c_i);
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());

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

let mut ab = domain.mul_polynomials_in_evaluation_domain(&a, &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);

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

Ok(ab)
}
}

fn h_query_scalars<F: PrimeField, D: EvaluationDomain<F>>(
Expand All @@ -107,3 +151,61 @@ impl R1CSToQAP for CircomReduction {
Ok(scalars.into_par_iter().skip(1).step_by(2).collect())
}
}

fn manual_fft<F: PrimeField>(vals: &mut Vec<F>, omega: F, n: usize) {
let log_n = ark_std::log2(n) as usize;

// Bit-reverse permutation
for i in 0..n {
let j = bit_reverse(i, log_n);
if i < j {
vals.swap(i, j);
}
}

// Cooley-Tukey
let mut len = 2;
while len <= n {
let half = len / 2;
let step = n / len;
// omega_len = omega^step = primitive len-th root
let omega_len = omega.pow([step as u64]);

for start in (0..n).step_by(len) {
let mut w = F::one();
for j in 0..half {
let u = vals[start + j];
let v = vals[start + j + half] * w;
vals[start + j] = u + v;
vals[start + j + half] = u - v;
w *= omega_len;
}
}
len <<= 1;
}
}

fn manual_ifft<F: PrimeField>(vals: &mut Vec<F>, omega_inv: F, n_inv: F, n: usize) {
manual_fft(vals, omega_inv, n);
// Scale by 1/n
for v in vals.iter_mut() {
*v *= n_inv;
}
}

fn apply_powers<F: PrimeField>(vals: &mut Vec<F>, base: F) {
let mut pow = F::one();
for v in vals.iter_mut() {
*v *= pow;
pow *= base;
}
}

fn bit_reverse(mut x: usize, log_n: usize) -> usize {
let mut result = 0;
for _ in 0..log_n {
result = (result << 1) | (x & 1);
x >>= 1;
}
result
}
Binary file added circom-prover/test-vectors/multiplexer.wasm
Binary file not shown.
Binary file added circom-prover/test-vectors/multiplexer_final.zkey
Binary file not shown.
Binary file added circom-prover/test-vectors/multiplier2_bls.wasm
Binary file not shown.
Binary file added circom-prover/test-vectors/mux.wasm
Binary file not shown.
Binary file added circom-prover/test-vectors/mux.zkey
Binary file not shown.
1 change: 1 addition & 0 deletions circom/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
out/
10 changes: 10 additions & 0 deletions circom/circuits/multiplexer.circom
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
pragma circom 2.1.9;

template Demo() {
signal input in;
signal output out;

signal x <-- 1;
out <== in * x;
}
component main = Demo();
15 changes: 15 additions & 0 deletions circom/circuits/mux.circom
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
pragma circom 2.2.3;

include "circomlib/circuits/multiplexer.circom";

template Demo {
signal input in;
signal output out;

component dotCheck = Multiplexer(1,2);
dotCheck.inp[0][0] <== 0;
dotCheck.inp[1][0] <== in;
dotCheck.sel <== 1;
dotCheck.out[0] ==> out;
}
component main = Demo();
3 changes: 3 additions & 0 deletions circom/input_mux.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"in": 42
}
Loading
Loading