mirror of
https://github.com/vacp2p/zerokit.git
synced 2026-01-07 21:04:02 -05:00
# 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.
1128 lines
41 KiB
Rust
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);
|
|
}
|
|
}
|
|
}
|