Files
zerokit/rln/tests/public.rs
Vinh Trịnh 77a8d28965 feat: unify RLN types, refactor public APIs, add full (de)serialization, align FFI/WASM/APIs, simplify errors, update docs/examples, and clean up zerokit (#355)
# Changes

- Unified the `RLN` struct and core protocol types across public, FFI,
and WASM so everything works consistently.
- Fully refactored `protocol.rs` and `public.rs` to clean up the API
surface and make the flow easier to work with.
- Added (de)serialization for `RLN_Proof` and `RLN_ProofValues`, and
matched all C, Nim, WASM, and Node.js examples.
- Aligned FFI and WASM behavior, added missing APIs, and standardized
how witness are created and passed around.
- Reworked the error types, added clearer verification messages, and
simplified the overall error structure.
- Updated variable names, README, Rust docs, and examples across the
repo, updated outdated RLN RFC link.
- Refactored `rln-cli` to use the new public API, removed
serialize-based cli example, and dropped the `eyre` crate.
- Bumped dependencies, fixed CI, fixed `+atomic` flags for latest
nightly Rust and added `Clippy.toml` for better fmt.
- Added a `prelude.rs` file for easier use, cleaned up public access for
types and types import across zerokit modules.
- Separated keygen, proof handling, slashing logic, and witness into
protocol folder.
2025-12-09 19:03:04 +07:00

1128 lines
41 KiB
Rust

#[cfg(test)]
mod test {
use std::str::FromStr;
use rand::{thread_rng, Rng};
use rln::prelude::*;
use serde_json::{json, Value};
fn fq_from_str(s: &str) -> Fq {
Fq::from_str(s).unwrap()
}
fn g1_from_str(g1: &[String]) -> G1Affine {
let x = fq_from_str(&g1[0]);
let y = fq_from_str(&g1[1]);
let z = fq_from_str(&g1[2]);
G1Affine::from(G1Projective::new(x, y, z))
}
fn g2_from_str(g2: &[Vec<String>]) -> G2Affine {
let c0 = fq_from_str(&g2[0][0]);
let c1 = fq_from_str(&g2[0][1]);
let x = Fq2::new(c0, c1);
let c0 = fq_from_str(&g2[1][0]);
let c1 = fq_from_str(&g2[1][1]);
let y = Fq2::new(c0, c1);
let c0 = fq_from_str(&g2[2][0]);
let c1 = fq_from_str(&g2[2][1]);
let z = Fq2::new(c0, c1);
G2Affine::from(G2Projective::new(x, y, z))
}
fn value_to_string_vec(value: &Value) -> Vec<String> {
value
.as_array()
.unwrap()
.iter()
.map(|val| val.as_str().unwrap().to_string())
.collect()
}
fn random_rln_witness(tree_depth: usize) -> Result<RLNWitnessInput, ProtocolError> {
let mut rng = thread_rng();
let identity_secret = IdSecret::rand(&mut rng);
let x = hash_to_field_le(&rng.gen::<[u8; 32]>());
let epoch = hash_to_field_le(&rng.gen::<[u8; 32]>());
let rln_identifier = hash_to_field_le(b"test-rln-identifier");
let mut path_elements: Vec<Fr> = Vec::new();
let mut identity_path_index: Vec<u8> = Vec::new();
for _ in 0..tree_depth {
path_elements.push(hash_to_field_le(&rng.gen::<[u8; 32]>()));
identity_path_index.push(rng.gen_range(0..2) as u8);
}
let user_message_limit = Fr::from(100);
let message_id = Fr::from(1);
let external_nullifier = poseidon_hash(&[epoch, rln_identifier]);
RLNWitnessInput::new(
identity_secret,
user_message_limit,
message_id,
path_elements,
identity_path_index,
x,
external_nullifier,
)
}
#[test]
fn test_groth16_proof_hardcoded() {
#[cfg(not(feature = "stateless"))]
let rln = RLN::new(DEFAULT_TREE_DEPTH, "").unwrap();
#[cfg(feature = "stateless")]
let rln = RLN::new().unwrap();
let valid_snarkjs_proof = json!({
"pi_a": [
"606446415626469993821291758185575230335423926365686267140465300918089871829",
"14881534001609371078663128199084130129622943308489025453376548677995646280161",
"1"
],
"pi_b": [
[
"18053812507994813734583839134426913715767914942522332114506614735770984570178",
"11219916332635123001710279198522635266707985651975761715977705052386984005181"
],
[
"17371289494006920912949790045699521359436706797224428511776122168520286372970",
"14038575727257298083893642903204723310279435927688342924358714639926373603890"
],
[
"1",
"0"
]
],
"pi_c": [
"17701377127561410274754535747274973758826089226897242202671882899370780845888",
"12608543716397255084418384146504333522628400182843246910626782513289789807030",
"1"
],
"protocol": "groth16",
"curve": "bn128"
});
let valid_ark_proof = Proof {
a: g1_from_str(&value_to_string_vec(&valid_snarkjs_proof["pi_a"])),
b: g2_from_str(
&valid_snarkjs_proof["pi_b"]
.as_array()
.unwrap()
.iter()
.map(value_to_string_vec)
.collect::<Vec<Vec<String>>>(),
),
c: g1_from_str(&value_to_string_vec(&valid_snarkjs_proof["pi_c"])),
};
let x = str_to_fr(
"20645213238265527935869146898028115621427162613172918400241870500502509785943",
10,
)
.unwrap();
let valid_proof_values = RLNProofValues {
x,
external_nullifier: str_to_fr(
"21074405743803627666274838159589343934394162804826017440941339048886754734203",
10,
)
.unwrap(),
y: str_to_fr(
"16401008481486069296141645075505218976370369489687327284155463920202585288271",
10,
)
.unwrap(),
root: str_to_fr(
"8502402278351299594663821509741133196466235670407051417832304486953898514733",
10,
)
.unwrap(),
nullifier: str_to_fr(
"9102791780887227194595604713537772536258726662792598131262022534710887343694",
10,
)
.unwrap(),
};
let verified = rln
.verify_with_roots(&valid_ark_proof, &valid_proof_values, &x, &[])
.is_ok();
assert!(verified);
}
#[test]
fn test_groth16_proof() {
let tree_depth = DEFAULT_TREE_DEPTH;
#[cfg(not(feature = "stateless"))]
let rln = RLN::new(tree_depth, "").unwrap();
#[cfg(feature = "stateless")]
let rln = RLN::new().unwrap();
// Note: we only test Groth16 proof generation, so we ignore setting the tree in the RLN object
let rln_witness = random_rln_witness(tree_depth).unwrap();
// We compute a Groth16 proof and proof values
let (proof, proof_values) = rln.generate_rln_proof(&rln_witness).unwrap();
// We verify the Groth16 proof against the provided proof values
let verified = rln.verify_zk_proof(&proof, &proof_values).is_ok();
assert!(verified);
}
#[cfg(not(feature = "stateless"))]
mod tree_test {
use ark_std::{rand::thread_rng, UniformRand};
use rand::Rng;
use rln::{
circuit::{Fr, DEFAULT_TREE_DEPTH},
hashers::{hash_to_field_le, poseidon_hash},
pm_tree_adapter::PmtreeConfig,
protocol::*,
public::RLN,
};
use serde_json::json;
const NO_OF_LEAVES: usize = 256;
#[test]
// We test merkle batch Merkle tree additions
fn test_merkle_operations() {
let tree_depth = DEFAULT_TREE_DEPTH;
// We generate a vector of random leaves
let mut leaves: Vec<Fr> = Vec::new();
let mut rng = thread_rng();
for _ in 0..NO_OF_LEAVES {
leaves.push(Fr::rand(&mut rng));
}
// We create a new tree
let mut rln = RLN::new(tree_depth, "").unwrap();
// We first add leaves one by one specifying the index
for (i, leaf) in leaves.iter().enumerate() {
// We check if the number of leaves set is consistent
assert_eq!(rln.leaves_set(), i);
rln.set_leaf(i, *leaf).unwrap();
}
// We get the root of the tree obtained adding one leaf per time
let root_single = rln.get_root();
// We reset the tree to default
rln.set_tree(tree_depth).unwrap();
// We add leaves one by one using the internal index (new leaves goes in next available position)
for leaf in &leaves {
rln.set_next_leaf(*leaf).unwrap();
}
// We check if numbers of leaves set is consistent
assert_eq!(rln.leaves_set(), NO_OF_LEAVES);
// We get the root of the tree obtained adding leaves using the internal index
let root_next = rln.get_root();
// We check if roots are the same
assert_eq!(root_single, root_next);
// We reset the tree to default
rln.set_tree(tree_depth).unwrap();
// We add leaves in a batch into the tree
rln.init_tree_with_leaves(leaves.clone()).unwrap();
// We check if number of leaves set is consistent
assert_eq!(rln.leaves_set(), NO_OF_LEAVES);
// We get the root of the tree obtained adding leaves in batch
let root_batch = rln.get_root();
// We check if roots are the same
assert_eq!(root_single, root_batch);
// We now delete all leaves set and check if the root corresponds to the empty tree root
// delete calls over indexes higher than no_of_leaves are ignored and will not increase self.tree.next_index
for i in 0..NO_OF_LEAVES {
rln.delete_leaf(i).unwrap();
}
// We check if number of leaves set is consistent
assert_eq!(rln.leaves_set(), NO_OF_LEAVES);
// We get the root of the tree obtained deleting all leaves
let root_delete = rln.get_root();
// We reset the tree to default
rln.set_tree(tree_depth).unwrap();
// We get the root of the empty tree
let root_empty = rln.get_root();
// We check if roots are the same
assert_eq!(root_delete, root_empty);
}
#[test]
// This test is similar to the one in ffi.rs but it uses the RLN object directly
// Uses `set_leaves_from` to set leaves in a batch
fn test_leaf_setting_with_index() {
let tree_depth = DEFAULT_TREE_DEPTH;
// We generate a vector of random leaves
let mut leaves: Vec<Fr> = Vec::new();
let mut rng = thread_rng();
for _ in 0..NO_OF_LEAVES {
leaves.push(Fr::rand(&mut rng));
}
// set_index is the index from which we start setting leaves
// random number between 0..no_of_leaves
let set_index = rng.gen_range(0..NO_OF_LEAVES) as usize;
// We create a new tree
let mut rln = RLN::new(tree_depth, "").unwrap();
// We add leaves in a batch into the tree
rln.init_tree_with_leaves(leaves.clone()).unwrap();
// We check if number of leaves set is consistent
assert_eq!(rln.leaves_set(), NO_OF_LEAVES);
// We get the root of the tree obtained adding leaves in batch
let root_batch_with_init = rln.get_root();
// `init_tree_with_leaves` resets the tree to the depth it was initialized with, using `set_tree`
// We add leaves in a batch starting from index 0..set_index
rln.init_tree_with_leaves(leaves[0..set_index].to_vec())
.unwrap();
// We add the remaining n leaves in a batch starting from index set_index
rln.set_leaves_from(set_index, leaves[set_index..].to_vec())
.unwrap();
// We check if number of leaves set is consistent
assert_eq!(rln.leaves_set(), NO_OF_LEAVES);
// We get the root of the tree obtained adding leaves in batch
let root_batch_with_custom_index = rln.get_root();
assert_eq!(root_batch_with_init, root_batch_with_custom_index);
// We reset the tree to default
rln.set_tree(tree_depth).unwrap();
// We add leaves one by one using the internal index (new leaves goes in next available position)
for leaf in &leaves {
rln.set_next_leaf(*leaf).unwrap();
}
// We check if numbers of leaves set is consistent
assert_eq!(rln.leaves_set(), NO_OF_LEAVES);
// We get the root of the tree obtained adding leaves using the internal index
let root_single_additions = rln.get_root();
assert_eq!(root_batch_with_init, root_single_additions);
rln.flush().unwrap();
}
#[test]
// This test is similar to the one in ffi.rs but it uses the RLN object directly
// Tests the atomic_operation fn, which set_leaves_from uses internally
fn test_atomic_operation() {
let tree_depth = DEFAULT_TREE_DEPTH;
// We generate a vector of random leaves
let mut leaves: Vec<Fr> = Vec::new();
let mut rng = thread_rng();
for _ in 0..NO_OF_LEAVES {
leaves.push(Fr::rand(&mut rng));
}
// We create a new tree
let mut rln = RLN::new(tree_depth, "").unwrap();
// We add leaves in a batch into the tree
rln.init_tree_with_leaves(leaves.clone()).unwrap();
// We check if number of leaves set is consistent
assert_eq!(rln.leaves_set(), NO_OF_LEAVES);
// We get the root of the tree obtained adding leaves in batch
let root_after_insertion = rln.get_root();
// We check if number of leaves set is consistent
assert_eq!(rln.leaves_set(), NO_OF_LEAVES);
let last_leaf = *leaves.last().unwrap();
let last_leaf_index = NO_OF_LEAVES - 1;
let indices = vec![last_leaf_index];
let last_leaf_vec = vec![last_leaf];
rln.atomic_operation(last_leaf_index, last_leaf_vec, indices)
.unwrap();
// We get the root of the tree obtained after a no-op
let root_after_noop = rln.get_root();
assert_eq!(root_after_insertion, root_after_noop);
}
#[test]
fn test_atomic_operation_zero_indexed() {
// Test duplicated from https://github.com/waku-org/go-zerokit-rln/pull/12/files
let tree_depth = DEFAULT_TREE_DEPTH;
// We generate a vector of random leaves
let mut leaves: Vec<Fr> = Vec::new();
let mut rng = thread_rng();
for _ in 0..NO_OF_LEAVES {
leaves.push(Fr::rand(&mut rng));
}
// We create a new tree
let mut rln = RLN::new(tree_depth, "").unwrap();
// We add leaves in a batch into the tree
rln.init_tree_with_leaves(leaves.clone()).unwrap();
// We check if number of leaves set is consistent
assert_eq!(rln.leaves_set(), NO_OF_LEAVES);
// We get the root of the tree obtained adding leaves in batch
let root_after_insertion = rln.get_root();
let zero_index = 0;
let indices = vec![zero_index];
let zero_leaf: Vec<Fr> = vec![];
rln.atomic_operation(0, zero_leaf, indices).unwrap();
// We get the root of the tree obtained after a deletion
let root_after_deletion = rln.get_root();
assert_ne!(root_after_insertion, root_after_deletion);
}
#[test]
fn test_atomic_operation_consistency() {
// Test duplicated from https://github.com/waku-org/go-zerokit-rln/pull/12/files
let tree_depth = DEFAULT_TREE_DEPTH;
// We generate a vector of random leaves
let mut leaves: Vec<Fr> = Vec::new();
let mut rng = thread_rng();
for _ in 0..NO_OF_LEAVES {
leaves.push(Fr::rand(&mut rng));
}
// We create a new tree
let mut rln = RLN::new(tree_depth, "").unwrap();
// We add leaves in a batch into the tree
rln.init_tree_with_leaves(leaves.clone()).unwrap();
// We check if number of leaves set is consistent
assert_eq!(rln.leaves_set(), NO_OF_LEAVES);
// We get the root of the tree obtained adding leaves in batch
let root_after_insertion = rln.get_root();
let set_index = rng.gen_range(0..NO_OF_LEAVES) as usize;
let indices = vec![set_index];
let zero_leaf: Vec<Fr> = vec![];
rln.atomic_operation(0, zero_leaf, indices).unwrap();
// We get the root of the tree obtained after a deletion
let root_after_deletion = rln.get_root();
assert_ne!(root_after_insertion, root_after_deletion);
// We get the leaf
let received_leaf = rln.get_leaf(set_index).unwrap();
assert_eq!(received_leaf, Fr::from(0));
}
#[test]
// This test is similar to the one in ffi.rs but it uses the RLN object directly
// This test checks if `set_leaves_from` throws an error when the index is out of bounds
fn test_set_leaves_bad_index() {
let tree_depth = DEFAULT_TREE_DEPTH;
// We generate a vector of random leaves
let mut leaves: Vec<Fr> = Vec::new();
let mut rng = thread_rng();
for _ in 0..NO_OF_LEAVES {
leaves.push(Fr::rand(&mut rng));
}
let bad_index = (1 << tree_depth) - rng.gen_range(0..NO_OF_LEAVES) as usize;
// We create a new tree
let mut rln = RLN::new(tree_depth, "").unwrap();
// Get root of empty tree
let root_empty = rln.get_root();
// We add leaves in a batch into the tree
#[allow(unused_must_use)]
rln.set_leaves_from(bad_index, leaves)
.expect_err("Should throw an error");
// We check if number of leaves set is consistent
assert_eq!(rln.leaves_set(), 0);
// Get root of tree after attempted set
let root_after_bad_set = rln.get_root();
assert_eq!(root_empty, root_after_bad_set);
}
#[test]
fn test_get_leaf() {
// We generate a random tree
let tree_depth = 10;
let mut rng = thread_rng();
let mut rln = RLN::new(tree_depth, "").unwrap();
// We generate a random leaf
let leaf = Fr::rand(&mut rng);
// We generate a random index
let index = rng.gen_range(0..(1 << tree_depth));
// We add the leaf to the tree
rln.set_leaf(index, leaf).unwrap();
// We get the leaf
let received_leaf = rln.get_leaf(index).unwrap();
// We ensure that the leaf is the same as the one we added
assert_eq!(received_leaf, leaf);
}
#[test]
fn test_valid_metadata() {
let tree_depth = DEFAULT_TREE_DEPTH;
let mut rln = RLN::new(tree_depth, "").unwrap();
let arbitrary_metadata: &[u8] = b"block_number:200000";
rln.set_metadata(arbitrary_metadata).unwrap();
let received_metadata = rln.get_metadata().unwrap();
assert_eq!(arbitrary_metadata, received_metadata);
}
#[test]
fn test_empty_metadata() {
let tree_depth = DEFAULT_TREE_DEPTH;
let rln = RLN::new(tree_depth, "").unwrap();
let received_metadata = rln.get_metadata().unwrap();
assert_eq!(received_metadata.len(), 0);
}
#[test]
fn test_rln_proof() {
let tree_depth = DEFAULT_TREE_DEPTH;
// We generate a vector of random leaves
let mut leaves: Vec<Fr> = Vec::new();
let mut rng = thread_rng();
for _ in 0..NO_OF_LEAVES {
let id_commitment = Fr::rand(&mut rng);
let rate_commitment = poseidon_hash(&[id_commitment, Fr::from(100)]);
leaves.push(rate_commitment);
}
// We create a new RLN instance
let mut rln = RLN::new(tree_depth, "").unwrap();
// We add leaves in a batch into the tree
rln.init_tree_with_leaves(leaves.clone()).unwrap();
// Generate identity pair
let (identity_secret, id_commitment) = keygen();
// We set as leaf rate_commitment after storing its index
let identity_index = rln.leaves_set();
let user_message_limit = Fr::from(65535);
let rate_commitment = poseidon_hash(&[id_commitment, user_message_limit]);
rln.set_next_leaf(rate_commitment).unwrap();
// We generate a random signal
let mut rng = rand::thread_rng();
let signal: [u8; 32] = rng.gen();
// We generate a random epoch
let epoch = hash_to_field_le(b"test-epoch");
// We generate a random rln_identifier
let rln_identifier = hash_to_field_le(b"test-rln-identifier");
// We generate a external nullifier
let external_nullifier = poseidon_hash(&[epoch, rln_identifier]);
// We choose a message_id satisfy 0 <= message_id < MESSAGE_LIMIT
let message_id = Fr::from(1);
// Hash the signal to get x
let x = hash_to_field_le(&signal);
// Get merkle proof for the identity
let (path_elements, identity_path_index) =
rln.get_merkle_proof(identity_index).unwrap();
// Create RLN witness
let rln_witness = RLNWitnessInput::new(
identity_secret,
user_message_limit,
message_id,
path_elements,
identity_path_index,
x,
external_nullifier,
)
.unwrap();
// Generate proof
let (proof, proof_values) = rln.generate_rln_proof(&rln_witness).unwrap();
// Verify proof
let verified = rln.verify_rln_proof(&proof, &proof_values, &x).is_ok();
assert!(verified);
}
#[test]
fn test_rln_with_witness() {
let tree_depth = DEFAULT_TREE_DEPTH;
// We generate a vector of random leaves
let mut leaves: Vec<Fr> = Vec::new();
let mut rng = thread_rng();
for _ in 0..NO_OF_LEAVES {
leaves.push(Fr::rand(&mut rng));
}
// We create a new RLN instance
let mut rln = RLN::new(tree_depth, "").unwrap();
// We add leaves in a batch into the tree
rln.init_tree_with_leaves(leaves.clone()).unwrap();
// Generate identity pair
let (identity_secret, id_commitment) = keygen();
// We set as leaf rate_commitment after storing its index
let identity_index = rln.leaves_set();
let user_message_limit = Fr::from(100);
let rate_commitment = poseidon_hash(&[id_commitment, user_message_limit]);
rln.set_next_leaf(rate_commitment).unwrap();
// We generate a random signal
let mut rng = rand::thread_rng();
let signal: [u8; 32] = rng.gen();
// We generate a random epoch
let epoch = hash_to_field_le(b"test-epoch");
// We generate a random rln_identifier
let rln_identifier = hash_to_field_le(b"test-rln-identifier");
// We generate a external nullifier
let external_nullifier = poseidon_hash(&[epoch, rln_identifier]);
// We choose a message_id satisfy 0 <= message_id < MESSAGE_LIMIT
let message_id = Fr::from(1);
// Hash the signal to get x
let x = hash_to_field_le(&signal);
// Get merkle proof for the identity
let (path_elements, identity_path_index) =
rln.get_merkle_proof(identity_index).unwrap();
// Create RLN witness
let rln_witness = RLNWitnessInput::new(
identity_secret,
user_message_limit,
message_id,
path_elements,
identity_path_index,
x,
external_nullifier,
)
.unwrap();
// Generate proof using witness
let (proof, proof_values) = rln.generate_rln_proof(&rln_witness).unwrap();
// Verify proof
let verified = rln.verify_rln_proof(&proof, &proof_values, &x).is_ok();
assert!(verified);
}
#[test]
fn proof_verification_with_roots() {
// The first part is similar to test_rln_with_witness
let tree_depth = DEFAULT_TREE_DEPTH;
// We generate a vector of random leaves
let mut leaves: Vec<Fr> = Vec::new();
let mut rng = thread_rng();
for _ in 0..NO_OF_LEAVES {
leaves.push(Fr::rand(&mut rng));
}
// We create a new RLN instance
let mut rln = RLN::new(tree_depth, "").unwrap();
// We add leaves in a batch into the tree
rln.init_tree_with_leaves(leaves.clone()).unwrap();
// Generate identity pair
let (identity_secret, id_commitment) = keygen();
// We set as leaf rate_commitment after storing its index
let identity_index = rln.leaves_set();
let user_message_limit = Fr::from(100);
let rate_commitment = poseidon_hash(&[id_commitment, user_message_limit]);
rln.set_next_leaf(rate_commitment).unwrap();
// We generate a random signal
let mut rng = thread_rng();
let signal: [u8; 32] = rng.gen();
// We generate a random epoch
let epoch = hash_to_field_le(b"test-epoch");
// We generate a random rln_identifier
let rln_identifier = hash_to_field_le(b"test-rln-identifier");
// We generate a external nullifier
let external_nullifier = poseidon_hash(&[epoch, rln_identifier]);
// We choose a message_id satisfy 0 <= message_id < MESSAGE_LIMIT
let message_id = Fr::from(1);
// Hash the signal to get x
let x = hash_to_field_le(&signal);
// Get merkle proof for the identity
let (path_elements, identity_path_index) =
rln.get_merkle_proof(identity_index).unwrap();
// Create RLN witness
let rln_witness = RLNWitnessInput::new(
identity_secret,
user_message_limit,
message_id,
path_elements,
identity_path_index,
x,
external_nullifier,
)
.unwrap();
// Generate proof
let (proof, proof_values) = rln.generate_rln_proof(&rln_witness).unwrap();
// If no roots is provided, proof validation is skipped and if the remaining proof values are valid, the proof will be correctly verified
let empty_roots: Vec<Fr> = vec![];
let verified = rln
.verify_with_roots(&proof, &proof_values, &x, &empty_roots)
.is_ok();
assert!(verified);
// We serialize random roots and check that the proof is not verified since it doesn't contain the correct root
let mut random_roots: Vec<Fr> = Vec::new();
for _ in 0..5 {
random_roots.push(Fr::rand(&mut rng));
}
let verified = rln
.verify_with_roots(&proof, &proof_values, &x, &random_roots)
.is_ok();
assert!(!verified);
// We get the root of the tree
let root = rln.get_root();
// We add the real root and we check if now the proof is verified
random_roots.push(root);
let verified = rln
.verify_with_roots(&proof, &proof_values, &x, &random_roots)
.is_ok();
assert!(verified);
}
#[test]
fn test_recover_id_secret() {
let tree_depth = DEFAULT_TREE_DEPTH;
// We create a new RLN instance
let mut rln = RLN::new(tree_depth, "").unwrap();
// Generate identity pair
let (identity_secret, id_commitment) = keygen();
let user_message_limit = Fr::from(100);
let rate_commitment = poseidon_hash(&[id_commitment, user_message_limit]);
// We set as leaf rate_commitment, its index would be equal to 0 since tree is empty
let identity_index = rln.leaves_set();
rln.set_next_leaf(rate_commitment).unwrap();
// We generate two proofs using same epoch but different signals.
// We generate two random signals
let mut rng = rand::thread_rng();
let signal1: [u8; 32] = rng.gen();
let signal2: [u8; 32] = rng.gen();
// We generate a random epoch
let epoch = hash_to_field_le(b"test-epoch");
// We generate a random rln_identifier
let rln_identifier = hash_to_field_le(b"test-rln-identifier");
// We generate a external nullifier
let external_nullifier = poseidon_hash(&[epoch, rln_identifier]);
// We choose a message_id satisfy 0 <= message_id < MESSAGE_LIMIT
let message_id = Fr::from(1);
// Hash the signals to get x values
let x1 = hash_to_field_le(&signal1);
let x2 = hash_to_field_le(&signal2);
// Get merkle proof for the identity
let (path_elements, identity_path_index) =
rln.get_merkle_proof(identity_index).unwrap();
// Create RLN witnesses for both signals
let rln_witness1 = RLNWitnessInput::new(
identity_secret.clone(),
user_message_limit,
message_id,
path_elements.clone(),
identity_path_index.clone(),
x1,
external_nullifier,
)
.unwrap();
let rln_witness2 = RLNWitnessInput::new(
identity_secret.clone(),
user_message_limit,
message_id,
path_elements.clone(),
identity_path_index.clone(),
x2,
external_nullifier,
)
.unwrap();
// Generate the first proof
let (_proof1, proof_values_1) = rln.generate_rln_proof(&rln_witness1).unwrap();
// Generate the second proof
let (_proof2, proof_values_2) = rln.generate_rln_proof(&rln_witness2).unwrap();
// Recover identity secret from two proof values
let recovered_identity_secret =
recover_id_secret(&proof_values_1, &proof_values_2).unwrap();
// We check if the recovered identity secret corresponds to the original one
assert_eq!(*recovered_identity_secret, *identity_secret);
// We now test that computing identity_secret is unsuccessful if shares computed from two different identity secret but within same epoch are passed
// We generate a new identity pair
let (identity_secret_new, id_commitment_new) = keygen();
let rate_commitment_new = poseidon_hash(&[id_commitment_new, user_message_limit]);
// We add it to the tree
let identity_index_new = rln.leaves_set();
rln.set_next_leaf(rate_commitment_new).unwrap();
// We generate a random signal
let signal3: [u8; 32] = rng.gen();
let x3 = hash_to_field_le(&signal3);
// Get merkle proof for the new identity
let (path_elements_new, identity_path_index_new) =
rln.get_merkle_proof(identity_index_new).unwrap();
// We prepare proof input. Note that epoch is the same as before
let rln_witness3 = RLNWitnessInput::new(
identity_secret.clone(),
user_message_limit,
message_id,
path_elements_new,
identity_path_index_new,
x3,
external_nullifier,
)
.unwrap();
// We generate the proof
let (_proof3, proof_values_3) = rln.generate_rln_proof(&rln_witness3).unwrap();
// We attempt to recover the secret using share1 (coming from identity_secret) and share3 (coming from identity_secret_new)
let recovered_identity_secret_new =
recover_id_secret(&proof_values_1, &proof_values_3).unwrap();
// ensure that the recovered secret does not match with either of the
// used secrets in proof generation
assert_ne!(*recovered_identity_secret_new, *identity_secret_new);
}
#[test]
fn test_tree_config_input_trait() {
let empty_json_input = "";
let rln_with_empty_json_config = RLN::new(DEFAULT_TREE_DEPTH, empty_json_input);
assert!(rln_with_empty_json_config.is_ok());
let json_config = json!({
"tree_config": {
"path": "pmtree-123456",
"temporary": false,
"cache_capacity": 1073741824,
"flush_every_ms": 500,
"mode": "HighThroughput",
"use_compression": false
}
});
let json_input = json_config.to_string();
let rln_with_json_config = RLN::new(DEFAULT_TREE_DEPTH, json_input.as_str());
assert!(rln_with_json_config.is_ok());
let default_pmtree_config = PmtreeConfig::default();
let rln_with_default_tree_config = RLN::new(DEFAULT_TREE_DEPTH, default_pmtree_config);
assert!(rln_with_default_tree_config.is_ok());
let custom_pmtree_config = PmtreeConfig::builder()
.temporary(true)
.use_compression(false)
.build();
let rln_with_custom_tree_config =
RLN::new(DEFAULT_TREE_DEPTH, custom_pmtree_config.unwrap());
assert!(rln_with_custom_tree_config.is_ok());
}
}
#[cfg(feature = "stateless")]
mod stateless_test {
use ark_std::{rand::thread_rng, UniformRand};
use rand::Rng;
use rln::{
circuit::Fr,
hashers::{hash_to_field_le, poseidon_hash, PoseidonHash},
protocol::*,
public::RLN,
};
use utils::{OptimalMerkleTree, ZerokitMerkleProof, ZerokitMerkleTree};
use super::DEFAULT_TREE_DEPTH;
type ConfigOf<T> = <T as ZerokitMerkleTree>::Config;
#[test]
fn test_stateless_rln_proof() {
// We create a new RLN instance
let rln = RLN::new().unwrap();
let default_leaf = Fr::from(0);
let mut tree: OptimalMerkleTree<PoseidonHash> = OptimalMerkleTree::new(
DEFAULT_TREE_DEPTH,
default_leaf,
ConfigOf::<OptimalMerkleTree<PoseidonHash>>::default(),
)
.unwrap();
// Generate identity pair
let (identity_secret, id_commitment) = keygen();
// We set as leaf rate_commitment after storing its index
let identity_index = tree.leaves_set();
let user_message_limit = Fr::from(100);
let rate_commitment = poseidon_hash(&[id_commitment, user_message_limit]);
tree.update_next(rate_commitment).unwrap();
// We generate a random signal
let mut rng = thread_rng();
let signal: [u8; 32] = rng.gen();
// We generate a random epoch
let epoch = hash_to_field_le(b"test-epoch");
// We generate a random rln_identifier
let rln_identifier = hash_to_field_le(b"test-rln-identifier");
let external_nullifier = poseidon_hash(&[epoch, rln_identifier]);
// Hash the signal to get x
let x = hash_to_field_le(&signal);
let merkle_proof = tree.proof(identity_index).expect("proof should exist");
let message_id = Fr::from(1);
let rln_witness = RLNWitnessInput::new(
identity_secret,
user_message_limit,
message_id,
merkle_proof.get_path_elements(),
merkle_proof.get_path_index(),
x,
external_nullifier,
)
.unwrap();
// Generate proof
let (proof, proof_values) = rln.generate_rln_proof(&rln_witness).unwrap();
// If no roots is provided, proof validation is skipped and if the remaining proof values are valid, the proof will be correctly verified
let empty_roots: Vec<Fr> = vec![];
let verified = rln
.verify_with_roots(&proof, &proof_values, &x, &empty_roots)
.is_ok();
assert!(verified);
// We check that the proof is not verified with random roots
let mut random_roots: Vec<Fr> = Vec::new();
for _ in 0..5 {
random_roots.push(Fr::rand(&mut rng));
}
let verified = rln
.verify_with_roots(&proof, &proof_values, &x, &random_roots)
.is_ok();
assert!(!verified);
// 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
random_roots.push(root);
let verified = rln
.verify_with_roots(&proof, &proof_values, &x, &random_roots)
.is_ok();
assert!(verified);
}
#[test]
fn test_stateless_recover_id_secret() {
// We create a new RLN instance
let rln = RLN::new().unwrap();
let default_leaf = Fr::from(0);
let mut tree: OptimalMerkleTree<PoseidonHash> = OptimalMerkleTree::new(
DEFAULT_TREE_DEPTH,
default_leaf,
ConfigOf::<OptimalMerkleTree<PoseidonHash>>::default(),
)
.unwrap();
// Generate identity pair
let (identity_secret, id_commitment) = keygen();
let user_message_limit = Fr::from(100);
let rate_commitment = poseidon_hash(&[id_commitment, user_message_limit]);
tree.update_next(rate_commitment).unwrap();
// We generate a random epoch
let epoch = hash_to_field_le(b"test-epoch");
// We generate a random rln_identifier
let rln_identifier = hash_to_field_le(b"test-rln-identifier");
let external_nullifier = poseidon_hash(&[epoch, rln_identifier]);
// We generate a random signal
let mut rng = thread_rng();
let signal1: [u8; 32] = rng.gen();
let x1 = hash_to_field_le(&signal1);
let signal2: [u8; 32] = rng.gen();
let x2 = hash_to_field_le(&signal2);
let identity_index = tree.leaves_set();
let merkle_proof = tree.proof(identity_index).expect("proof should exist");
let message_id = Fr::from(1);
let rln_witness1 = RLNWitnessInput::new(
identity_secret.clone(),
user_message_limit,
message_id,
merkle_proof.get_path_elements(),
merkle_proof.get_path_index(),
x1,
external_nullifier,
)
.unwrap();
let rln_witness2 = RLNWitnessInput::new(
identity_secret.clone(),
user_message_limit,
message_id,
merkle_proof.get_path_elements(),
merkle_proof.get_path_index(),
x2,
external_nullifier,
)
.unwrap();
// Generate proofs
let (_proof1, proof_values_1) = rln.generate_rln_proof(&rln_witness1).unwrap();
let (_proof2, proof_values_2) = rln.generate_rln_proof(&rln_witness2).unwrap();
// Recover identity secret from two proof values
let recovered_identity_secret =
recover_id_secret(&proof_values_1, &proof_values_2).unwrap();
// We check if the recovered identity secret corresponds to the original one
assert_eq!(*recovered_identity_secret, *identity_secret);
// We now test that computing identity_secret is unsuccessful if shares computed from two different identity secret but within same epoch are passed
// We generate a new identity pair
let (identity_secret_new, id_commitment_new) = keygen();
let rate_commitment_new = poseidon_hash(&[id_commitment_new, user_message_limit]);
tree.update_next(rate_commitment_new).unwrap();
let signal3: [u8; 32] = rng.gen();
let x3 = hash_to_field_le(&signal3);
let identity_index_new = tree.leaves_set();
let merkle_proof_new = tree.proof(identity_index_new).expect("proof should exist");
let rln_witness3 = RLNWitnessInput::new(
identity_secret_new.clone(),
user_message_limit,
message_id,
merkle_proof_new.get_path_elements(),
merkle_proof_new.get_path_index(),
x3,
external_nullifier,
)
.unwrap();
// Generate proof with different identity
let (_proof3, proof_values_3) = rln.generate_rln_proof(&rln_witness3).unwrap();
// Attempt to recover secret from mismatched shares
let recovered_identity_secret_new =
recover_id_secret(&proof_values_1, &proof_values_3).unwrap();
// ensure that the recovered secret does not match with either of the
// used secrets in proof generation
assert_ne!(*recovered_identity_secret_new, *identity_secret_new);
}
}
}