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

refactor(RLN:) Remove dependencies and add new APIs #45

Merged
merged 18 commits into from
Sep 15, 2022
Merged
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
2 changes: 1 addition & 1 deletion .gitmodules
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[submodule "rln/vendor/rln"]
path = rln/vendor/rln
ignore = dirty
url = https://github.com/privacy-scaling-explorations/rln.git
url = https://github.com/Rate-Limiting-Nullifier/rln_circuits
[submodule "semaphore/vendor/semaphore"]
path = semaphore/vendor/semaphore
ignore = dirty
Expand Down
28 changes: 17 additions & 11 deletions rln/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,28 +9,34 @@ crate-type = ["cdylib", "rlib", "staticlib"]
[dependencies]

# ZKP Generation
ark-ec = { version = "0.3.0", default-features = false, features = ["parallel"] }
ark-ff = { version = "0.3.0", default-features = false, features = ["parallel", "asm"] }
ark-std = { version = "0.3.0", default-features = false, features = ["parallel"] }
ark-bn254 = { version = "0.3.0" }
ark-groth16 = { git = "https://github.com/arkworks-rs/groth16", rev = "765817f", features = ["parallel"] }
ark-relations = { version = "0.3.0", default-features = false, features = [ "std" ] }
ark-serialize = { version = "0.3.0", default-features = false }
ark-circom = { git = "https://github.com/gakonst/ark-circom", features = ["circom-2"] }
wasmer = { version = "2.0" }
ark-circom = { git = "https://github.com/gakonst/ark-circom", rev = "06eb075", features = ["circom-2"] }
#ark-circom = { git = "https://github.com/vacp2p/ark-circom", branch = "no-ethers-core", features = ["circom-2"] }
Comment on lines +18 to +19
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@oskarth The vacp2p fork is a subset of the original ark-circom, which is lighter in terms of dependencies while guaranteeing zerokit the functionalities it needs. I was unsure which dep activate by default (I have pros and cons for both), so I kept the original one. But feel free to change it (the difference is ~60 dependencies less compiled)

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah original one is better unless our fork is a must. We should upstream change we make, or at least start discussion.

I feel like there should be some form of dead code compilation that can be done here? So it doesn't matter how many dependencies are in ark-circom for zerokit binary size.

Copy link
Contributor Author

@s1fr0 s1fr0 Sep 15, 2022

Choose a reason for hiding this comment

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

You can get a warning for unused functions/code (dead_code). To detect unused dependencies you need special tools like udeps, so I suspect the compiler doesn't figure it out automatically which one are unused and skips their compilation. But I might be wrong, I tried to search something like what you said but with no luck.

Copy link
Contributor

Choose a reason for hiding this comment

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

