Skip to content

Commit 9414c45

Browse files
authored
Bump ark circom (#221)
* Use latest `ark-circom` * Fmt & clippy * Fix noir & noname * Fix wasm * Fmt * Better approach to adjust indexes * Fix clippy
1 parent d2f4ed1 commit 9414c45

File tree

11 files changed

+198
-196
lines changed

11 files changed

+198
-196
lines changed

Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,12 @@ rayon = { version = "1" }
3737
revm = { version = "19.5.0", default-features = false }
3838
rust-crypto = { version = "0.2" }
3939
thiserror = { version = "1.0" }
40+
tokio = "1.44.1"
41+
wasmer = { version = "4.4.0", default-features = false }
4042

4143
# Arkworks family
4244
ark-bn254 = { version = "^0.5.0", default-features = false }
43-
ark-circom = { git = "https://github.com/winderica/circom-compat", rev = "9f8d7c", default-features = false } # "arkworks-next" branch
45+
ark-circom = { version = "^0.5.0", default-features = false }
4446
ark-crypto-primitives = { version = "^0.5.0", default-features = false }
4547
ark-ec = { version = "^0.5.0", default-features = false }
4648
ark-ff = { version = "^0.5.0", default-features = false }

experimental-frontends/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ acvm = { workspace = true }
1818
folding-schemes = { workspace = true }
1919
serde = { workspace = true, features = ["derive"] }
2020
serde_json = { workspace = true }
21+
tokio = { workspace = true }
22+
wasmer = { workspace = true }
2123

2224
[dev-dependencies]
2325
ark-bn254 = { workspace = true, features = ["r1cs"] }

experimental-frontends/src/circom/mod.rs

Lines changed: 84 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
1-
use ark_circom::circom::{CircomCircuit, R1CS as CircomR1CS};
1+
use ark_circom::circom::R1CS as CircomR1CS;
22
use ark_ff::PrimeField;
3-
use ark_r1cs_std::alloc::AllocVar;
4-
use ark_r1cs_std::fields::fp::FpVar;
5-
use ark_r1cs_std::fields::fp::FpVar::Var;
6-
use ark_r1cs_std::R1CSVar;
7-
use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystemRef, SynthesisError};
3+
use ark_r1cs_std::{
4+
fields::fp::{AllocatedFp, FpVar},
5+
R1CSVar,
6+
};
7+
use ark_relations::{
8+
lc,
9+
r1cs::{ConstraintSystemRef, SynthesisError, Variable},
10+
};
811
use ark_std::fmt::Debug;
912
use folding_schemes::{frontend::FCircuit, utils::PathOrBin, Error};
10-
use num_bigint::BigInt;
13+
use num_bigint::{BigInt, BigUint};
1114

1215
pub mod utils;
1316
use crate::utils::{VecF, VecFpVar};
@@ -17,7 +20,7 @@ use utils::CircomWrapper;
1720
/// The parameter `EIL` indicates the length of the ExternalInputs vector of field elements.
1821
#[derive(Clone, Debug)]
1922
pub struct CircomFCircuit<F: PrimeField, const SL: usize, const EIL: usize> {
20-
circom_wrapper: CircomWrapper<F>,
23+
circom_wrapper: CircomWrapper,
2124
r1cs: CircomR1CS<F>,
2225
}
2326

@@ -54,76 +57,107 @@ impl<F: PrimeField, const SL: usize, const EIL: usize> FCircuit<F> for CircomFCi
5457
#[cfg(test)]
5558
assert_eq!(external_inputs.0.len(), EIL);
5659

57-
let input_values = self.fpvars_to_bigints(&z_i)?;
60+
let input_values = Self::fpvars_to_bigints(&z_i);
5861
let mut inputs_map = vec![("ivc_input".to_string(), input_values)];
5962

6063
if EIL > 0 {
61-
let external_inputs_bi = self.fpvars_to_bigints(&external_inputs.0)?;
64+
let external_inputs_bi = Self::fpvars_to_bigints(&external_inputs.0);
6265
inputs_map.push(("external_inputs".to_string(), external_inputs_bi));
6366
}
6467

68+
// The layout of `witness` is as follows:
69+
// [
70+
// 1, // The constant 1 is implicitly allocated by Arkworks
71+
// ...z_{i + 1}, // The next state marked as `signal output` in the circom circuit
72+
// ...z_i, // The current state marked as `signal input` in the circom circuit
73+
// ...external_inputs, // The optional external inputs marked as `external input` in the circom circuit
74+
// ...aux, // The intermediate witnesses
75+
// ]
76+
// Here, 1, z_i, and external_inputs have already been allocated in the
77+
// constraint system, while z_{i + 1} and aux are yet to be allocated.
6578
let witness = self
6679
.circom_wrapper
67-
.extract_witness(&inputs_map)
80+
.extract_witness(inputs_map)
6881
.map_err(|_| SynthesisError::AssignmentMissing)?;
6982

