Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Initial Circom 2 support #10

Merged
merged 21 commits into from
Nov 29, 2021
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,4 @@ harness = false

[features]
bench-complex-all = []
circom-2 = []
1 change: 1 addition & 0 deletions src/circom/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ impl<E: PairingEngine> CircomConfig<E> {
pub fn new(wtns: impl AsRef<Path>, r1cs: impl AsRef<Path>) -> Result<Self> {
let wtns = WitnessCalculator::new(wtns).unwrap();
let reader = File::open(r1cs)?;
println!("CircomConfig new, about to read new R1CS file");
oskarth marked this conversation as resolved.
Show resolved Hide resolved
let r1cs = R1CSFile::new(reader)?.into();
Ok(Self {
wtns,
Expand Down
3 changes: 3 additions & 0 deletions src/circom/r1cs_reader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ impl<E: PairingEngine> R1CSFile<E> {
let _sec_type = reader.read_u32::<LittleEndian>()?;
let sec_size = reader.read_u64::<LittleEndian>()?;

println!("R1CSFile new, about to create new header");

let header = Header::new(&mut reader, sec_size)?;
let _sec_type = reader.read_u32::<LittleEndian>()?;
let _sec_size = reader.read_u64::<LittleEndian>()?;
Expand Down Expand Up @@ -93,6 +95,7 @@ pub struct Header {
impl Header {
fn new<R: Read>(mut reader: R, size: u64) -> Result<Header> {
let field_size = reader.read_u32::<LittleEndian>()?;
println!("r1cs_reader header new field_size {}", field_size);
if field_size != 32 {
return Err(Error::new(
ErrorKind::InvalidData,
Expand Down
27 changes: 27 additions & 0 deletions src/witness/circom.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,34 @@ impl Wasm {
Ok(())
}


// Circom 2.0
pub fn get_version(&self) -> Result<i32> {
self.get_i32("getVersion")
}

// Circom 1
pub fn get_fr_len(&self) -> Result<i32> {
self.get_i32("getFrLen")
}

// Circom 2.0
pub fn get_field_num_len32(&self) -> Result<i32> {
self.get_i32("getFieldNumLen32")
}
Copy link
Collaborator

@gakonst gakonst Nov 24, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could perhaps create a struct CircomBase (for any shared functions), struct Circom (for 1.0) and struct Circom2 (for 2.0) to better separate 1.0 vs 2.0 functions, and instantiate the struct Wasm with one or the other based on the feature?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried a few different things here, with enums/generics etc. The current trait version is the one that that seemed somewhat sensible and I managed to get to compile 😅 Let me know what you think.


// Circom 1
pub fn get_ptr_raw_prime(&self) -> Result<i32> {
self.get_i32("getPRawPrime")
}

// Circom 2.0
pub fn get_raw_prime(&self) -> Result<()> {
let func = self.func("getRawPrime");
let _result = func.call(&[])?;
Ok(())
}

pub fn get_n_vars(&self) -> Result<i32> {
self.get_i32("getNVars")
}
Expand All @@ -38,6 +58,13 @@ impl Wasm {
Ok(res[0].unwrap_i32())
}

// Circom 2.0
pub fn read_shared_rw_memory(&self, i: i32) -> Result<i32> {
let func = self.func("readSharedRWMemory");
let result = func.call(&[i.into()])?;
Ok(result[0].unwrap_i32())
}

pub fn get_signal_offset32(
&self,
p_sig_offset: u32,
Expand Down
72 changes: 69 additions & 3 deletions src/witness/witness_calculator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,16 @@ pub struct WitnessCalculator {
#[error("{0}")]
struct ExitCode(u32);

// Based on https://github.com/oskarth/hello-circom/blob/master/multiplier2_js/witness_calculator.js#L254-L261
fn from_array32(arr: Vec<i32>) -> BigInt {
let mut res = BigInt::zero();
let radix = BigInt::from(0x100000000u64);
for i in 0..arr.len() {
res = res * &radix + BigInt::from(arr[i]);
}
return res;
}

impl WitnessCalculator {
pub fn new(path: impl AsRef<std::path::Path>) -> Result<Self> {
let store = Store::default();
Expand All @@ -38,16 +48,56 @@ impl WitnessCalculator {
"logFinishComponent" => runtime::log_component(&store),
"logStartComponent" => runtime::log_component(&store),
"log" => runtime::log_component(&store),
"exceptionHandler" => runtime::exception_handler(&store),
"showSharedRWMemory" => runtime::show_memory(&store),
}
};
let instance = Wasm::new(Instance::new(&module, &import_object)?);

let n32 = (instance.get_fr_len()? >> 2) - 2;
let version;
let n32;

// Circom 1
#[cfg(not(feature = "circom-2"))]
oskarth marked this conversation as resolved.
Show resolved Hide resolved
{
version = 1;
n32 = (instance.get_fr_len()? >> 2) - 2;
}

// Circom 2.0
#[cfg(feature = "circom-2")]
{
version = instance.get_version()?;
n32 = instance.get_field_num_len32()?;
}

println!("Circom version {}, n32 {}", version, n32);

let mut memory = SafeMemory::new(memory, n32 as usize, BigInt::zero());
let prime: BigInt;

// Circom 1
#[cfg(not(feature = "circom-2"))]
{
let ptr = instance.get_ptr_raw_prime()?;
prime = memory.read_big(ptr as usize, n32 as usize)?;
}

// Circom 2.0
#[cfg(feature = "circom-2")]
{
let _res = instance.get_raw_prime()?;
let mut arr = vec![0; n32 as usize];
for i in 0..n32 {
let res = instance.read_shared_rw_memory(i)?;
arr[(n32 as usize) - (i as usize) - 1] = res;
}

prime = from_array32(arr);
}

println!("Circom prime is {}", prime);

let ptr = instance.get_ptr_raw_prime()?;
let prime = memory.read_big(ptr as usize, n32 as usize)?;
let n64 = ((prime.bits() - 1) / 64 + 1) as i32;
memory.prime = prime;

Expand Down Expand Up @@ -162,6 +212,22 @@ mod runtime {
Function::new_native(store, func)
}

// Circom 2.0
pub fn exception_handler(store: &Store) -> Function {
oskarth marked this conversation as resolved.
Show resolved Hide resolved
#[allow(unused)]
fn func(a: i32) {
println!("exception_handler hit, {}", a);
}
Function::new_native(store, func)
}

// Circom 2.0
pub fn show_memory(store: &Store) -> Function {
#[allow(unused)]
fn func() {}
Function::new_native(store, func)
}

pub fn log_signal(store: &Store) -> Function {
#[allow(unused)]
fn func(a: i32, b: i32) {}
Expand Down
Binary file added test-vectors/circom2_multiplier2.r1cs
Binary file not shown.
Binary file added test-vectors/circom2_multiplier2.wasm
Binary file not shown.
32 changes: 32 additions & 0 deletions tests/groth16.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,35 @@ fn groth16_proof_wrong_input() {

builder.build().unwrap_err();
}

#[test]
#[cfg(feature = "circom-2")]
fn groth16_proof_circom2() -> Result<()> {
let cfg = CircomConfig::<Bn254>::new(
"./test-vectors/circom2_multiplier2.wasm",
"./test-vectors/circom2_multiplier2.r1cs",
)?;
let mut builder = CircomBuilder::new(cfg);
builder.push_input("a", 3);
builder.push_input("b", 11);

// create an empty instance for setting it up
let circom = builder.setup();

let mut rng = thread_rng();
let params = generate_random_parameters::<Bn254, _, _>(circom, &mut rng)?;

let circom = builder.build()?;

let inputs = circom.get_public_inputs().unwrap();

let proof = prove(circom, &params, &mut rng)?;

let pvk = prepare_verifying_key(&params.vk);

let verified = verify_proof(&pvk, &proof, &inputs)?;

assert!(verified);

Ok(())
}