What matters here is the final compilation size, not if it is part of compilation process (CI isn't that slow). I could be wrong, but I think this gets remove with e.g. https://doc.rust-lang.org/rustc/codegen-options/index.html#link-dead-code

For wasm there are tools like https://rustwasm.github.io/docs/book/reference/code-size.html#the-twiggy-code-size-profiler

Can continue this discussion in relevant issue though

Copy link
Contributor

Choose a reason for hiding this comment

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

I think we might want ethers in future anyway, so we can deal with ZK smart contracts etc from Zerokit too

wasmer = "2.3.0"

# error handling
color-eyre = "0.5"
color-eyre = "0.5.11"
thiserror = "1.0.0"

# utilities
hex-literal = "0.3"
num-bigint = { version = "0.4", default-features = false, features = ["rand"] }
once_cell = "1.8"
cfg-if = "1.0"
num-bigint = { version = "0.4.3", default-features = false, features = ["rand"] }
num-traits = "0.2.11"
once_cell = "1.14.0"
rand = "0.8"
tiny-keccak = "2.0.2"
num-traits = "0.2.15"
tiny-keccak = { version = "2.0.2", features = ["keccak"] }

# serialization
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
serde_json = "1.0.48"
serde_json = "1.0.48"

[dev-dependencies]

hex-literal = "0.3.4"

[features]
fullmerkletree = []
114 changes: 75 additions & 39 deletions rln/src/circuit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use num_bigint::BigUint;
use once_cell::sync::OnceCell;
use serde_json::Value;
use std::fs::File;
use std::io::{Error, ErrorKind, Read, Result};
use std::io::{Cursor, Error, ErrorKind, Result};
use std::path::Path;
use std::str::FromStr;
use std::sync::Mutex;
Expand All @@ -22,18 +22,18 @@ const VK_FILENAME: &str = "verifying_key.json";
const WASM_FILENAME: &str = "rln.wasm";

// These parameters are used for tests
// Note that the circuit and keys in TEST_RESOURCES_FOLDER are compiled for Merkle trees of height 15 and 19
// Changing these parameters to other values than these two defaults will cause zkSNARK proof verification to fail
//pub const TEST_TREE_HEIGHT: usize = 15;
//pub const TEST_RESOURCES_FOLDER: &str = "./resources/tree_height_15/";
//pub const TEST_TREE_HEIGHT: usize = 19;
//pub const TEST_RESOURCES_FOLDER: &str = "./resources/tree_height_19/";
pub const TEST_TREE_HEIGHT: usize = 20;
pub const TEST_RESOURCES_FOLDER: &str = "./resources/tree_height_20/";
// Note that the circuit and keys in TEST_RESOURCES_FOLDER are compiled for Merkle trees of height 15, 19 and 20
// Changing these parameters to other values than these defaults will cause zkSNARK proof verification to fail
pub const TEST_PARAMETERS_INDEX: usize = 2;
pub const TEST_TREE_HEIGHT: usize = [15, 19, 20][TEST_PARAMETERS_INDEX];
pub const TEST_RESOURCES_FOLDER: &str = [
"./resources/tree_height_15/",
"./resources/tree_height_19/",
"./resources/tree_height_20/",
][TEST_PARAMETERS_INDEX];

// The following types define the pairing friendly elliptic curve, the underlying finite fields and groups default to this module
// Note that proofs are serialized assuming Fr to be 4x8 = 32 bytes in size. Hence, changing to a curve with different encoding will make proof verification to fail

pub type Curve = Bn254;
pub type Fr = ArkFr;
pub type Fq = ArkFq;
Expand All @@ -43,9 +43,21 @@ pub type G1Projective = ArkG1Projective;
pub type G2Affine = ArkG2Affine;
pub type G2Projective = ArkG2Projective;

#[allow(non_snake_case)]
// Loads the proving key using a bytes vector
pub fn zkey_from_raw(zkey_data: &Vec<u8>) -> Result<(ProvingKey<Curve>, ConstraintMatrices<Fr>)> {
if !zkey_data.is_empty() {
let mut c = Cursor::new(zkey_data);
let proving_key_and_matrices = read_zkey(&mut c)?;
Ok(proving_key_and_matrices)
} else {
Err(Error::new(ErrorKind::NotFound, "No proving key found!"))
}
}

// Loads the proving key
pub fn ZKEY(resources_folder: &str) -> Result<(ProvingKey<Curve>, ConstraintMatrices<Fr>)> {
pub fn zkey_from_folder(
resources_folder: &str,
) -> Result<(ProvingKey<Curve>, ConstraintMatrices<Fr>)> {
let zkey_path = format!("{resources_folder}{ZKEY_FILENAME}");
if Path::new(&zkey_path).exists() {
let mut file = File::open(&zkey_path)?;
Expand All @@ -56,9 +68,27 @@ pub fn ZKEY(resources_folder: &str) -> Result<(ProvingKey<Curve>, ConstraintMatr
}
}

#[allow(non_snake_case)]
// Loads the verification key from a bytes vector
pub fn vk_from_raw(vk_data: &Vec<u8>, zkey_data: &Vec<u8>) -> Result<VerifyingKey<Curve>> {
let verifying_key: VerifyingKey<Curve>;

if !vk_data.is_empty() {
verifying_key = vk_from_vector(vk_data);
Ok(verifying_key)
} else if !zkey_data.is_empty() {
let (proving_key, _matrices) = zkey_from_raw(zkey_data)?;
verifying_key = proving_key.vk;
Ok(verifying_key)
} else {
Err(Error::new(
ErrorKind::NotFound,
"No proving/verification key found!",
))
}
}

// Loads the verification key
pub fn VK(resources_folder: &str) -> Result<VerifyingKey<Curve>> {
pub fn vk_from_folder(resources_folder: &str) -> Result<VerifyingKey<Curve>> {
let vk_path = format!("{resources_folder}{VK_FILENAME}");
let zkey_path = format!("{resources_folder}{ZKEY_FILENAME}");

Expand All @@ -68,7 +98,7 @@ pub fn VK(resources_folder: &str) -> Result<VerifyingKey<Curve>> {
verifying_key = vk_from_json(&vk_path);
Ok(verifying_key)
} else if Path::new(&zkey_path).exists() {
let (proving_key, _matrices) = ZKEY(resources_folder)?;
let (proving_key, _matrices) = zkey_from_folder(resources_folder)?;
verifying_key = proving_key.vk;
Ok(verifying_key)
} else {
Expand All @@ -81,32 +111,25 @@ pub fn VK(resources_folder: &str) -> Result<VerifyingKey<Curve>> {

static WITNESS_CALCULATOR: OnceCell<Mutex<WitnessCalculator>> = OnceCell::new();

// Loads the circuit WASM
fn read_wasm(resources_folder: &str) -> Vec<u8> {
let wasm_path = format!("{resources_folder}{WASM_FILENAME}");
let mut wasm_file = File::open(&wasm_path).expect("no file found");
let metadata = std::fs::metadata(&wasm_path).expect("unable to read metadata");
let mut wasm_buffer = vec![0; metadata.len() as usize];
wasm_file
.read_exact(&mut wasm_buffer)
.expect("buffer overflow");
wasm_buffer
}

#[allow(non_snake_case)]
// Initializes the witness calculator
pub fn CIRCOM(resources_folder: &str) -> &'static Mutex<WitnessCalculator> {
// Initializes the witness calculator using a bytes vector
pub fn circom_from_raw(wasm_buffer: Vec<u8>) -> &'static Mutex<WitnessCalculator> {
WITNESS_CALCULATOR.get_or_init(|| {
// We read the wasm file
let wasm_buffer = read_wasm(resources_folder);
let store = Store::default();
let module = Module::from_binary(&store, &wasm_buffer).expect("wasm should be valid");
let module = Module::new(&store, wasm_buffer).unwrap();
let result =
WitnessCalculator::from_module(module).expect("Failed to create witness calculator");
Mutex::new(result)
})
}

// Initializes the witness calculator
pub fn circom_from_folder(resources_folder: &str) -> &'static Mutex<WitnessCalculator> {
// We read the wasm file
let wasm_path = format!("{resources_folder}{WASM_FILENAME}");
let wasm_buffer = std::fs::read(&wasm_path).unwrap();
circom_from_raw(wasm_buffer)
}

// The following function implementations are taken/adapted from https://github.com/gakonst/ark-circom/blob/1732e15d6313fe176b0b1abb858ac9e095d0dbd7/src/zkey.rs

// Utilities to convert a json verification key in a groth16::VerificationKey
Expand Down Expand Up @@ -182,11 +205,8 @@ fn json_to_g2(json: &Value, key: &str) -> G2Affine {
G2Affine::from(G2Projective::new(x, y, z))
}

// Computes the verification key from its JSON serialization
fn vk_from_json(vk_path: &str) -> VerifyingKey<Curve> {
let json = std::fs::read_to_string(vk_path).unwrap();
let json: Value = serde_json::from_str(&json).unwrap();

// Converts JSON to a VerifyingKey
fn to_verifying_key(json: serde_json::Value) -> VerifyingKey<Curve> {
VerifyingKey {
alpha_g1: json_to_g1(&json, "vk_alpha_1"),
beta_g2: json_to_g2(&json, "vk_beta_2"),
Expand All @@ -196,8 +216,24 @@ fn vk_from_json(vk_path: &str) -> VerifyingKey<Curve> {
}
}

// Computes the verification key from its JSON serialization
fn vk_from_json(vk_path: &str) -> VerifyingKey<Curve> {
let json = std::fs::read_to_string(vk_path).unwrap();
let json: Value = serde_json::from_str(&json).unwrap();

to_verifying_key(json)
}

// Computes the verification key from a bytes vector containing its JSON serialization
fn vk_from_vector(vk: &[u8]) -> VerifyingKey<Curve> {
let json = String::from_utf8(vk.to_vec()).expect("Found invalid UTF-8");
let json: Value = serde_json::from_str(&json).unwrap();

to_verifying_key(json)
}

// Checks verification key to be correct with respect to proving key
pub fn check_vk_from_zkey(resources_folder: &str, verifying_key: VerifyingKey<Curve>) {
let (proving_key, _matrices) = ZKEY(resources_folder).unwrap();
let (proving_key, _matrices) = zkey_from_folder(resources_folder).unwrap();
assert_eq!(proving_key.vk, verifying_key);
}
91 changes: 91 additions & 0 deletions rln/src/ffi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,23 @@ pub extern "C" fn new(tree_height: usize, input_buffer: *const Buffer, ctx: *mut
true
}

#[allow(clippy::not_unsafe_ptr_arg_deref)]
#[no_mangle]
pub extern "C" fn new_with_params(
tree_height: usize,
circom_buffer: *const Buffer,
zkey_buffer: *const Buffer,
vk_buffer: *const Buffer,
ctx: *mut *mut RLN,
) -> bool {
let circom_data = <&[u8]>::from(unsafe { &*circom_buffer });
let zkey_data = <&[u8]>::from(unsafe { &*zkey_buffer });
let vk_data = <&[u8]>::from(unsafe { &*vk_buffer });
let rln = RLN::new_with_params(tree_height, circom_data, zkey_data, vk_data);
unsafe { *ctx = Box::into_raw(Box::new(rln)) };
true
}

////////////////////////////////////////////////////////
// Merkle tree APIs
////////////////////////////////////////////////////////
Expand Down Expand Up @@ -251,6 +268,8 @@ mod test {
use crate::utils::*;
use ark_std::{rand::thread_rng, UniformRand};
use rand::Rng;
use std::fs::File;
use std::io::Read;
use std::mem::MaybeUninit;
use std::time::{Duration, Instant};

Expand Down Expand Up @@ -578,6 +597,78 @@ mod test {
);
}

#[test]
// Creating a RLN with raw data should generate same results as using a path to resources
fn test_rln_raw_ffi() {
let tree_height = TEST_TREE_HEIGHT;

// We create a RLN instance using a resource folder path
let mut rln_pointer = MaybeUninit::<*mut RLN>::uninit();
let input_buffer = &Buffer::from(TEST_RESOURCES_FOLDER.as_bytes());
let success = new(tree_height, input_buffer, rln_pointer.as_mut_ptr());
assert!(success, "RLN object creation failed");
let rln_pointer = unsafe { &mut *rln_pointer.assume_init() };

// We obtain the root from the RLN instance
let mut output_buffer = MaybeUninit::<Buffer>::uninit();
let success = get_root(rln_pointer, output_buffer.as_mut_ptr());
assert!(success, "get root call failed");
let output_buffer = unsafe { output_buffer.assume_init() };
let result_data = <&[u8]>::from(&output_buffer).to_vec();
let (root_rln_folder, _) = bytes_le_to_fr(&result_data);

// Reading the raw data from the files required for instantiating a RLN instance using raw data
let circom_path = format!("./resources/tree_height_{TEST_TREE_HEIGHT}/rln.wasm");
let mut circom_file = File::open(&circom_path).expect("no file found");
let metadata = std::fs::metadata(&circom_path).expect("unable to read metadata");
let mut circom_buffer = vec![0; metadata.len() as usize];
circom_file
.read_exact(&mut circom_buffer)
.expect("buffer overflow");

let zkey_path = format!("./resources/tree_height_{TEST_TREE_HEIGHT}/rln_final.zkey");
let mut zkey_file = File::open(&zkey_path).expect("no file found");
let metadata = std::fs::metadata(&zkey_path).expect("unable to read metadata");
let mut zkey_buffer = vec![0; metadata.len() as usize];
zkey_file
.read_exact(&mut zkey_buffer)
.expect("buffer overflow");

let vk_path = format!("./resources/tree_height_{TEST_TREE_HEIGHT}/verification_key.json");

let mut vk_file = File::open(&vk_path).expect("no file found");
let metadata = std::fs::metadata(&vk_path).expect("unable to read metadata");
let mut vk_buffer = vec![0; metadata.len() as usize];
vk_file.read_exact(&mut vk_buffer).expect("buffer overflow");

let circom_data = &Buffer::from(&circom_buffer[..]);
let zkey_data = &Buffer::from(&zkey_buffer[..]);
let vk_data = &Buffer::from(&vk_buffer[..]);

// Creating a RLN instance passing the raw data
let mut rln_pointer_raw_bytes = MaybeUninit::<*mut RLN>::uninit();
let success = new_with_params(
tree_height,
circom_data,
zkey_data,
vk_data,
rln_pointer_raw_bytes.as_mut_ptr(),
);
assert!(success, "RLN object creation failed");
let rln_pointer2 = unsafe { &mut *rln_pointer_raw_bytes.assume_init() };

// We obtain the root from the RLN instance containing raw data
let mut output_buffer = MaybeUninit::<Buffer>::uninit();
let success = get_root(rln_pointer2, output_buffer.as_mut_ptr());
assert!(success, "get root call failed");
let output_buffer = unsafe { output_buffer.assume_init() };
let result_data = <&[u8]>::from(&output_buffer).to_vec();
let (root_rln_raw, _) = bytes_le_to_fr(&result_data);

// And compare that the same root was generated
assert_eq!(root_rln_folder, root_rln_raw);
}

#[test]
// Computes and verifies an RLN ZK proof using FFI APIs
fn test_rln_proof_ffi() {
Expand Down
17 changes: 10 additions & 7 deletions rln/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@ pub mod utils;
#[cfg(test)]
mod test {

use crate::circuit::{Fr, CIRCOM, TEST_RESOURCES_FOLDER, TEST_TREE_HEIGHT, VK, ZKEY};
use crate::circuit::{
circom_from_folder, vk_from_folder, zkey_from_folder, Fr, TEST_RESOURCES_FOLDER,
TEST_TREE_HEIGHT,
};
use crate::poseidon_hash::poseidon_hash;
use crate::poseidon_tree::PoseidonTree;
use crate::protocol::*;
Expand Down Expand Up @@ -327,9 +330,9 @@ mod test {
// We test a RLN proof generation and verification
fn test_witness_from_json() {
// We generate all relevant keys
let proving_key = ZKEY(TEST_RESOURCES_FOLDER).unwrap();
let verification_key = VK(TEST_RESOURCES_FOLDER).unwrap();
let builder = CIRCOM(TEST_RESOURCES_FOLDER);
let proving_key = zkey_from_folder(TEST_RESOURCES_FOLDER).unwrap();
let verification_key = vk_from_folder(TEST_RESOURCES_FOLDER).unwrap();
let builder = circom_from_folder(TEST_RESOURCES_FOLDER);

// We compute witness from the json input example
let mut witness_json: &str = "";
Expand Down Expand Up @@ -386,9 +389,9 @@ mod test {
);

// We generate all relevant keys
let proving_key = ZKEY(TEST_RESOURCES_FOLDER).unwrap();
let verification_key = VK(TEST_RESOURCES_FOLDER).unwrap();
let builder = CIRCOM(TEST_RESOURCES_FOLDER);
let proving_key = zkey_from_folder(TEST_RESOURCES_FOLDER).unwrap();
let verification_key = vk_from_folder(TEST_RESOURCES_FOLDER).unwrap();
let builder = circom_from_folder(TEST_RESOURCES_FOLDER);

// Let's generate a zkSNARK proof
let proof = generate_proof(builder, &proving_key, &rln_witness).unwrap();
Expand Down
Loading