70-
// Since public inputs are already allocated variables, we will tell `circom-compat` to not re-allocate those
71-
let mut already_allocated_public_inputs = vec![];
72-
for var in z_i.iter() {
73-
match var {
74-
Var(var) => already_allocated_public_inputs.push(var.variable),
75-
_ => return Err(SynthesisError::Unsatisfiable), // allocated z_i should be Var
76-
}
83+
// In order to convert the indexes of variables in the circom circuit to
84+
// those in the arkworks circuit, we adopt the tricks from
85+
// https://github.com/arnaucube/circom-compat/pull/1
86+
87+
// Since our cs might already have allocated constraints,
88+
// We store a mapping between circom's defined indexes and the newly obtained cs indexes
89+
let mut circom_index_to_cs_index = vec![];
90+
91+
// Constant 1 at idx 0 is already allocated by arkworks
92+
circom_index_to_cs_index.push(Variable::One);
93+
94+
// Allocate the next state (1..1 + SL) as witness, and at the same time,
95+
// record the allocated variable's index in `circom_index_to_cs_index`.
96+
// Cf. https://github.com/arnaucube/circom-compat/blob/22c8f5/src/circom/circuit.rs#L56-L86
97+
let mut z_i1 = vec![];
98+
for &w in witness.iter().skip(1).take(SL) {
99+
let v = cs.new_witness_variable(|| Ok(w))?;
100+
circom_index_to_cs_index.push(v);
101+
z_i1.push(FpVar::Var(AllocatedFp::new(Some(w), v, cs.clone())));
77102
}
78103

79-
// Initializes the CircomCircuit.
80-
let circom_circuit = CircomCircuit {
81-
r1cs: self.r1cs.clone(),
82-
witness: Some(witness.clone()),
83-
public_inputs_indexes: already_allocated_public_inputs,
84-
allocate_inputs_as_witnesses: true,
85-
};
104+
// `z_i` and `external_inputs` have already been allocated as witness,
105+
// so we just record their indexes in `circom_index_to_cs_index`.
106+
// Cf. https://github.com/arnaucube/circom-compat/blob/22c8f5/src/circom/circuit.rs#L89-L95
107+
for v in z_i.iter().chain(&external_inputs.0) {
108+
match v {
109+
FpVar::Var(v) => circom_index_to_cs_index.push(v.variable),
110+
// safe because `z_i` and `external_inputs` are allocated as
111+
// witness (not constant)
112+
_ => unreachable!(),
113+
};
114+
}
86115

87-
// Generates the constraints for the circom_circuit.
88-
circom_circuit.generate_constraints(cs.clone())?;
116+
// Allocate the remaining aux variables as witness.
117+
// Also, record their indexes in `circom_index_to_cs_index`.
118+
// Cf. https://github.com/arnaucube/circom-compat/blob/22c8f5/src/circom/circuit.rs#L106-L121
119+
for w in witness.into_iter().skip(circom_index_to_cs_index.len()) {
120+
circom_index_to_cs_index.push(cs.new_witness_variable(|| Ok(w))?);
121+
}
122+
123+
let fold_lc = |lc, &(i, coeff)| lc + (coeff, circom_index_to_cs_index[i]);
89124

90-
// TODO: https://github.com/privacy-scaling-explorations/sonobe/issues/104
91-
// We disable checking constraints for now
92-
// Checks for constraint satisfaction.
93-
// if !cs.is_satisfied().unwrap() {
94-
// return Err(SynthesisError::Unsatisfiable);
95-
// }
125+
// Generates the constraints for the circom_circuit.
126+
for (a, b, c) in &self.r1cs.constraints {
127+
cs.enforce_constraint(
128+
a.iter().fold(lc!(), fold_lc),
129+
b.iter().fold(lc!(), fold_lc),
130+
c.iter().fold(lc!(), fold_lc),
131+
)?;
132+
}
96133

97-
// Extracts the z_i1(next state) from the witness vector.
98-
let z_i1: Vec<FpVar<F>> =
99-
Vec::<FpVar<F>>::new_witness(cs.clone(), || Ok(witness[1..1 + SL].to_vec()))?;
134+
#[cfg(test)]
135+
if !cs.is_in_setup_mode() && !cs.is_satisfied()? {
136+
return Err(SynthesisError::Unsatisfiable);
137+
}
100138

101139
Ok(z_i1)
102140
}
103141
}
104142

105143
impl<F: PrimeField, const SL: usize, const EIL: usize> CircomFCircuit<F, SL, EIL> {
106-
fn fpvars_to_bigints(&self, fpvars: &[FpVar<F>]) -> Result<Vec<BigInt>, SynthesisError> {
107-
let mut input_values = Vec::new();
108-
// converts each FpVar to PrimeField value, then to num_bigint::BigInt.
109-
for fp_var in fpvars.iter() {
110-
// extracts the PrimeField value from FpVar.
111-
let primefield_value = fp_var.value()?;
112-
// converts the PrimeField value to num_bigint::BigInt.
113-
let num_bigint_value = self
114-
.circom_wrapper
115-
.ark_primefield_to_num_bigint(primefield_value);
116-
input_values.push(num_bigint_value);
117-
}
118-
Ok(input_values)
144+
fn fpvars_to_bigints(fpvars: &[FpVar<F>]) -> Vec<BigInt> {
145+
fpvars
146+
.value()
147+
.unwrap_or(vec![F::zero(); fpvars.len()])
148+
.into_iter()
149+
.map(Into::<BigUint>::into)
150+
.map(BigInt::from)
151+
.collect()
119152
}
120153
}
121154

122155
#[cfg(test)]
123156
pub mod tests {
124157
use super::*;
125158
use ark_bn254::Fr;
126-
use ark_relations::r1cs::ConstraintSystem;
159+
use ark_r1cs_std::alloc::AllocVar;
160+
use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystem};
127161
use std::path::PathBuf;
128162

129163
/// Native implementation of `src/circom/test_folder/cubic_circuit.r1cs`

0 commit comments

Comments
 (0)