diff --git a/.gitmodules b/.gitmodules index 4356047..52b09e2 100644 --- a/.gitmodules +++ b/.gitmodules @@ -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 diff --git a/rln/Cargo.toml b/rln/Cargo.toml index 4e05d09..2c26f50 100644 --- a/rln/Cargo.toml +++ b/rln/Cargo.toml @@ -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"] } +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" \ No newline at end of file +serde_json = "1.0.48" + +[dev-dependencies] + +hex-literal = "0.3.4" + +[features] +fullmerkletree = [] diff --git a/rln/src/circuit.rs b/rln/src/circuit.rs index 301e187..45e85b8 100644 --- a/rln/src/circuit.rs +++ b/rln/src/circuit.rs @@ -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; @@ -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; @@ -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) -> Result<(ProvingKey, ConstraintMatrices)> { + 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, ConstraintMatrices)> { +pub fn zkey_from_folder( + resources_folder: &str, +) -> Result<(ProvingKey, ConstraintMatrices)> { let zkey_path = format!("{resources_folder}{ZKEY_FILENAME}"); if Path::new(&zkey_path).exists() { let mut file = File::open(&zkey_path)?; @@ -56,9 +68,27 @@ pub fn ZKEY(resources_folder: &str) -> Result<(ProvingKey, ConstraintMatr } } -#[allow(non_snake_case)] +// Loads the verification key from a bytes vector +pub fn vk_from_raw(vk_data: &Vec, zkey_data: &Vec) -> Result> { + let verifying_key: VerifyingKey; + + 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> { +pub fn vk_from_folder(resources_folder: &str) -> Result> { let vk_path = format!("{resources_folder}{VK_FILENAME}"); let zkey_path = format!("{resources_folder}{ZKEY_FILENAME}"); @@ -68,7 +98,7 @@ pub fn VK(resources_folder: &str) -> Result> { 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 { @@ -81,32 +111,25 @@ pub fn VK(resources_folder: &str) -> Result> { static WITNESS_CALCULATOR: OnceCell> = OnceCell::new(); -// Loads the circuit WASM -fn read_wasm(resources_folder: &str) -> Vec { - 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 { +// Initializes the witness calculator using a bytes vector +pub fn circom_from_raw(wasm_buffer: Vec) -> &'static Mutex { 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 { + // 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 @@ -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 { - 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 { VerifyingKey { alpha_g1: json_to_g1(&json, "vk_alpha_1"), beta_g2: json_to_g2(&json, "vk_beta_2"), @@ -196,8 +216,24 @@ fn vk_from_json(vk_path: &str) -> VerifyingKey { } } +// Computes the verification key from its JSON serialization +fn vk_from_json(vk_path: &str) -> VerifyingKey { + 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 { + 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) { - 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); } diff --git a/rln/src/ffi.rs b/rln/src/ffi.rs index 70753bb..5d99661 100644 --- a/rln/src/ffi.rs +++ b/rln/src/ffi.rs @@ -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 //////////////////////////////////////////////////////// @@ -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}; @@ -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::::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::::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() { diff --git a/rln/src/lib.rs b/rln/src/lib.rs index ac09898..d2e0d58 100644 --- a/rln/src/lib.rs +++ b/rln/src/lib.rs @@ -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::*; @@ -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 = ""; @@ -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(); diff --git a/rln/src/poseidon_tree.rs b/rln/src/poseidon_tree.rs index aa48010..ab6c839 100644 --- a/rln/src/poseidon_tree.rs +++ b/rln/src/poseidon_tree.rs @@ -5,13 +5,20 @@ use crate::circuit::Fr; use crate::merkle_tree::*; use crate::poseidon_hash::poseidon_hash; +use cfg_if::cfg_if; -// The zerokit RLN default Merkle tree implementation. -// To switch to FullMerkleTree implementation it is enough to redefine the following two types -pub type PoseidonTree = OptimalMerkleTree; -pub type MerkleProof = OptimalMerkleProof; -//pub type PoseidonTree = FullMerkleTree; -//pub type MerkleProof = FullMerkleProof; +// The zerokit RLN default Merkle tree implementation is the OptimalMerkleTree. +// To switch to FullMerkleTree implementation, it is enough to enable the fullmerkletree feature + +cfg_if! { + if #[cfg(feature = "fullmerkletree")] { + pub type PoseidonTree = FullMerkleTree; + pub type MerkleProof = FullMerkleProof; + } else { + pub type PoseidonTree = OptimalMerkleTree; + pub type MerkleProof = OptimalMerkleProof; + } +} // The zerokit RLN default Hasher #[derive(Clone, Copy, PartialEq, Eq)] diff --git a/rln/src/protocol.rs b/rln/src/protocol.rs index 9850265..5bd4fc8 100644 --- a/rln/src/protocol.rs +++ b/rln/src/protocol.rs @@ -12,6 +12,7 @@ use color_eyre::Result; use num_bigint::BigInt; use rand::Rng; use std::sync::Mutex; +#[cfg(debug_assertions)] use std::time::Instant; use thiserror::Error; use tiny_keccak::{Hasher as _, Keccak}; @@ -415,6 +416,8 @@ pub fn generate_proof( .into_iter() .map(|(name, values)| (name.to_string(), values)); + // If in debug mode, we measure and later print time take to compute witness + #[cfg(debug_assertions)] let now = Instant::now(); let full_assignment = witness_calculator @@ -423,6 +426,7 @@ pub fn generate_proof( .calculate_witness_element::(inputs, false) .map_err(ProofError::WitnessError)?; + #[cfg(debug_assertions)] println!("witness generation took: {:.2?}", now.elapsed()); // Random Values @@ -430,7 +434,10 @@ pub fn generate_proof( let r = Fr::rand(&mut rng); let s = Fr::rand(&mut rng); + // If in debug mode, we measure and later print time take to compute proof + #[cfg(debug_assertions)] let now = Instant::now(); + let proof = create_proof_with_reduction_and_matrices::<_, CircomReduction>( &proving_key.0, r, @@ -440,6 +447,8 @@ pub fn generate_proof( proving_key.1.num_constraints, full_assignment.as_slice(), )?; + + #[cfg(debug_assertions)] println!("proof generation took: {:.2?}", now.elapsed()); Ok(proof) @@ -469,8 +478,14 @@ pub fn verify_proof( // Check that the proof is valid let pvk = prepare_verifying_key(verifying_key); //let pr: ArkProof = (*proof).into(); + + // If in debug mode, we measure and later print time take to verify proof + #[cfg(debug_assertions)] let now = Instant::now(); + let verified = ark_verify_proof(&pvk, proof, &inputs)?; + + #[cfg(debug_assertions)] println!("verify took: {:.2?}", now.elapsed()); Ok(verified) diff --git a/rln/src/public.rs b/rln/src/public.rs index 561eddf..c972350 100644 --- a/rln/src/public.rs +++ b/rln/src/public.rs @@ -4,13 +4,15 @@ use ark_circom::WitnessCalculator; use ark_groth16::Proof as ArkProof; use ark_groth16::{ProvingKey, VerifyingKey}; use ark_relations::r1cs::ConstraintMatrices; -use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, Read, Write}; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; use std::default::Default; -use std::io::Cursor; -use std::io::{self, Result}; +use std::io::{self, Cursor, Read, Result, Write}; use std::sync::Mutex; -use crate::circuit::{Curve, Fr, CIRCOM, TEST_RESOURCES_FOLDER, TEST_TREE_HEIGHT, VK, ZKEY}; +use crate::circuit::{ + circom_from_folder, circom_from_raw, vk_from_folder, vk_from_raw, zkey_from_folder, + zkey_from_raw, Curve, Fr, TEST_RESOURCES_FOLDER, TEST_TREE_HEIGHT, +}; use crate::poseidon_tree::PoseidonTree; use crate::protocol::*; use crate::utils::*; @@ -25,7 +27,6 @@ pub struct RLN<'a> { proving_key: Result<(ProvingKey, ConstraintMatrices)>, verification_key: Result>, tree: PoseidonTree, - resources_folder: String, } impl RLN<'_> { @@ -36,10 +37,40 @@ impl RLN<'_> { let resources_folder = String::from_utf8(input).expect("Found invalid UTF-8"); - let witness_calculator = CIRCOM(&resources_folder); + let witness_calculator = circom_from_folder(&resources_folder); - let proving_key = ZKEY(&resources_folder); - let verification_key = VK(&resources_folder); + let proving_key = zkey_from_folder(&resources_folder); + let verification_key = vk_from_folder(&resources_folder); + + // We compute a default empty tree + let tree = PoseidonTree::default(tree_height); + + RLN { + witness_calculator, + proving_key, + verification_key, + tree, + } + } + + pub fn new_with_params( + tree_height: usize, + mut circom_data: R, + mut zkey_data: R, + mut vk_data: R, + ) -> RLN<'static> { + // We read input + let mut circom_vec: Vec = Vec::new(); + circom_data.read_to_end(&mut circom_vec).unwrap(); + let mut zkey_vec: Vec = Vec::new(); + zkey_data.read_to_end(&mut zkey_vec).unwrap(); + let mut vk_vec: Vec = Vec::new(); + vk_data.read_to_end(&mut vk_vec).unwrap(); + + let witness_calculator = circom_from_raw(circom_vec); + + let proving_key = zkey_from_raw(&zkey_vec); + let verification_key = vk_from_raw(&vk_vec, &zkey_vec); // We compute a default empty tree let tree = PoseidonTree::default(tree_height); @@ -49,7 +80,6 @@ impl RLN<'_> { proving_key, verification_key, tree, - resources_folder, } }