Migrate usage from E:Pairing to F:PrimeField when Pairing is not needed

The motivation to do so, is so we can use the witness generation with
other curves that don't have pairings (and hence the Pairing trait
implemented).
This commit is contained in:
2024-02-22 11:15:49 +01:00
parent 170b10fc9e
commit 5f8cfd49be
8 changed files with 73 additions and 79 deletions

View File

@@ -1,4 +1,4 @@
use ark_ec::pairing::Pairing; use ark_ff::PrimeField;
use std::{fs::File, path::Path}; use std::{fs::File, path::Path};
use super::{CircomCircuit, R1CS}; use super::{CircomCircuit, R1CS};
@@ -10,20 +10,20 @@ use crate::{circom::R1CSFile, witness::WitnessCalculator};
use color_eyre::Result; use color_eyre::Result;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct CircomBuilder<E: Pairing> { pub struct CircomBuilder<F: PrimeField> {
pub cfg: CircomConfig<E>, pub cfg: CircomConfig<F>,
pub inputs: HashMap<String, Vec<BigInt>>, pub inputs: HashMap<String, Vec<BigInt>>,
} }
// Add utils for creating this from files / directly from bytes // Add utils for creating this from files / directly from bytes
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct CircomConfig<E: Pairing> { pub struct CircomConfig<F: PrimeField> {
pub r1cs: R1CS<E>, pub r1cs: R1CS<F>,
pub wtns: WitnessCalculator, pub wtns: WitnessCalculator,
pub sanity_check: bool, pub sanity_check: bool,
} }
impl<E: Pairing> CircomConfig<E> { impl<F: PrimeField> CircomConfig<F> {
pub fn new(wtns: impl AsRef<Path>, r1cs: impl AsRef<Path>) -> Result<Self> { pub fn new(wtns: impl AsRef<Path>, r1cs: impl AsRef<Path>) -> Result<Self> {
let wtns = WitnessCalculator::new(wtns).unwrap(); let wtns = WitnessCalculator::new(wtns).unwrap();
let reader = File::open(r1cs)?; let reader = File::open(r1cs)?;
@@ -36,10 +36,10 @@ impl<E: Pairing> CircomConfig<E> {
} }
} }
impl<E: Pairing> CircomBuilder<E> { impl<F: PrimeField> CircomBuilder<F> {
/// Instantiates a new builder using the provided WitnessGenerator and R1CS files /// Instantiates a new builder using the provided WitnessGenerator and R1CS files
/// for your circuit /// for your circuit
pub fn new(cfg: CircomConfig<E>) -> Self { pub fn new(cfg: CircomConfig<F>) -> Self {
Self { Self {
cfg, cfg,
inputs: HashMap::new(), inputs: HashMap::new(),
@@ -54,7 +54,7 @@ impl<E: Pairing> CircomBuilder<E> {
/// Generates an empty circom circuit with no witness set, to be used for /// Generates an empty circom circuit with no witness set, to be used for
/// generation of the trusted setup parameters /// generation of the trusted setup parameters
pub fn setup(&self) -> CircomCircuit<E> { pub fn setup(&self) -> CircomCircuit<F> {
let mut circom = CircomCircuit { let mut circom = CircomCircuit {
r1cs: self.cfg.r1cs.clone(), r1cs: self.cfg.r1cs.clone(),
witness: None, witness: None,
@@ -68,20 +68,20 @@ impl<E: Pairing> CircomBuilder<E> {
/// Creates the circuit populated with the witness corresponding to the previously /// Creates the circuit populated with the witness corresponding to the previously
/// provided inputs /// provided inputs
pub fn build(mut self) -> Result<CircomCircuit<E>> { pub fn build(mut self) -> Result<CircomCircuit<F>> {
let mut circom = self.setup(); let mut circom = self.setup();
// calculate the witness // calculate the witness
let witness = self let witness = self
.cfg .cfg
.wtns .wtns
.calculate_witness_element::<E, _>(self.inputs, self.cfg.sanity_check)?; .calculate_witness_element::<F, _>(self.inputs, self.cfg.sanity_check)?;
circom.witness = Some(witness); circom.witness = Some(witness);
// sanity check // sanity check
debug_assert!({ debug_assert!({
use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystem}; use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystem};
let cs = ConstraintSystem::<E::ScalarField>::new_ref(); let cs = ConstraintSystem::<F>::new_ref();
circom.clone().generate_constraints(cs.clone()).unwrap(); circom.clone().generate_constraints(cs.clone()).unwrap();
let is_satisfied = cs.is_satisfied().unwrap(); let is_satisfied = cs.is_satisfied().unwrap();
if !is_satisfied { if !is_satisfied {

View File

@@ -1,4 +1,4 @@
use ark_ec::pairing::Pairing; use ark_ff::PrimeField;
use ark_relations::r1cs::{ use ark_relations::r1cs::{
ConstraintSynthesizer, ConstraintSystemRef, LinearCombination, SynthesisError, Variable, ConstraintSynthesizer, ConstraintSystemRef, LinearCombination, SynthesisError, Variable,
}; };
@@ -8,13 +8,13 @@ use super::R1CS;
use color_eyre::Result; use color_eyre::Result;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct CircomCircuit<E: Pairing> { pub struct CircomCircuit<F: PrimeField> {
pub r1cs: R1CS<E>, pub r1cs: R1CS<F>,
pub witness: Option<Vec<E::ScalarField>>, pub witness: Option<Vec<F>>,
} }
impl<E: Pairing> CircomCircuit<E> { impl<F: PrimeField> CircomCircuit<F> {
pub fn get_public_inputs(&self) -> Option<Vec<E::ScalarField>> { pub fn get_public_inputs(&self) -> Option<Vec<F>> {
match &self.witness { match &self.witness {
None => None, None => None,
Some(w) => match &self.r1cs.wire_mapping { Some(w) => match &self.r1cs.wire_mapping {
@@ -25,11 +25,8 @@ impl<E: Pairing> CircomCircuit<E> {
} }
} }
impl<E: Pairing> ConstraintSynthesizer<E::ScalarField> for CircomCircuit<E> { impl<F: PrimeField> ConstraintSynthesizer<F> for CircomCircuit<F> {
fn generate_constraints( fn generate_constraints(self, cs: ConstraintSystemRef<F>) -> Result<(), SynthesisError> {
self,
cs: ConstraintSystemRef<E::ScalarField>,
) -> Result<(), SynthesisError> {
let witness = &self.witness; let witness = &self.witness;
let wire_mapping = &self.r1cs.wire_mapping; let wire_mapping = &self.r1cs.wire_mapping;
@@ -37,7 +34,7 @@ impl<E: Pairing> ConstraintSynthesizer<E::ScalarField> for CircomCircuit<E> {
for i in 1..self.r1cs.num_inputs { for i in 1..self.r1cs.num_inputs {
cs.new_input_variable(|| { cs.new_input_variable(|| {
Ok(match witness { Ok(match witness {
None => E::ScalarField::from(1u32), None => F::from(1u32),
Some(w) => match wire_mapping { Some(w) => match wire_mapping {
Some(m) => w[m[i]], Some(m) => w[m[i]],
None => w[i], None => w[i],
@@ -49,7 +46,7 @@ impl<E: Pairing> ConstraintSynthesizer<E::ScalarField> for CircomCircuit<E> {
for i in 0..self.r1cs.num_aux { for i in 0..self.r1cs.num_aux {
cs.new_witness_variable(|| { cs.new_witness_variable(|| {
Ok(match witness { Ok(match witness {
None => E::ScalarField::from(1u32), None => F::from(1u32),
Some(w) => match wire_mapping { Some(w) => match wire_mapping {
Some(m) => w[m[i + self.r1cs.num_inputs]], Some(m) => w[m[i + self.r1cs.num_inputs]],
None => w[i + self.r1cs.num_inputs], None => w[i + self.r1cs.num_inputs],
@@ -65,12 +62,10 @@ impl<E: Pairing> ConstraintSynthesizer<E::ScalarField> for CircomCircuit<E> {
Variable::Witness(index - self.r1cs.num_inputs) Variable::Witness(index - self.r1cs.num_inputs)
} }
}; };
let make_lc = |lc_data: &[(usize, E::ScalarField)]| { let make_lc = |lc_data: &[(usize, F)]| {
lc_data.iter().fold( lc_data.iter().fold(
LinearCombination::<E::ScalarField>::zero(), LinearCombination::<F>::zero(),
|lc: LinearCombination<E::ScalarField>, (index, coeff)| { |lc: LinearCombination<F>, (index, coeff)| lc + (*coeff, make_index(*index)),
lc + (*coeff, make_index(*index))
},
) )
}; };
@@ -90,12 +85,12 @@ impl<E: Pairing> ConstraintSynthesizer<E::ScalarField> for CircomCircuit<E> {
mod tests { mod tests {
use super::*; use super::*;
use crate::{CircomBuilder, CircomConfig}; use crate::{CircomBuilder, CircomConfig};
use ark_bn254::{Bn254, Fr}; use ark_bn254::Fr;
use ark_relations::r1cs::ConstraintSystem; use ark_relations::r1cs::ConstraintSystem;
#[test] #[test]
fn satisfied() { fn satisfied() {
let cfg = CircomConfig::<Bn254>::new( let cfg = CircomConfig::<Fr>::new(
"./test-vectors/mycircuit.wasm", "./test-vectors/mycircuit.wasm",
"./test-vectors/mycircuit.r1cs", "./test-vectors/mycircuit.r1cs",
) )

View File

@@ -1,5 +1,3 @@
use ark_ec::pairing::Pairing;
pub mod r1cs_reader; pub mod r1cs_reader;
pub use r1cs_reader::{R1CSFile, R1CS}; pub use r1cs_reader::{R1CSFile, R1CS};
@@ -12,5 +10,5 @@ pub use builder::{CircomBuilder, CircomConfig};
mod qap; mod qap;
pub use qap::CircomReduction; pub use qap::CircomReduction;
pub type Constraints<E> = (ConstraintVec<E>, ConstraintVec<E>, ConstraintVec<E>); pub type Constraints<F> = (ConstraintVec<F>, ConstraintVec<F>, ConstraintVec<F>);
pub type ConstraintVec<E> = Vec<(usize, <E as Pairing>::ScalarField)>; pub type ConstraintVec<F> = Vec<(usize, F)>;

View File

@@ -4,8 +4,8 @@
use byteorder::{LittleEndian, ReadBytesExt}; use byteorder::{LittleEndian, ReadBytesExt};
use std::io::{Error, ErrorKind}; use std::io::{Error, ErrorKind};
use ark_ec::pairing::Pairing; use ark_ff::PrimeField;
use ark_serialize::{CanonicalDeserialize, SerializationError, SerializationError::IoError}; use ark_serialize::{SerializationError, SerializationError::IoError};
use ark_std::io::{Read, Seek, SeekFrom}; use ark_std::io::{Read, Seek, SeekFrom};
use std::collections::HashMap; use std::collections::HashMap;
@@ -15,16 +15,16 @@ type IoResult<T> = Result<T, SerializationError>;
use super::{ConstraintVec, Constraints}; use super::{ConstraintVec, Constraints};
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct R1CS<E: Pairing> { pub struct R1CS<F: PrimeField> {
pub num_inputs: usize, pub num_inputs: usize,
pub num_aux: usize, pub num_aux: usize,
pub num_variables: usize, pub num_variables: usize,
pub constraints: Vec<Constraints<E>>, pub constraints: Vec<Constraints<F>>,
pub wire_mapping: Option<Vec<usize>>, pub wire_mapping: Option<Vec<usize>>,
} }
impl<E: Pairing> From<R1CSFile<E>> for R1CS<E> { impl<F: PrimeField> From<R1CSFile<F>> for R1CS<F> {
fn from(file: R1CSFile<E>) -> Self { fn from(file: R1CSFile<F>) -> Self {
let num_inputs = (1 + file.header.n_pub_in + file.header.n_pub_out) as usize; let num_inputs = (1 + file.header.n_pub_in + file.header.n_pub_out) as usize;
let num_variables = file.header.n_wires as usize; let num_variables = file.header.n_wires as usize;
let num_aux = num_variables - num_inputs; let num_aux = num_variables - num_inputs;
@@ -38,20 +38,20 @@ impl<E: Pairing> From<R1CSFile<E>> for R1CS<E> {
} }
} }
pub struct R1CSFile<E: Pairing> { pub struct R1CSFile<F: PrimeField> {
pub version: u32, pub version: u32,
pub header: Header, pub header: Header,
pub constraints: Vec<Constraints<E>>, pub constraints: Vec<Constraints<F>>,
pub wire_mapping: Vec<u64>, pub wire_mapping: Vec<u64>,
} }
impl<E: Pairing> R1CSFile<E> { impl<F: PrimeField> R1CSFile<F> {
/// reader must implement the Seek trait, for example with a Cursor /// reader must implement the Seek trait, for example with a Cursor
/// ///
/// ```rust,ignore /// ```rust,ignore
/// let reader = BufReader::new(Cursor::new(&data[..])); /// let reader = BufReader::new(Cursor::new(&data[..]));
/// ``` /// ```
pub fn new<R: Read + Seek>(mut reader: R) -> IoResult<R1CSFile<E>> { pub fn new<R: Read + Seek>(mut reader: R) -> IoResult<R1CSFile<F>> {
let mut magic = [0u8; 4]; let mut magic = [0u8; 4];
reader.read_exact(&mut magic)?; reader.read_exact(&mut magic)?;
if magic != [0x72, 0x31, 0x63, 0x73] { if magic != [0x72, 0x31, 0x63, 0x73] {
@@ -117,7 +117,7 @@ impl<E: Pairing> R1CSFile<E> {
reader.seek(SeekFrom::Start(*constraint_offset?))?; reader.seek(SeekFrom::Start(*constraint_offset?))?;
let constraints = read_constraints::<&mut R, E>(&mut reader, &header)?; let constraints = read_constraints::<&mut R, F>(&mut reader, &header)?;
let wire2label_offset = sec_offsets.get(&wire2label_type).ok_or_else(|| { let wire2label_offset = sec_offsets.get(&wire2label_type).ok_or_else(|| {
Error::new( Error::new(
@@ -177,15 +177,16 @@ impl Header {
let mut prime_size = vec![0u8; field_size as usize]; let mut prime_size = vec![0u8; field_size as usize];
reader.read_exact(&mut prime_size)?; reader.read_exact(&mut prime_size)?;
if prime_size // TODO WIP
!= hex::decode("010000f093f5e1439170b97948e833285d588181b64550b829a031e1724e6430") // if prime_size
.unwrap() // != hex::decode("010000f093f5e1439170b97948e833285d588181b64550b829a031e1724e6430")
{ // .unwrap()
return Err(IoError(Error::new( // {
ErrorKind::InvalidData, // return Err(IoError(Error::new(
"This parser only supports bn256", // ErrorKind::InvalidData,
))); // "This parser only supports bn256",
} // )));
// }
Ok(Header { Ok(Header {
field_size, field_size,
@@ -200,29 +201,29 @@ impl Header {
} }
} }
fn read_constraint_vec<R: Read, E: Pairing>(mut reader: R) -> IoResult<ConstraintVec<E>> { fn read_constraint_vec<R: Read, F: PrimeField>(mut reader: R) -> IoResult<ConstraintVec<F>> {
let n_vec = reader.read_u32::<LittleEndian>()? as usize; let n_vec = reader.read_u32::<LittleEndian>()? as usize;
let mut vec = Vec::with_capacity(n_vec); let mut vec = Vec::with_capacity(n_vec);
for _ in 0..n_vec { for _ in 0..n_vec {
vec.push(( vec.push((
reader.read_u32::<LittleEndian>()? as usize, reader.read_u32::<LittleEndian>()? as usize,
E::ScalarField::deserialize_uncompressed(&mut reader)?, F::deserialize_uncompressed(&mut reader)?,
)); ));
} }
Ok(vec) Ok(vec)
} }
fn read_constraints<R: Read, E: Pairing>( fn read_constraints<R: Read, F: PrimeField>(
mut reader: R, mut reader: R,
header: &Header, header: &Header,
) -> IoResult<Vec<Constraints<E>>> { ) -> IoResult<Vec<Constraints<F>>> {
// todo check section size // todo check section size
let mut vec = Vec::with_capacity(header.n_constraints as usize); let mut vec = Vec::with_capacity(header.n_constraints as usize);
for _ in 0..header.n_constraints { for _ in 0..header.n_constraints {
vec.push(( vec.push((
read_constraint_vec::<&mut R, E>(&mut reader)?, read_constraint_vec::<&mut R, F>(&mut reader)?,
read_constraint_vec::<&mut R, E>(&mut reader)?, read_constraint_vec::<&mut R, F>(&mut reader)?,
read_constraint_vec::<&mut R, E>(&mut reader)?, read_constraint_vec::<&mut R, F>(&mut reader)?,
)); ));
} }
Ok(vec) Ok(vec)
@@ -251,7 +252,7 @@ fn read_map<R: Read>(mut reader: R, size: u64, header: &Header) -> IoResult<Vec<
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use ark_bn254::{Bn254, Fr}; use ark_bn254::Fr;
use ark_std::io::{BufReader, Cursor}; use ark_std::io::{BufReader, Cursor};
#[test] #[test]
@@ -309,7 +310,7 @@ mod tests {
); );
let reader = BufReader::new(Cursor::new(&data[..])); let reader = BufReader::new(Cursor::new(&data[..]));
let file = R1CSFile::<Bn254>::new(reader).unwrap(); let file = R1CSFile::<Fr>::new(reader).unwrap();
assert_eq!(file.version, 1); assert_eq!(file.version, 1);
assert_eq!(file.header.field_size, 32); assert_eq!(file.header.field_size, 32);

View File

@@ -1,4 +1,5 @@
use super::{fnv, CircomBase, SafeMemory, Wasm}; use super::{fnv, CircomBase, SafeMemory, Wasm};
use ark_ff::PrimeField;
use color_eyre::Result; use color_eyre::Result;
use num_bigint::BigInt; use num_bigint::BigInt;
use num_traits::Zero; use num_traits::Zero;
@@ -255,16 +256,15 @@ impl WitnessCalculator {
} }
pub fn calculate_witness_element< pub fn calculate_witness_element<
E: ark_ec::pairing::Pairing, F: PrimeField,
I: IntoIterator<Item = (String, Vec<BigInt>)>, I: IntoIterator<Item = (String, Vec<BigInt>)>,
>( >(
&mut self, &mut self,
inputs: I, inputs: I,
sanity_check: bool, sanity_check: bool,
) -> Result<Vec<E::ScalarField>> { ) -> Result<Vec<F>> {
use ark_ff::PrimeField;
let witness = self.calculate_witness(inputs, sanity_check)?; let witness = self.calculate_witness(inputs, sanity_check)?;
let modulus = <E::ScalarField as PrimeField>::MODULUS; let modulus = <F>::MODULUS;
// convert it to field elements // convert it to field elements
use num_traits::Signed; use num_traits::Signed;
@@ -277,7 +277,7 @@ impl WitnessCalculator {
} else { } else {
w.to_biguint().unwrap() w.to_biguint().unwrap()
}; };
E::ScalarField::from(w) F::from(w)
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();

View File

@@ -848,7 +848,7 @@ mod tests {
let mut file = File::open(path).unwrap(); let mut file = File::open(path).unwrap();
let (params, _matrices) = read_zkey(&mut file).unwrap(); // binfile.proving_key().unwrap(); let (params, _matrices) = read_zkey(&mut file).unwrap(); // binfile.proving_key().unwrap();
let cfg = CircomConfig::<Bn254>::new( let cfg = CircomConfig::<Fr>::new(
"./test-vectors/mycircuit.wasm", "./test-vectors/mycircuit.wasm",
"./test-vectors/mycircuit.r1cs", "./test-vectors/mycircuit.r1cs",
) )
@@ -895,7 +895,7 @@ mod tests {
let s = ark_bn254::Fr::rand(rng); let s = ark_bn254::Fr::rand(rng);
let full_assignment = wtns let full_assignment = wtns
.calculate_witness_element::<Bn254, _>(inputs, false) .calculate_witness_element::<Fr, _>(inputs, false)
.unwrap(); .unwrap();
let proof = Groth16::<Bn254, CircomReduction>::create_proof_with_reduction_and_matrices( let proof = Groth16::<Bn254, CircomReduction>::create_proof_with_reduction_and_matrices(
&params, &params,

View File

@@ -2,7 +2,7 @@ use ark_circom::{CircomBuilder, CircomConfig};
use ark_std::rand::thread_rng; use ark_std::rand::thread_rng;
use color_eyre::Result; use color_eyre::Result;
use ark_bn254::Bn254; use ark_bn254::{Bn254, Fr};
use ark_crypto_primitives::snark::SNARK; use ark_crypto_primitives::snark::SNARK;
use ark_groth16::Groth16; use ark_groth16::Groth16;
@@ -10,7 +10,7 @@ type GrothBn = Groth16<Bn254>;
#[test] #[test]
fn groth16_proof() -> Result<()> { fn groth16_proof() -> Result<()> {
let cfg = CircomConfig::<Bn254>::new( let cfg = CircomConfig::<Fr>::new(
"./test-vectors/mycircuit.wasm", "./test-vectors/mycircuit.wasm",
"./test-vectors/mycircuit.r1cs", "./test-vectors/mycircuit.r1cs",
)?; )?;
@@ -41,7 +41,7 @@ fn groth16_proof() -> Result<()> {
#[test] #[test]
fn groth16_proof_wrong_input() { fn groth16_proof_wrong_input() {
let cfg = CircomConfig::<Bn254>::new( let cfg = CircomConfig::<Fr>::new(
"./test-vectors/mycircuit.wasm", "./test-vectors/mycircuit.wasm",
"./test-vectors/mycircuit.r1cs", "./test-vectors/mycircuit.r1cs",
) )
@@ -63,7 +63,7 @@ fn groth16_proof_wrong_input() {
#[test] #[test]
#[cfg(feature = "circom-2")] #[cfg(feature = "circom-2")]
fn groth16_proof_circom2() -> Result<()> { fn groth16_proof_circom2() -> Result<()> {
let cfg = CircomConfig::<Bn254>::new( let cfg = CircomConfig::<Fr>::new(
"./test-vectors/circom2_multiplier2.wasm", "./test-vectors/circom2_multiplier2.wasm",
"./test-vectors/circom2_multiplier2.r1cs", "./test-vectors/circom2_multiplier2.r1cs",
)?; )?;
@@ -95,7 +95,7 @@ fn groth16_proof_circom2() -> Result<()> {
#[test] #[test]
#[cfg(feature = "circom-2")] #[cfg(feature = "circom-2")]
fn witness_generation_circom2() -> Result<()> { fn witness_generation_circom2() -> Result<()> {
let cfg = CircomConfig::<Bn254>::new( let cfg = CircomConfig::<Fr>::new(
"./test-vectors/circom2_multiplier2.wasm", "./test-vectors/circom2_multiplier2.wasm",
"./test-vectors/circom2_multiplier2.r1cs", "./test-vectors/circom2_multiplier2.r1cs",
)?; )?;

View File

@@ -2,7 +2,7 @@ use ark_circom::{ethereum, CircomBuilder, CircomConfig};
use ark_std::rand::thread_rng; use ark_std::rand::thread_rng;
use color_eyre::Result; use color_eyre::Result;
use ark_bn254::Bn254; use ark_bn254::{Bn254, Fr};
use ark_crypto_primitives::snark::SNARK; use ark_crypto_primitives::snark::SNARK;
use ark_groth16::Groth16; use ark_groth16::Groth16;
@@ -16,7 +16,7 @@ use std::{convert::TryFrom, sync::Arc};
#[tokio::test] #[tokio::test]
async fn solidity_verifier() -> Result<()> { async fn solidity_verifier() -> Result<()> {
let cfg = CircomConfig::<Bn254>::new( let cfg = CircomConfig::<Fr>::new(
"./test-vectors/mycircuit.wasm", "./test-vectors/mycircuit.wasm",
"./test-vectors/mycircuit.r1cs", "./test-vectors/mycircuit.r1cs",
)?; )?;