Stateless Feature (#265)

* add stateless feature and tests

* update docs and new function
This commit is contained in:
Ekaterina Broslavskaya 2024-08-28 13:41:18 +03:00 committed by GitHub
parent c4579e1917
commit 0d5642492a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 1705 additions and 747 deletions

View File

@ -8,7 +8,7 @@ jobs:
linux:
strategy:
matrix:
feature: ["default", "arkzkey"]
feature: ["default", "arkzkey", "stateless"]
target:
- x86_64-unknown-linux-gnu
- aarch64-unknown-linux-gnu
@ -47,7 +47,7 @@ jobs:
runs-on: macos-latest
strategy:
matrix:
feature: ["default", "arkzkey"]
feature: ["default", "arkzkey", "stateless"]
target:
- x86_64-apple-darwin
- aarch64-apple-darwin

16
Cargo.lock generated
View File

@ -993,6 +993,15 @@ dependencies = [
"subtle",
]
[[package]]
name = "document-features"
version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb6969eaabd2421f8a2775cfd2471a2b634372b4a25d41e3bd647b79912850a0"
dependencies = [
"litrs",
]
[[package]]
name = "ecdsa"
version = "0.16.7"
@ -1611,6 +1620,12 @@ version = "0.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f"
[[package]]
name = "litrs"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5"
[[package]]
name = "lock_api"
version = "0.4.9"
@ -2281,6 +2296,7 @@ dependencies = [
"cfg-if",
"color-eyre",
"criterion 0.4.0",
"document-features",
"lazy_static 1.4.0",
"num-bigint",
"num-traits",

View File

@ -59,6 +59,8 @@ utils = { package = "zerokit_utils", version = "=0.5.1", path = "../utils/", def
serde_json = "=1.0.96"
serde = { version = "=1.0.163", features = ["derive"] }
document-features = { version = "=0.2.10", optional = true }
[dev-dependencies]
sled = "=0.34.7"
criterion = { version = "=0.4.0", features = ["html_reports"] }
@ -75,6 +77,7 @@ parallel = [
wasm = ["wasmer/js", "wasmer/std"]
fullmerkletree = ["default"]
arkzkey = ["ark-zkey"]
stateless = []
# Note: pmtree feature is still experimental
pmtree-ft = ["utils/pmtree-ft"]
@ -94,3 +97,6 @@ harness = false
[[bench]]
name = "poseidon_tree_benchmark"
harness = false
[package.metadata.docs.rs]
all-features = true

View File

@ -192,6 +192,7 @@ impl<'a> From<&Buffer> for &'a [u8] {
////////////////////////////////////////////////////////
#[allow(clippy::not_unsafe_ptr_arg_deref)]
#[cfg(not(feature = "stateless"))]
#[no_mangle]
pub extern "C" fn new(tree_height: usize, input_buffer: *const Buffer, ctx: *mut *mut RLN) -> bool {
match RLN::new(tree_height, input_buffer.process()) {
@ -207,6 +208,23 @@ pub extern "C" fn new(tree_height: usize, input_buffer: *const Buffer, ctx: *mut
}
#[allow(clippy::not_unsafe_ptr_arg_deref)]
#[cfg(feature = "stateless")]
#[no_mangle]
pub extern "C" fn new(ctx: *mut *mut RLN) -> bool {
match RLN::new() {
Ok(rln) => {
unsafe { *ctx = Box::into_raw(Box::new(rln)) };
true
}
Err(err) => {
eprintln!("could not instantiate rln: {err}");
false
}
}
}
#[allow(clippy::not_unsafe_ptr_arg_deref)]
#[cfg(not(feature = "stateless"))]
#[no_mangle]
pub extern "C" fn new_with_params(
tree_height: usize,
@ -234,47 +252,79 @@ pub extern "C" fn new_with_params(
}
}
#[allow(clippy::not_unsafe_ptr_arg_deref)]
#[cfg(feature = "stateless")]
#[no_mangle]
pub extern "C" fn new_with_params(
circom_buffer: *const Buffer,
zkey_buffer: *const Buffer,
vk_buffer: *const Buffer,
ctx: *mut *mut RLN,
) -> bool {
match RLN::new_with_params(
circom_buffer.process().to_vec(),
zkey_buffer.process().to_vec(),
vk_buffer.process().to_vec(),
) {
Ok(rln) => {
unsafe { *ctx = Box::into_raw(Box::new(rln)) };
true
}
Err(err) => {
eprintln!("could not instantiate rln: {err}");
false
}
}
}
////////////////////////////////////////////////////////
// Merkle tree APIs
////////////////////////////////////////////////////////
#[allow(clippy::not_unsafe_ptr_arg_deref)]
#[no_mangle]
#[cfg(not(feature = "stateless"))]
pub extern "C" fn set_tree(ctx: *mut RLN, tree_height: usize) -> bool {
call!(ctx, set_tree, tree_height)
}
#[allow(clippy::not_unsafe_ptr_arg_deref)]
#[no_mangle]
#[cfg(not(feature = "stateless"))]
pub extern "C" fn delete_leaf(ctx: *mut RLN, index: usize) -> bool {
call!(ctx, delete_leaf, index)
}
#[allow(clippy::not_unsafe_ptr_arg_deref)]
#[no_mangle]
#[cfg(not(feature = "stateless"))]
pub extern "C" fn set_leaf(ctx: *mut RLN, index: usize, input_buffer: *const Buffer) -> bool {
call!(ctx, set_leaf, index, input_buffer)
}
#[allow(clippy::not_unsafe_ptr_arg_deref)]
#[no_mangle]
#[cfg(not(feature = "stateless"))]
pub extern "C" fn get_leaf(ctx: *mut RLN, index: usize, output_buffer: *mut Buffer) -> bool {
call_with_output_arg!(ctx, get_leaf, output_buffer, index)
}
#[allow(clippy::not_unsafe_ptr_arg_deref)]
#[no_mangle]
#[cfg(not(feature = "stateless"))]
pub extern "C" fn leaves_set(ctx: *mut RLN) -> usize {
ctx.process().leaves_set()
}
#[allow(clippy::not_unsafe_ptr_arg_deref)]
#[no_mangle]
#[cfg(not(feature = "stateless"))]
pub extern "C" fn set_next_leaf(ctx: *mut RLN, input_buffer: *const Buffer) -> bool {
call!(ctx, set_next_leaf, input_buffer)
}
#[allow(clippy::not_unsafe_ptr_arg_deref)]
#[no_mangle]
#[cfg(not(feature = "stateless"))]
pub extern "C" fn set_leaves_from(
ctx: *mut RLN,
index: usize,
@ -285,12 +335,14 @@ pub extern "C" fn set_leaves_from(
#[allow(clippy::not_unsafe_ptr_arg_deref)]
#[no_mangle]
#[cfg(not(feature = "stateless"))]
pub extern "C" fn init_tree_with_leaves(ctx: *mut RLN, input_buffer: *const Buffer) -> bool {
call!(ctx, init_tree_with_leaves, input_buffer)
}
#[allow(clippy::not_unsafe_ptr_arg_deref)]
#[no_mangle]
#[cfg(not(feature = "stateless"))]
pub extern "C" fn atomic_operation(
ctx: *mut RLN,
index: usize,
@ -302,6 +354,7 @@ pub extern "C" fn atomic_operation(
#[allow(clippy::not_unsafe_ptr_arg_deref)]
#[no_mangle]
#[cfg(not(feature = "stateless"))]
pub extern "C" fn seq_atomic_operation(
ctx: *mut RLN,
leaves_buffer: *const Buffer,
@ -318,12 +371,14 @@ pub extern "C" fn seq_atomic_operation(
#[allow(clippy::not_unsafe_ptr_arg_deref)]
#[no_mangle]
#[cfg(not(feature = "stateless"))]
pub extern "C" fn get_root(ctx: *const RLN, output_buffer: *mut Buffer) -> bool {
call_with_output_arg!(ctx, get_root, output_buffer)
}
#[allow(clippy::not_unsafe_ptr_arg_deref)]
#[no_mangle]
#[cfg(not(feature = "stateless"))]
pub extern "C" fn get_proof(ctx: *const RLN, index: usize, output_buffer: *mut Buffer) -> bool {
call_with_output_arg!(ctx, get_proof, output_buffer, index)
}
@ -353,6 +408,7 @@ pub extern "C" fn verify(
#[allow(clippy::not_unsafe_ptr_arg_deref)]
#[no_mangle]
#[cfg(not(feature = "stateless"))]
pub extern "C" fn generate_rln_proof(
ctx: *mut RLN,
input_buffer: *const Buffer,
@ -378,6 +434,7 @@ pub extern "C" fn generate_rln_proof_with_witness(
#[allow(clippy::not_unsafe_ptr_arg_deref)]
#[no_mangle]
#[cfg(not(feature = "stateless"))]
pub extern "C" fn verify_rln_proof(
ctx: *const RLN,
proof_buffer: *const Buffer,
@ -461,18 +518,21 @@ pub extern "C" fn recover_id_secret(
#[allow(clippy::not_unsafe_ptr_arg_deref)]
#[no_mangle]
#[cfg(not(feature = "stateless"))]
pub extern "C" fn set_metadata(ctx: *mut RLN, input_buffer: *const Buffer) -> bool {
call!(ctx, set_metadata, input_buffer)
}
#[allow(clippy::not_unsafe_ptr_arg_deref)]
#[no_mangle]
#[cfg(not(feature = "stateless"))]
pub extern "C" fn get_metadata(ctx: *const RLN, output_buffer: *mut Buffer) -> bool {
call_with_output_arg!(ctx, get_metadata, output_buffer)
}
#[allow(clippy::not_unsafe_ptr_arg_deref)]
#[no_mangle]
#[cfg(not(feature = "stateless"))]
pub extern "C" fn flush(ctx: *mut RLN) -> bool {
call!(ctx, flush)
}

View File

@ -43,6 +43,7 @@ pub const RLN_IDENTIFIER: &[u8] = b"zerokit/rln/010203040506070809";
pub struct RLN {
proving_key: (ProvingKey<Curve>, ConstraintMatrices<Fr>),
pub(crate) verification_key: VerifyingKey<Curve>,
#[cfg(not(feature = "stateless"))]
pub(crate) tree: PoseidonTree,
// The witness calculator can't be loaded in zerokit. Since this struct
@ -66,12 +67,12 @@ impl RLN {
/// use std::io::Cursor;
///
/// let tree_height = 20;
/// let input = Cursor::new(json!({}).to_string());;
/// let input = Cursor::new(json!({}).to_string());
///
/// // We create a new RLN instance
/// let mut rln = RLN::new(tree_height, input);
/// ```
#[cfg(not(target_arch = "wasm32"))]
#[cfg(all(not(target_arch = "wasm32"), not(feature = "stateless")))]
pub fn new<R: Read>(tree_height: usize, mut input_data: R) -> Result<RLN> {
// We read input
let mut input: Vec<u8> = Vec::new();
@ -102,12 +103,35 @@ impl RLN {
witness_calculator: witness_calculator.to_owned(),
proving_key: proving_key.to_owned(),
verification_key: verification_key.to_owned(),
#[cfg(not(feature = "stateless"))]
tree,
#[cfg(target_arch = "wasm32")]
_marker: PhantomData,
})
}
/// Creates a new stateless RLN object by loading circuit resources from a folder.
/// ```
/// // We create a new RLN instance
/// let mut rln = RLN::new(input);
/// ```
#[cfg_attr(docsrs, doc(cfg(feature = "stateless")))]
#[cfg(all(not(target_arch = "wasm32"), feature = "stateless"))]
pub fn new() -> Result<RLN> {
#[cfg(not(target_arch = "wasm32"))]
let witness_calculator = circom_from_folder();
let proving_key = zkey_from_folder();
let verification_key = vk_from_folder();
Ok(RLN {
witness_calculator: witness_calculator.to_owned(),
proving_key: proving_key.to_owned(),
verification_key: verification_key.to_owned(),
#[cfg(target_arch = "wasm32")]
_marker: PhantomData,
})
}
/// Creates a new RLN object by passing circuit resources as byte vectors.
///
/// Input parameters are
@ -145,7 +169,7 @@ impl RLN {
/// tree_config_input,
/// );
/// ```
#[cfg(not(target_arch = "wasm32"))]
#[cfg(all(not(target_arch = "wasm32"), not(feature = "stateless")))]
pub fn new_with_params<R: Read>(
tree_height: usize,
circom_vec: Vec<u8>,
@ -180,14 +204,16 @@ impl RLN {
witness_calculator,
proving_key,
verification_key,
#[cfg(not(feature = "stateless"))]
tree,
#[cfg(target_arch = "wasm32")]
_marker: PhantomData,
})
}
#[cfg(target_arch = "wasm32")]
#[cfg(all(target_arch = "wasm32", not(feature = "stateless")))]
pub fn new_with_params(tree_height: usize, zkey_vec: Vec<u8>, vk_vec: Vec<u8>) -> Result<RLN> {
// TODO: check this lines while update rln-wasm
#[cfg(not(target_arch = "wasm32"))]
let witness_calculator = circom_from_raw(circom_vec)?;
@ -205,6 +231,93 @@ impl RLN {
})
}
/// Creates a new stateless RLN object by passing circuit resources as byte vectors.
///
/// Input parameters are
/// - `circom_vec`: a byte vector containing the ZK circuit (`rln.wasm`) as binary file
/// - `zkey_vec`: a byte vector containing to the proving key (`rln_final.zkey`) or (`rln_final.arkzkey`) as binary file
/// - `vk_vec`: a byte vector containing to the verification key (`verification_key.arkvkey`) as binary file
///
/// Example:
/// ```
/// use std::fs::File;
/// use std::io::Read;
///
/// let resources_folder = "./resources/tree_height_20/";
///
/// let mut resources: Vec<Vec<u8>> = Vec::new();
/// for filename in ["rln.wasm", "rln_final.zkey", "verification_key.arkvkey"] {
/// let fullpath = format!("{resources_folder}{filename}");
/// let mut file = File::open(&fullpath).expect("no file found");
/// let metadata = std::fs::metadata(&fullpath).expect("unable to read metadata");
/// let mut buffer = vec![0; metadata.len() as usize];
/// file.read_exact(&mut buffer).expect("buffer overflow");
/// resources.push(buffer);
/// }
///
/// let mut rln = RLN::new_with_params(
/// resources[0].clone(),
/// resources[1].clone(),
/// resources[2].clone(),
/// );
/// ```
#[cfg(all(not(target_arch = "wasm32"), feature = "stateless"))]
pub fn new_with_params(circom_vec: Vec<u8>, zkey_vec: Vec<u8>, vk_vec: Vec<u8>) -> Result<RLN> {
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)?;
Ok(RLN {
witness_calculator,
proving_key,
verification_key,
})
}
/// Creates a new stateless RLN object by passing circuit resources as byte vectors.
///
/// Input parameters are
/// - `zkey_vec`: a byte vector containing to the proving key (`rln_final.zkey`) or (`rln_final.arkzkey`) as binary file
/// - `vk_vec`: a byte vector containing to the verification key (`verification_key.arkvkey`) as binary file
///
/// Example:
/// ```
/// use std::fs::File;
/// use std::io::Read;
///
/// let resources_folder = "./resources/tree_height_20/";
///
/// let mut resources: Vec<Vec<u8>> = Vec::new();
/// for filename in ["rln_final.zkey", "verification_key.arkvkey"] {
/// let fullpath = format!("{resources_folder}{filename}");
/// let mut file = File::open(&fullpath).expect("no file found");
/// let metadata = std::fs::metadata(&fullpath).expect("unable to read metadata");
/// let mut buffer = vec![0; metadata.len() as usize];
/// file.read_exact(&mut buffer).expect("buffer overflow");
/// resources.push(buffer);
/// }
///
/// let mut rln = RLN::new_with_params(
/// resources[0].clone(),
/// resources[1].clone(),
/// );
/// ```
#[cfg(all(target_arch = "wasm32", feature = "stateless"))]
pub fn new_with_params(zkey_vec: Vec<u8>, vk_vec: Vec<u8>) -> Result<RLN> {
#[cfg(not(target_arch = "wasm32"))]
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)?;
Ok(RLN {
proving_key,
verification_key,
_marker: PhantomData,
})
}
////////////////////////////////////////////////////////
// Merkle-tree APIs
////////////////////////////////////////////////////////
@ -214,6 +327,7 @@ impl RLN {
///
/// Input values are:
/// - `tree_height`: the height of the Merkle tree.
#[cfg(not(feature = "stateless"))]
pub fn set_tree(&mut self, tree_height: usize) -> Result<()> {
// We compute a default empty tree of desired height
self.tree = PoseidonTree::default(tree_height)?;
@ -244,6 +358,7 @@ impl RLN {
/// let mut buffer = Cursor::new(serialize_field_element(rate_commitment));
/// rln.set_leaf(id_index, &mut buffer).unwrap();
/// ```
#[cfg(not(feature = "stateless"))]
pub fn set_leaf<R: Read>(&mut self, index: usize, mut input_data: R) -> Result<()> {
// We read input
let mut leaf_byte: Vec<u8> = Vec::new();
@ -273,6 +388,7 @@ impl RLN {
/// let mut buffer = Cursor::new(Vec::<u8>::new());
/// rln.get_leaf(id_index, &mut buffer).unwrap();
/// let rate_commitment = deserialize_field_element(&buffer.into_inner()).unwrap();
#[cfg(not(feature = "stateless"))]
pub fn get_leaf<W: Write>(&self, index: usize, mut output_data: W) -> Result<()> {
// We get the leaf at input index
let leaf = self.tree.get(index)?;
@ -315,6 +431,7 @@ impl RLN {
/// let mut buffer = Cursor::new(vec_fr_to_bytes_le(&leaves));
/// rln.set_leaves_from(index, &mut buffer).unwrap();
/// ```
#[cfg(not(feature = "stateless"))]
pub fn set_leaves_from<R: Read>(&mut self, index: usize, mut input_data: R) -> Result<()> {
// We read input
let mut leaves_byte: Vec<u8> = Vec::new();
@ -335,6 +452,7 @@ impl RLN {
///
/// Input values are:
/// - `input_data`: a reader for the serialization of multiple leaf values (serialization done with [`rln::utils::vec_fr_to_bytes_le`](crate::utils::vec_fr_to_bytes_le))
#[cfg(not(feature = "stateless"))]
pub fn init_tree_with_leaves<R: Read>(&mut self, input_data: R) -> Result<()> {
// reset the tree
// NOTE: this requires the tree to be initialized with the correct height initially
@ -385,6 +503,7 @@ impl RLN {
/// let mut indices_buffer = Cursor::new(vec_u8_to_bytes_le(&indices));
/// rln.atomic_operation(index, &mut leaves_buffer, indices_buffer).unwrap();
/// ```
#[cfg(not(feature = "stateless"))]
pub fn atomic_operation<R: Read>(
&mut self,
index: usize,
@ -410,6 +529,7 @@ impl RLN {
Ok(())
}
#[cfg(not(feature = "stateless"))]
pub fn leaves_set(&mut self) -> usize {
self.tree.leaves_set()
}
@ -457,6 +577,7 @@ impl RLN {
/// let mut buffer = Cursor::new(fr_to_bytes_le(&rate_commitment));
/// rln.set_next_leaf(&mut buffer).unwrap();
/// ```
#[cfg(not(feature = "stateless"))]
pub fn set_next_leaf<R: Read>(&mut self, mut input_data: R) -> Result<()> {
// We read input
let mut leaf_byte: Vec<u8> = Vec::new();
@ -482,6 +603,7 @@ impl RLN {
/// let index = 10;
/// rln.delete_leaf(index).unwrap();
/// ```
#[cfg(not(feature = "stateless"))]
pub fn delete_leaf(&mut self, index: usize) -> Result<()> {
self.tree.delete(index)?;
Ok(())
@ -499,6 +621,7 @@ impl RLN {
/// let metadata = b"some metadata";
/// rln.set_metadata(metadata).unwrap();
/// ```
#[cfg(not(feature = "stateless"))]
pub fn set_metadata(&mut self, metadata: &[u8]) -> Result<()> {
self.tree.set_metadata(metadata)?;
Ok(())
@ -518,6 +641,7 @@ impl RLN {
/// rln.get_metadata(&mut buffer).unwrap();
/// let metadata = buffer.into_inner();
/// ```
#[cfg(not(feature = "stateless"))]
pub fn get_metadata<W: Write>(&self, mut output_data: W) -> Result<()> {
let metadata = self.tree.metadata()?;
output_data.write_all(&metadata)?;
@ -537,6 +661,7 @@ impl RLN {
/// rln.get_root(&mut buffer).unwrap();
/// let (root, _) = bytes_le_to_fr(&buffer.into_inner());
/// ```
#[cfg(not(feature = "stateless"))]
pub fn get_root<W: Write>(&self, mut output_data: W) -> Result<()> {
let root = self.tree.root();
output_data.write_all(&fr_to_bytes_le(&root))?;
@ -559,6 +684,7 @@ impl RLN {
/// rln.get_subtree_root(level, index, &mut buffer).unwrap();
/// let (subroot, _) = bytes_le_to_fr(&buffer.into_inner());
/// ```
#[cfg(not(feature = "stateless"))]
pub fn get_subtree_root<W: Write>(
&self,
level: usize,
@ -592,6 +718,7 @@ impl RLN {
/// let (path_elements, read) = bytes_le_to_vec_fr(&buffer_inner);
/// let (identity_path_index, _) = bytes_le_to_vec_u8(&buffer_inner[read..].to_vec());
/// ```
#[cfg(not(feature = "stateless"))]
pub fn get_proof<W: Write>(&self, index: usize, mut output_data: W) -> Result<()> {
let merkle_proof = self.tree.proof(index).expect("proof should exist");
let path_elements = merkle_proof.get_path_elements();
@ -635,6 +762,7 @@ impl RLN {
/// let idxs = bytes_le_to_vec_usize(&buffer.into_inner()).unwrap();
/// assert_eq!(idxs, [0, 1, 2, 3, 4]);
/// ```
#[cfg(not(feature = "stateless"))]
pub fn get_empty_leaves_indices<W: Write>(&self, mut output_data: W) -> Result<()> {
let idxs = self.tree.get_empty_leaves_indices();
idxs.serialize_compressed(&mut output_data)?;
@ -793,7 +921,7 @@ impl RLN {
/// // proof_data is [ proof<128> | root<32> | external_nullifier<32> | x<32> | y<32> | nullifier<32>]
/// let mut proof_data = output_buffer.into_inner();
/// ```
#[cfg(not(target_arch = "wasm32"))]
#[cfg(all(not(target_arch = "wasm32"), not(feature = "stateless")))]
pub fn generate_rln_proof<R: Read, W: Write>(
&mut self,
mut input_data: R,
@ -890,6 +1018,7 @@ impl RLN {
///
/// assert!(verified);
/// ```
#[cfg(not(feature = "stateless"))]
pub fn verify_rln_proof<R: Read>(&self, mut input_data: R) -> Result<bool> {
let mut serialized: Vec<u8> = Vec::new();
input_data.read_to_end(&mut serialized)?;
@ -1254,6 +1383,7 @@ impl RLN {
/// - `input_data`: a reader for the serialization of `[ identity_secret<32> | id_index<8> | user_message_limit<32> | message_id<32> | external_nullifier<32> | signal_len<8> | signal<var> ]`
///
/// The function returns the corresponding [`RLNWitnessInput`](crate::protocol::RLNWitnessInput) object serialized using [`rln::protocol::serialize_witness`](crate::protocol::serialize_witness)).
#[cfg(not(feature = "stateless"))]
pub fn get_serialized_rln_witness<R: Read>(&mut self, mut input_data: R) -> Result<Vec<u8>> {
// We read input RLN witness and we serialize_compressed it
let mut witness_byte: Vec<u8> = Vec::new();
@ -1293,6 +1423,7 @@ impl RLN {
/// This function should be called before the RLN object is dropped.
/// If not called, the connection will be closed when the RLN object is dropped.
/// This improves robustness of the tree.
#[cfg(not(feature = "stateless"))]
pub fn flush(&mut self) -> Result<()> {
self.tree.close_db_connection()
}
@ -1301,9 +1432,14 @@ impl RLN {
#[cfg(not(target_arch = "wasm32"))]
impl Default for RLN {
fn default() -> Self {
let tree_height = TEST_TREE_HEIGHT;
let buffer = Cursor::new(json!({}).to_string());
Self::new(tree_height, buffer).unwrap()
#[cfg(not(feature = "stateless"))]
{
let tree_height = TEST_TREE_HEIGHT;
let buffer = Cursor::new(json!({}).to_string());
Self::new(tree_height, buffer).unwrap()
}
#[cfg(feature = "stateless")]
Self::new().unwrap()
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,5 @@
#[cfg(test)]
#[cfg(not(feature = "stateless"))]
mod test {
use ark_std::{rand::thread_rng, UniformRand};
use rand::Rng;
@ -529,7 +530,7 @@ mod test {
#[test]
// Computes and verifies an RLN ZK proof by checking proof's root against an input roots buffer
fn test_verify_with_roots() {
fn test_verify_with_roots_ffi() {
// First part similar to test_rln_proof_ffi
let user_message_limit = Fr::from(100);
@ -905,7 +906,7 @@ mod test {
}
#[test]
fn test_get_leaf() {
fn test_get_leaf_ffi() {
// We create a RLN instance
let no_of_leaves = 1 << TEST_TREE_HEIGHT;
@ -945,7 +946,7 @@ mod test {
}
#[test]
fn test_valid_metadata() {
fn test_valid_metadata_ffi() {
// We create a RLN instance
let rln_pointer = create_rln_instance();
@ -966,7 +967,7 @@ mod test {
}
#[test]
fn test_empty_metadata() {
fn test_empty_metadata_ffi() {
// We create a RLN instance
let rln_pointer = create_rln_instance();
@ -978,3 +979,471 @@ mod test {
assert_eq!(output_buffer.len, 0);
}
}
#[cfg(test)]
#[cfg(feature = "stateless")]
mod stateless_test {
use ark_std::{rand::thread_rng, UniformRand};
use rand::Rng;
use rln::circuit::*;
use rln::ffi::generate_rln_proof_with_witness;
use rln::ffi::{hash as ffi_hash, poseidon_hash as ffi_poseidon_hash, *};
use rln::hashers::{hash_to_field, poseidon_hash as utils_poseidon_hash, ROUND_PARAMS};
use rln::poseidon_tree::PoseidonTree;
use rln::protocol::*;
use rln::public::RLN;
use rln::utils::*;
use std::mem::MaybeUninit;
use std::time::{Duration, Instant};
use utils::ZerokitMerkleTree;
type ConfigOf<T> = <T as ZerokitMerkleTree>::Config;
fn create_rln_instance() -> &'static mut RLN {
let mut rln_pointer = MaybeUninit::<*mut RLN>::uninit();
let success = new(rln_pointer.as_mut_ptr());
assert!(success, "RLN object creation failed");
unsafe { &mut *rln_pointer.assume_init() }
}
fn identity_pair_gen(rln_pointer: &mut RLN) -> (Fr, Fr) {
let mut output_buffer = MaybeUninit::<Buffer>::uninit();
let success = key_gen(rln_pointer, output_buffer.as_mut_ptr());
assert!(success, "key gen call failed");
let output_buffer = unsafe { output_buffer.assume_init() };
let result_data = <&[u8]>::from(&output_buffer).to_vec();
let (identity_secret_hash, read) = bytes_le_to_fr(&result_data);
let (id_commitment, _) = bytes_le_to_fr(&result_data[read..].to_vec());
(identity_secret_hash, id_commitment)
}
fn rln_proof_gen_with_witness(rln_pointer: &mut RLN, serialized: &[u8]) -> Vec<u8> {
let input_buffer = &Buffer::from(serialized);
let mut output_buffer = MaybeUninit::<Buffer>::uninit();
let success =
generate_rln_proof_with_witness(rln_pointer, input_buffer, output_buffer.as_mut_ptr());
assert!(success, "generate rln proof call failed");
let output_buffer = unsafe { output_buffer.assume_init() };
<&[u8]>::from(&output_buffer).to_vec()
}
#[test]
fn test_recover_id_secret_stateless_ffi() {
let default_leaf = Fr::from(0);
let mut tree = PoseidonTree::new(
TEST_TREE_HEIGHT,
default_leaf,
ConfigOf::<PoseidonTree>::default(),
)
.unwrap();
let rln_pointer = create_rln_instance();
// We generate a new identity pair
let (identity_secret_hash, id_commitment) = identity_pair_gen(rln_pointer);
let user_message_limit = Fr::from(100);
let rate_commitment = utils_poseidon_hash(&[id_commitment, user_message_limit]);
tree.update_next(rate_commitment).unwrap();
// We generate a random epoch
let epoch = hash_to_field(b"test-epoch");
let rln_identifier = hash_to_field(b"test-rln-identifier");
let external_nullifier = utils_poseidon_hash(&[epoch, rln_identifier]);
// We generate two proofs using same epoch but different signals.
// We generate a random signal
let mut rng = thread_rng();
let signal1: [u8; 32] = rng.gen();
let x1 = hash_to_field(&signal1);
let signal2: [u8; 32] = rng.gen();
let x2 = hash_to_field(&signal2);
let identity_index = tree.leaves_set();
let merkle_proof = tree.proof(identity_index).expect("proof should exist");
// We prepare input for generate_rln_proof API
let rln_witness1 = rln_witness_from_values(
identity_secret_hash,
&merkle_proof,
x1,
external_nullifier,
user_message_limit,
Fr::from(1),
)
.unwrap();
let serialized1 = serialize_witness(&rln_witness1).unwrap();
let rln_witness2 = rln_witness_from_values(
identity_secret_hash,
&merkle_proof,
x2,
external_nullifier,
user_message_limit,
Fr::from(1),
)
.unwrap();
let serialized2 = serialize_witness(&rln_witness2).unwrap();
// We call generate_rln_proof for first proof values
// result_data is [ proof<128> | root<32> | external_nullifier<32> | x<32> | y<32> | nullifier<32> ]
let proof_data_1 = rln_proof_gen_with_witness(rln_pointer, serialized1.as_ref());
// We call generate_rln_proof
// result_data is [ proof<128> | root<32> | external_nullifier<32> | x<32> | y<32> | nullifier<32> ]
let proof_data_2 = rln_proof_gen_with_witness(rln_pointer, serialized2.as_ref());
let input_proof_buffer_1 = &Buffer::from(proof_data_1.as_ref());
let input_proof_buffer_2 = &Buffer::from(proof_data_2.as_ref());
let mut output_buffer = MaybeUninit::<Buffer>::uninit();
let success = recover_id_secret(
rln_pointer,
input_proof_buffer_1,
input_proof_buffer_2,
output_buffer.as_mut_ptr(),
);
assert!(success, "recover id secret call failed");
let output_buffer = unsafe { output_buffer.assume_init() };
let serialized_identity_secret_hash = <&[u8]>::from(&output_buffer).to_vec();
// We passed two shares for the same secret, so recovery should be successful
// To check it, we ensure that recovered identity secret hash is empty
assert!(!serialized_identity_secret_hash.is_empty());
// We check if the recovered identity secret hash corresponds to the original one
let (recovered_identity_secret_hash, _) = bytes_le_to_fr(&serialized_identity_secret_hash);
assert_eq!(recovered_identity_secret_hash, identity_secret_hash);
// We now test that computing identity_secret_hash is unsuccessful if shares computed from two different identity secret hashes but within same epoch are passed
// We generate a new identity pair
let (identity_secret_hash_new, id_commitment_new) = identity_pair_gen(rln_pointer);
let rate_commitment_new = utils_poseidon_hash(&[id_commitment_new, user_message_limit]);
tree.update_next(rate_commitment_new).unwrap();
// We generate a random signals
let signal3: [u8; 32] = rng.gen();
let x3 = hash_to_field(&signal3);
let identity_index_new = tree.leaves_set();
let merkle_proof_new = tree.proof(identity_index_new).expect("proof should exist");
let rln_witness3 = rln_witness_from_values(
identity_secret_hash_new,
&merkle_proof_new,
x3,
external_nullifier,
user_message_limit,
Fr::from(1),
)
.unwrap();
let serialized3 = serialize_witness(&rln_witness3).unwrap();
// We call generate_rln_proof
// result_data is [ proof<128> | root<32> | external_nullifier<32> | x<32> | y<32> | nullifier<32> ]
let proof_data_3 = rln_proof_gen_with_witness(rln_pointer, serialized3.as_ref());
// We attempt to recover the secret using share1 (coming from identity_secret_hash) and share3 (coming from identity_secret_hash_new)
let input_proof_buffer_1 = &Buffer::from(proof_data_1.as_ref());
let input_proof_buffer_3 = &Buffer::from(proof_data_3.as_ref());
let mut output_buffer = MaybeUninit::<Buffer>::uninit();
let success = recover_id_secret(
rln_pointer,
input_proof_buffer_1,
input_proof_buffer_3,
output_buffer.as_mut_ptr(),
);
assert!(success, "recover id secret call failed");
let output_buffer = unsafe { output_buffer.assume_init() };
let serialized_identity_secret_hash = <&[u8]>::from(&output_buffer).to_vec();
let (recovered_identity_secret_hash_new, _) =
bytes_le_to_fr(&serialized_identity_secret_hash);
// ensure that the recovered secret does not match with either of the
// used secrets in proof generation
assert_ne!(recovered_identity_secret_hash_new, identity_secret_hash_new);
}
#[test]
fn test_verify_with_roots_stateless_ffi() {
let default_leaf = Fr::from(0);
let mut tree = PoseidonTree::new(
TEST_TREE_HEIGHT,
default_leaf,
ConfigOf::<PoseidonTree>::default(),
)
.unwrap();
let rln_pointer = create_rln_instance();
// We generate a new identity pair
let (identity_secret_hash, id_commitment) = identity_pair_gen(rln_pointer);
let identity_index = tree.leaves_set();
let user_message_limit = Fr::from(100);
let rate_commitment = utils_poseidon_hash(&[id_commitment, user_message_limit]);
tree.update_next(rate_commitment).unwrap();
// We generate a random epoch
let epoch = hash_to_field(b"test-epoch");
let rln_identifier = hash_to_field(b"test-rln-identifier");
let external_nullifier = utils_poseidon_hash(&[epoch, rln_identifier]);
// We generate two proofs using same epoch but different signals.
// We generate a random signal
let mut rng = thread_rng();
let signal: [u8; 32] = rng.gen();
let x = hash_to_field(&signal);
let merkle_proof = tree.proof(identity_index).expect("proof should exist");
// We prepare input for generate_rln_proof API
let rln_witness = rln_witness_from_values(
identity_secret_hash,
&merkle_proof,
x,
external_nullifier,
user_message_limit,
Fr::from(1),
)
.unwrap();
let serialized = serialize_witness(&rln_witness).unwrap();
let mut proof_data = rln_proof_gen_with_witness(rln_pointer, serialized.as_ref());
proof_data.append(&mut normalize_usize(signal.len()));
proof_data.append(&mut signal.to_vec());
// If no roots is provided, proof validation is skipped and if the remaining proof values are valid, the proof will be correctly verified
let mut roots_data: Vec<u8> = Vec::new();
let input_buffer = &Buffer::from(proof_data.as_ref());
let roots_buffer = &Buffer::from(roots_data.as_ref());
let mut proof_is_valid: bool = false;
let proof_is_valid_ptr = &mut proof_is_valid as *mut bool;
let success =
verify_with_roots(rln_pointer, input_buffer, roots_buffer, proof_is_valid_ptr);
assert!(success, "verify call failed");
// Proof should be valid
assert_eq!(proof_is_valid, true);
// We serialize in the roots buffer some random values and we check that the proof is not verified since doesn't contain the correct root the proof refers to
for _ in 0..5 {
roots_data.append(&mut fr_to_bytes_le(&Fr::rand(&mut rng)));
}
let input_buffer = &Buffer::from(proof_data.as_ref());
let roots_buffer = &Buffer::from(roots_data.as_ref());
let mut proof_is_valid: bool = false;
let proof_is_valid_ptr = &mut proof_is_valid as *mut bool;
let success =
verify_with_roots(rln_pointer, input_buffer, roots_buffer, proof_is_valid_ptr);
assert!(success, "verify call failed");
// Proof should be invalid.
assert_eq!(proof_is_valid, false);
// We get the root of the tree obtained adding one leaf per time
let root = tree.root();
// We add the real root and we check if now the proof is verified
roots_data.append(&mut fr_to_bytes_le(&root));
let input_buffer = &Buffer::from(proof_data.as_ref());
let roots_buffer = &Buffer::from(roots_data.as_ref());
let mut proof_is_valid: bool = false;
let proof_is_valid_ptr = &mut proof_is_valid as *mut bool;
let success =
verify_with_roots(rln_pointer, input_buffer, roots_buffer, proof_is_valid_ptr);
assert!(success, "verify call failed");
// Proof should be valid.
assert_eq!(proof_is_valid, true);
}
#[test]
fn test_groth16_proofs_performance_stateless_ffi() {
// We create a RLN instance
let rln_pointer = create_rln_instance();
// We compute some benchmarks regarding proof and verify API calls
// Note that circuit loading requires some initial overhead.
// Once the circuit is loaded (i.e., when the RLN object is created), proof generation and verification times should be similar at each call.
let sample_size = 100;
let mut prove_time: u128 = 0;
let mut verify_time: u128 = 0;
for _ in 0..sample_size {
// We generate random witness instances and relative proof values
let rln_witness = random_rln_witness(TEST_TREE_HEIGHT);
let proof_values = proof_values_from_witness(&rln_witness).unwrap();
// We prepare id_commitment and we set the leaf at provided index
let rln_witness_ser = serialize_witness(&rln_witness).unwrap();
let input_buffer = &Buffer::from(rln_witness_ser.as_ref());
let mut output_buffer = MaybeUninit::<Buffer>::uninit();
let now = Instant::now();
let success = prove(rln_pointer, input_buffer, output_buffer.as_mut_ptr());
prove_time += now.elapsed().as_nanos();
assert!(success, "prove call failed");
let output_buffer = unsafe { output_buffer.assume_init() };
// We read the returned proof and we append proof values for verify
let serialized_proof = <&[u8]>::from(&output_buffer).to_vec();
let serialized_proof_values = serialize_proof_values(&proof_values);
let mut verify_data = Vec::<u8>::new();
verify_data.extend(&serialized_proof);
verify_data.extend(&serialized_proof_values);
// We prepare input proof values and we call verify
let input_buffer = &Buffer::from(verify_data.as_ref());
let mut proof_is_valid: bool = false;
let proof_is_valid_ptr = &mut proof_is_valid as *mut bool;
let now = Instant::now();
let success = verify(rln_pointer, input_buffer, proof_is_valid_ptr);
verify_time += now.elapsed().as_nanos();
assert!(success, "verify call failed");
assert_eq!(proof_is_valid, true);
}
println!(
"Average prove API call time: {:?}",
Duration::from_nanos((prove_time / sample_size).try_into().unwrap())
);
println!(
"Average verify API call time: {:?}",
Duration::from_nanos((verify_time / sample_size).try_into().unwrap())
);
}
#[test]
// Tests hash to field using FFI APIs
fn test_seeded_keygen_stateless_ffi() {
// We create a RLN instance
let rln_pointer = create_rln_instance();
// We generate a new identity pair from an input seed
let seed_bytes: &[u8] = &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
let input_buffer = &Buffer::from(seed_bytes);
let mut output_buffer = MaybeUninit::<Buffer>::uninit();
let success = seeded_key_gen(rln_pointer, input_buffer, output_buffer.as_mut_ptr());
assert!(success, "seeded key gen call failed");
let output_buffer = unsafe { output_buffer.assume_init() };
let result_data = <&[u8]>::from(&output_buffer).to_vec();
let (identity_secret_hash, read) = bytes_le_to_fr(&result_data);
let (id_commitment, _) = bytes_le_to_fr(&result_data[read..].to_vec());
// We check against expected values
let expected_identity_secret_hash_seed_bytes = str_to_fr(
"0x766ce6c7e7a01bdf5b3f257616f603918c30946fa23480f2859c597817e6716",
16,
);
let expected_id_commitment_seed_bytes = str_to_fr(
"0xbf16d2b5c0d6f9d9d561e05bfca16a81b4b873bb063508fae360d8c74cef51f",
16,
);
assert_eq!(
identity_secret_hash,
expected_identity_secret_hash_seed_bytes.unwrap()
);
assert_eq!(id_commitment, expected_id_commitment_seed_bytes.unwrap());
}
#[test]
// Tests hash to field using FFI APIs
fn test_seeded_extended_keygen_stateless_ffi() {
// We create a RLN instance
let rln_pointer = create_rln_instance();
// We generate a new identity tuple from an input seed
let seed_bytes: &[u8] = &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
let input_buffer = &Buffer::from(seed_bytes);
let mut output_buffer = MaybeUninit::<Buffer>::uninit();
let success =
seeded_extended_key_gen(rln_pointer, input_buffer, output_buffer.as_mut_ptr());
assert!(success, "seeded key gen call failed");
let output_buffer = unsafe { output_buffer.assume_init() };
let result_data = <&[u8]>::from(&output_buffer).to_vec();
let (identity_trapdoor, identity_nullifier, identity_secret_hash, id_commitment) =
deserialize_identity_tuple(result_data);
// We check against expected values
let expected_identity_trapdoor_seed_bytes = str_to_fr(
"0x766ce6c7e7a01bdf5b3f257616f603918c30946fa23480f2859c597817e6716",
16,
);
let expected_identity_nullifier_seed_bytes = str_to_fr(
"0x1f18714c7bc83b5bca9e89d404cf6f2f585bc4c0f7ed8b53742b7e2b298f50b4",
16,
);
let expected_identity_secret_hash_seed_bytes = str_to_fr(
"0x2aca62aaa7abaf3686fff2caf00f55ab9462dc12db5b5d4bcf3994e671f8e521",
16,
);
let expected_id_commitment_seed_bytes = str_to_fr(
"0x68b66aa0a8320d2e56842581553285393188714c48f9b17acd198b4f1734c5c",
16,
);
assert_eq!(
identity_trapdoor,
expected_identity_trapdoor_seed_bytes.unwrap()
);
assert_eq!(
identity_nullifier,
expected_identity_nullifier_seed_bytes.unwrap()
);
assert_eq!(
identity_secret_hash,
expected_identity_secret_hash_seed_bytes.unwrap()
);
assert_eq!(id_commitment, expected_id_commitment_seed_bytes.unwrap());
}
#[test]
// Tests hash to field using FFI APIs
fn test_hash_to_field_stateless_ffi() {
let mut rng = rand::thread_rng();
let signal: [u8; 32] = rng.gen();
// We prepare id_commitment and we set the leaf at provided index
let input_buffer = &Buffer::from(signal.as_ref());
let mut output_buffer = MaybeUninit::<Buffer>::uninit();
let success = ffi_hash(input_buffer, output_buffer.as_mut_ptr());
assert!(success, "hash call failed");
let output_buffer = unsafe { output_buffer.assume_init() };
// We read the returned proof and we append proof values for verify
let serialized_hash = <&[u8]>::from(&output_buffer).to_vec();
let (hash1, _) = bytes_le_to_fr(&serialized_hash);
let hash2 = hash_to_field(&signal);
assert_eq!(hash1, hash2);
}
#[test]
// Test Poseidon hash FFI
fn test_poseidon_hash_stateless_ffi() {
// generate random number between 1..ROUND_PARAMS.len()
let mut rng = thread_rng();
let number_of_inputs = rng.gen_range(1..ROUND_PARAMS.len());
let mut inputs = Vec::with_capacity(number_of_inputs);
for _ in 0..number_of_inputs {
inputs.push(Fr::rand(&mut rng));
}
let inputs_ser = vec_fr_to_bytes_le(&inputs).unwrap();
let input_buffer = &Buffer::from(inputs_ser.as_ref());
let expected_hash = utils_poseidon_hash(inputs.as_ref());
let mut output_buffer = MaybeUninit::<Buffer>::uninit();
let success = ffi_poseidon_hash(input_buffer, output_buffer.as_mut_ptr());
assert!(success, "poseidon hash call failed");
let output_buffer = unsafe { output_buffer.assume_init() };
let result_data = <&[u8]>::from(&output_buffer).to_vec();
let (received_hash, _) = bytes_le_to_fr(&result_data);
assert_eq!(received_hash, expected_hash);
}
}

View File

@ -12,6 +12,7 @@ mod test {
#[test]
// This test is similar to the one in lib, but uses only public API
#[cfg(not(feature = "stateless"))]
fn test_merkle_proof() {
let leaf_index = 3;
let user_message_limit = 1;

View File

@ -13,9 +13,11 @@ bench = false
[dependencies]
ark-ff = { version = "=0.4.1", default-features = false, features = ["asm"] }
num-bigint = { version = "=0.4.3", default-features = false, features = ["rand"] }
num-bigint = { version = "=0.4.3", default-features = false, features = [
"rand",
] }
color-eyre = "=0.6.2"
pmtree = { package = "vacp2p_pmtree", version = "=2.0.2", optional = true}
pmtree = { package = "vacp2p_pmtree", version = "=2.0.2", optional = true }
sled = "=0.34.7"
serde = "=1.0.163"
lazy_static = "1.4.0"