fix(rln-cli): improve configuration handling with a single format, implement relay and stateless example

fix(rln-cli): handle missing or empty config file; improve readability of input/output; remove duplicate config field

fix(rln-cli): back to using path for input data in RLN-CLI, add real-world spam prevention example

fix(rln-cli): fix Readme.md format, remove TREE_HEIGHT constant and fix recover_id_secret part for relay example

fix(rln-cli): implement stateless RLN example

fix(rln-cli): add missing Cargo.lock update

fix(rln-cli): update RLN-CLI version to 0.7.0
This commit is contained in:
vinhtc27 2025-02-14 03:29:21 +07:00
parent 8a3e33be41
commit 5e41de256d
No known key found for this signature in database
GPG Key ID: 9EB7190DAB18D951
11 changed files with 853 additions and 103 deletions

1
.gitignore vendored
View File

@ -3,6 +3,7 @@
*.log
tmp/
rln/pmtree_db
rln-cli/database
# Generated by Cargo
# will have compiled files and executables

17
Cargo.lock generated
View File

@ -704,9 +704,9 @@ dependencies = [
[[package]]
name = "clap"
version = "4.5.27"
version = "4.5.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "769b0145982b4b48713e01ec42d61614425f27b7058bda7180a3a41f30104796"
checksum = "8acebd8ad879283633b343856142139f2da2317c96b05b4dd6181c61e2480184"
dependencies = [
"clap_builder",
"clap_derive",
@ -714,9 +714,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.5.27"
version = "4.5.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b26884eb4b57140e4d2d93652abfa49498b938b3c9179f9fc487b0acc3edad7"
checksum = "f6ba32cbda51c7e1dfd49acc1457ba1a7dec5b64fe360e828acb13ca8dc9c2f9"
dependencies = [
"anstream",
"anstyle",
@ -726,9 +726,9 @@ dependencies = [
[[package]]
name = "clap_derive"
version = "4.5.24"
version = "4.5.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "54b755194d6389280185988721fffba69495eed5ee9feeee9a599b53db80318c"
checksum = "bf4ced95c6f4a675af3da73304b9ac4ed991640c36374e4b46795c49e17cf1ed"
dependencies = [
"heck 0.5.0",
"proc-macro2",
@ -3441,14 +3441,15 @@ dependencies = [
[[package]]
name = "rln-cli"
version = "0.3.0"
version = "0.7.0"
dependencies = [
"clap 4.5.27",
"clap 4.5.29",
"clap_derive",
"color-eyre",
"rln",
"serde",
"serde_json",
"zerokit_utils",
]
[[package]]

View File

@ -1,16 +1,26 @@
[package]
name = "rln-cli"
version = "0.3.0"
version = "0.7.0"
edition = "2021"
[[example]]
name = "relay"
path = "src/examples/relay.rs"
[[example]]
name = "stateless"
path = "src/examples/stateless.rs"
required-features = ["stateless"]
[dependencies]
rln = { path = "../rln", default-features = true, features = ["arkzkey"] }
clap = { version = "4.5.0", features = ["cargo", "derive", "env"]}
clap_derive = { version = "4.5.0" }
rln = { path = "../rln", default-features = true, features = ["pmtree-ft"] }
zerokit_utils = { path = "../utils" }
clap = { version = "4.5.29", features = ["cargo", "derive", "env"] }
clap_derive = { version = "4.5.28" }
color-eyre = "0.6.2"
# serialization
serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0.138"
serde = { version = "1.0.217", features = ["derive"] }
[features]
arkzkey = []
arkzkey = ["rln/arkzkey"]
stateless = ["rln/stateless"]

166
rln-cli/README.md Normal file
View File

@ -0,0 +1,166 @@
# Zerokit RLN-CLI
The Zerokit RLN-CLI provides a command-line interface for interacting with the public API of the [Zerokit RLN Module](../rln/README.md).
It also contain:
+ [Relay Example](#relay-example) to demonstrate the use of the RLN module for spam prevention.
+ [Stateless Example](#stateless-example) to demonstrate the use of the RLN module for stateless features.
## Configuration
The CLI can be configured using a JSON configuration file (see the [example](example.config.json)).
You can specify the configuration file path using the `RLN_CONFIG_PATH` environment variable:
```bash
export RLN_CONFIG_PATH=example.config.json
```
Alternatively, you can provide the configuration file path as an argument for each command:
```bash
RLN_CONFIG_PATH=example.config.json cargo run -- <SUBCOMMAND> [OPTIONS]
```
If the configuration file is empty, default settings will be used, but the tree data folder will be temporary and not saved to the preconfigured path.
We recommend using the example config, as all commands (except `new` and `create-with-params`) require an initialized RLN instance.
## CLI Commands
### Instance Management
To initialize a new RLN instance:
```bash
cargo run new --tree-height <HEIGHT>
```
To initialize an RLN instance with custom parameters:
```bash
cargo run new-with-params --resources-path <PATH> --tree-height <HEIGHT>
```
To update the Merkle tree height:
```bash
cargo run set-tree --tree-height <HEIGHT>
```
### Leaf Operations
To set a single leaf:
```bash
cargo run set-leaf --index <INDEX> --input <INPUT_PATH>
```
To set multiple leaves:
```bash
cargo run set-multiple-leaves --index <START_INDEX> --input <INPUT_PATH>
```
To reset multiple leaves:
```bash
cargo run reset-multiple-leaves --input <INPUT_PATH>
```
To set the next available leaf:
```bash
cargo run set-next-leaf --input <INPUT_PATH>
```
To delete a specific leaf:
```bash
cargo run delete-leaf --index <INDEX>
```
### Proof Operations
To generate a proof:
```bash
cargo run prove --input <INPUT_PATH>
```
To generate an RLN proof:
```bash
cargo run generate-proof --input <INPUT_PATH>
```
To verify a proof:
```bash
cargo run verify --input <PROOF_PATH>
```
To verify a proof with multiple Merkle roots:
```bash
cargo run verify-with-roots --input <INPUT_PATH> --roots <ROOTS_PATH>
```
### Tree Information
To retrieve the current Merkle root:
```bash
cargo run get-root
```
To obtain a Merkle proof for a specific index:
```bash
cargo run get-proof --index <INDEX>
```
## Feature Flags
The CLI supports optional features. To enable the **arkzkey** feature, run:
```bash
cargo run --features arkzkey -- <SUBCOMMAND> [OPTIONS]
```
For more details, refer to the [Zerokit RLN Module](../rln/README.md) documentation.
## Relay Example
The following [Example](src/examples/relay.rs) demonstrates how RLN enables spam prevention in anonymous environments for multple users.
You can run the example using the following command:
```bash
cargo run --example relay
```
or with the **arkzkey** feature flag:
```bash
cargo run --example relay --features arkzkey
```
You can also change **MESSAGE_LIMIT** constant in the [relay.rs](src/examples/relay.rs) file to see how the RLN instance behaves with different parameters.
## Stateless Example
The following [Example](src/examples/stateless.rs) demonstrates how RLN can be used for stateless features by creating the Merkle tree outside of RLN instance.
You can run the example using the following command:
```bash
cargo run --example stateless --features stateless
```
or with the **arkzkey** feature flag:
```bash
cargo run --example stateless --features stateless,arkzkey
```

View File

@ -0,0 +1,11 @@
{
"tree_config": {
"path": "database",
"temporary": false,
"cache_capacity": 150000,
"flush_every_ms": 12000,
"mode": "HighThroughput",
"use_compression": false
},
"tree_height": 20
}

View File

@ -1,49 +1,51 @@
use std::path::PathBuf;
use clap::Subcommand;
use rln::circuit::TEST_TREE_HEIGHT;
#[derive(Subcommand)]
pub(crate) enum Commands {
New {
#[arg(short, long, default_value_t = TEST_TREE_HEIGHT)]
tree_height: usize,
/// Sets a custom config file
#[arg(short, long)]
config: PathBuf,
},
NewWithParams {
#[arg(short, long, default_value_t = TEST_TREE_HEIGHT)]
tree_height: usize,
/// Sets a custom config file
#[arg(short, long)]
config: PathBuf,
#[arg(short, long)]
tree_config_input: PathBuf,
#[arg(short, long, default_value = "../rln/resources/tree_height_20")]
resources_path: PathBuf,
},
SetTree {
#[arg(short, long, default_value_t = TEST_TREE_HEIGHT)]
tree_height: usize,
},
SetLeaf {
#[arg(short, long)]
index: usize,
#[arg(short, long)]
file: PathBuf,
input: PathBuf,
},
SetMultipleLeaves {
#[arg(short, long)]
index: usize,
#[arg(short, long)]
file: PathBuf,
input: PathBuf,
},
ResetMultipleLeaves {
#[arg(short, long)]
file: PathBuf,
input: PathBuf,
},
SetNextLeaf {
#[arg(short, long)]
file: PathBuf,
input: PathBuf,
},
DeleteLeaf {
#[arg(short, long)]
index: usize,
},
GetRoot,
GetProof {
#[arg(short, long)]
index: usize,
},
Prove {
@ -52,7 +54,7 @@ pub(crate) enum Commands {
},
Verify {
#[arg(short, long)]
file: PathBuf,
input: PathBuf,
},
GenerateProof {
#[arg(short, long)]

View File

@ -1,8 +1,10 @@
use color_eyre::Result;
use serde::{Deserialize, Serialize};
use std::{fs::File, io::Read, path::PathBuf};
pub const RLN_STATE_PATH: &str = "RLN_STATE_PATH";
use color_eyre::Result;
use serde::{Deserialize, Serialize};
use serde_json::Value;
pub const RLN_CONFIG_PATH: &str = "RLN_CONFIG_PATH";
#[derive(Default, Serialize, Deserialize)]
pub(crate) struct Config {
@ -11,19 +13,26 @@ pub(crate) struct Config {
#[derive(Default, Serialize, Deserialize)]
pub(crate) struct InnerConfig {
pub file: PathBuf,
pub tree_height: usize,
pub tree_config: Value,
}
impl Config {
pub(crate) fn load_config() -> Result<Config> {
let path = PathBuf::from(std::env::var(RLN_STATE_PATH)?);
match std::env::var(RLN_CONFIG_PATH) {
Ok(env) => {
let path = PathBuf::from(env);
let mut file = File::open(path)?;
let mut contents = String::new();
file.read_to_string(&mut contents)?;
let inner: InnerConfig = serde_json::from_str(&contents)?;
Ok(Config { inner: Some(inner) })
}
Err(_) => Ok(Config::default()),
}
}
let mut file = File::open(path)?;
let mut contents = String::new();
file.read_to_string(&mut contents)?;
let state: Config = serde_json::from_str(&contents)?;
Ok(state)
pub(crate) fn as_bytes(&self) -> Vec<u8> {
serde_json::to_string(&self.inner).unwrap().into_bytes()
}
}

View File

@ -0,0 +1,298 @@
use std::{
collections::HashMap,
io::{stdin, stdout, Cursor, Write},
};
use clap::{Parser, Subcommand};
use color_eyre::{eyre::eyre, Result};
use rln::{
circuit::{Fr, TEST_TREE_HEIGHT},
hashers::{hash_to_field, poseidon_hash},
protocol::{deserialize_field_element, keygen},
public::RLN,
utils::{bytes_le_to_fr, fr_to_bytes_le, generate_input_buffer, normalize_usize},
};
const MESSAGE_LIMIT: u32 = 1;
#[derive(Parser)]
#[command(author, version, about, long_about = None)]
struct Cli {
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
List,
Register,
Send {
#[arg(short, long)]
user_index: usize,
#[arg(short, long)]
message_id: u32,
#[arg(short, long)]
signal: String,
},
Clear,
Exit,
}
#[derive(Debug, Clone)]
struct Identity {
identity_secret_hash: Fr,
id_commitment: Fr,
}
impl Identity {
fn new() -> Self {
let (identity_secret_hash, id_commitment) = keygen();
Identity {
identity_secret_hash,
id_commitment,
}
}
}
struct RLNSystem {
rln: RLN,
// Mimic smart contract storage for used nullifiers for each epoch
used_nullifiers: HashMap<[u8; 32], Vec<u8>>,
// Mimic local storage for user identities of each user index
local_identities: HashMap<usize, Identity>,
}
impl RLNSystem {
fn new() -> Result<Self> {
let rln = RLN::new(TEST_TREE_HEIGHT, generate_input_buffer())?;
Ok(RLNSystem {
rln,
used_nullifiers: HashMap::new(),
local_identities: HashMap::new(),
})
}
fn list_users(&self) {
if self.local_identities.is_empty() {
println!("No users registered yet.");
return;
}
println!("Registered users:");
for (index, identity) in &self.local_identities {
println!("User Index: {index}");
println!("+ Identity Secret Hash: {}", identity.identity_secret_hash);
println!("+ Identity Commitment: {}", identity.id_commitment);
println!();
}
}
fn register_user(&mut self) -> Result<usize> {
let index = self.rln.leaves_set();
let identity = Identity::new();
let rate_commitment = poseidon_hash(&[identity.id_commitment, Fr::from(MESSAGE_LIMIT)]);
let mut buffer = Cursor::new(fr_to_bytes_le(&rate_commitment));
match self.rln.set_next_leaf(&mut buffer) {
Ok(_) => {
println!("Registered User Index: {index}");
println!("+ Identity secret hash: {}", identity.identity_secret_hash);
println!("+ Identity commitment: {},", identity.id_commitment);
self.local_identities.insert(index, identity);
}
Err(_) => {
println!("Maximum user limit reached: 2^{TEST_TREE_HEIGHT}");
}
};
Ok(index)
}
fn generate_proof(
&mut self,
user_index: usize,
message_id: u32,
signal: &str,
external_nullifier: Fr,
) -> Result<Vec<u8>> {
let identity = match self.local_identities.get(&user_index) {
Some(identity) => identity,
None => return Err(eyre!("user index {user_index} not found")),
};
let mut input_buffer = Cursor::new(Vec::new());
self.rln.get_leaf(user_index, &mut input_buffer)?;
let stored_rate_commitment = deserialize_field_element(input_buffer.into_inner());
let expected_rate_commitment =
poseidon_hash(&[identity.id_commitment, Fr::from(MESSAGE_LIMIT)]);
if stored_rate_commitment != expected_rate_commitment {
return Err(eyre!("user mismatch in merkle tree"));
}
let mut serialized = Vec::new();
serialized.append(&mut fr_to_bytes_le(&identity.identity_secret_hash));
serialized.append(&mut normalize_usize(user_index));
serialized.append(&mut fr_to_bytes_le(&Fr::from(MESSAGE_LIMIT)));
serialized.append(&mut fr_to_bytes_le(&Fr::from(message_id)));
serialized.append(&mut fr_to_bytes_le(&external_nullifier));
serialized.append(&mut normalize_usize(signal.len()));
serialized.append(&mut signal.as_bytes().to_vec());
let mut input_buffer = Cursor::new(serialized);
let mut output_buffer = Cursor::new(Vec::new());
self.rln
.generate_rln_proof(&mut input_buffer, &mut output_buffer)?;
println!("Proof generated successfully:");
println!("+ User Index: {user_index}");
println!("+ Message ID: {message_id}");
println!("+ Signal: {signal}");
Ok(output_buffer.into_inner())
}
fn verify_proof(&mut self, proof_data: Vec<u8>, signal: &str) -> Result<()> {
let mut proof_with_signal = proof_data.clone();
proof_with_signal.append(&mut normalize_usize(signal.len()));
proof_with_signal.append(&mut signal.as_bytes().to_vec());
let mut input_buffer = Cursor::new(proof_with_signal);
match self.rln.verify_rln_proof(&mut input_buffer) {
Ok(true) => {
let nullifier = &proof_data[256..288];
let nullifier_key: [u8; 32] = nullifier.try_into()?;
if let Some(previous_proof) = self.used_nullifiers.get(&nullifier_key) {
self.handle_duplicate_message_id(previous_proof.clone(), proof_data)?;
return Ok(());
}
self.used_nullifiers.insert(nullifier_key, proof_data);
println!("Message verified and accepted");
}
Ok(false) => {
println!("Verification failed: message_id must be unique within the epoch and satisfy 0 <= message_id < MESSAGE_LIMIT: {MESSAGE_LIMIT}");
}
Err(err) => return Err(err),
}
Ok(())
}
fn handle_duplicate_message_id(
&mut self,
previous_proof: Vec<u8>,
current_proof: Vec<u8>,
) -> Result<()> {
let mut proof1 = Cursor::new(previous_proof);
let mut proof2 = Cursor::new(current_proof);
let mut output = Cursor::new(Vec::new());
match self
.rln
.recover_id_secret(&mut proof1, &mut proof2, &mut output)
{
Ok(_) => {
let output_data = output.into_inner();
let (leaked_identity_secret_hash, _) = bytes_le_to_fr(&output_data);
if let Some((user_index, identity)) = self
.local_identities
.iter()
.find(|(_, identity)| {
identity.identity_secret_hash == leaked_identity_secret_hash
})
.map(|(index, identity)| (*index, identity))
{
let real_identity_secret_hash = identity.identity_secret_hash;
if leaked_identity_secret_hash != real_identity_secret_hash {
Err(eyre!("identity secret hash mismatch {leaked_identity_secret_hash} != {real_identity_secret_hash}"))
} else {
println!("DUPLICATE message ID detected! Reveal identity secret hash: {leaked_identity_secret_hash}");
self.local_identities.remove(&user_index);
self.rln.delete_leaf(user_index)?;
println!("User index {user_index} has been SLASHED");
Ok(())
}
} else {
Err(eyre!(
"user identity secret hash {leaked_identity_secret_hash} not found"
))
}
}
Err(err) => Err(eyre!("Failed to recover identity secret: {err}")),
}
}
}
fn main() -> Result<()> {
let mut rln_system = RLNSystem::new()?;
let rln_epoch = hash_to_field(b"epoch");
let rln_identifier = hash_to_field(b"rln-identifier");
let external_nullifier = poseidon_hash(&[rln_epoch, rln_identifier]);
println!("RLN Relay Example:");
println!("Message Limit: {MESSAGE_LIMIT}");
println!("----------------------------------");
println!();
show_commands();
loop {
print!("\n> ");
stdout().flush()?;
let mut input = String::new();
stdin().read_line(&mut input)?;
let trimmed = input.trim();
let args = std::iter::once("rln").chain(trimmed.split_whitespace());
match Cli::try_parse_from(args) {
Ok(cli) => match cli.command {
Commands::List => {
rln_system.list_users();
}
Commands::Register => {
rln_system.register_user()?;
}
Commands::Send {
user_index,
message_id,
signal,
} => {
match rln_system.generate_proof(
user_index,
message_id,
&signal,
external_nullifier,
) {
Ok(proof) => {
if let Err(err) = rln_system.verify_proof(proof, &signal) {
println!("Verification error: {err}");
};
}
Err(err) => {
println!("Proof generation error: {err}");
}
}
}
Commands::Clear => {
print!("\x1B[2J\x1B[1;1H");
show_commands();
}
Commands::Exit => {
break;
}
},
Err(err) => {
eprintln!("Command error: {err}");
}
}
}
Ok(())
}
fn show_commands() {
println!("Available commands:");
println!(" list - List registered users");
println!(" register - Register a new user index");
println!(" send -u <index> -m <message_id> -s <signal> - Send a message with proof");
println!(" clear - Clear the screen");
println!(" exit - Exit the program");
}

View File

@ -0,0 +1,214 @@
use std::io::Cursor;
use rln::{
circuit::{Fr, TEST_TREE_HEIGHT},
hashers::{hash_to_field, poseidon_hash},
poseidon_tree::PoseidonTree,
protocol::{keygen, rln_witness_from_values, serialize_witness},
public::RLN,
utils::{bytes_le_to_fr, fr_to_bytes_le, normalize_usize},
};
use zerokit_utils::ZerokitMerkleTree;
type ConfigOf<T> = <T as ZerokitMerkleTree>::Config;
fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("RLN Stateless Example");
// Create RLN instance
#[cfg(feature = "stateless")]
let mut rln = RLN::new()?;
println!("1. Created stateless RLN instance");
// Create Merkle tree outside of RLN instance
let default_leaf = Fr::from(0);
let mut tree = PoseidonTree::new(
TEST_TREE_HEIGHT,
default_leaf,
ConfigOf::<PoseidonTree>::default(),
)?;
println!("2. Created Merkle tree outside of RLN instance with height: {TEST_TREE_HEIGHT}");
// Generate user identity with user message limit
let (identity_secret_hash, id_commitment) = keygen();
println!("3. Generated user identity with secret hash: {identity_secret_hash}");
let message_limit: u32 = 1;
let user_message_limit: Fr = Fr::from(message_limit);
let rate_commitment = poseidon_hash(&[id_commitment, user_message_limit]);
println!("4. Generated rate commitment with message limit: {message_limit}");
// Add user to tree
let identity_index = tree.leaves_set();
tree.update_next(rate_commitment)?;
let merkle_proof = tree.proof(identity_index)?;
println!("5. Added user to tree with identity index: {identity_index}");
// Get tree root
let tree_root = tree.root();
println!("6. Tree root: {tree_root}");
// Setup nullifier components
let epoch = hash_to_field(b"epoch");
let rln_identifier = hash_to_field(b"rln-identifier");
let external_nullifier = poseidon_hash(&[epoch, rln_identifier]);
// Prepare root buffer - Handle correctly for stateless verification
let root_serialized = fr_to_bytes_le(&tree_root);
let mut root_buffer = Cursor::new(root_serialized);
println!("7. Set up external nullifier and root buffer");
// Generate first message signal
let mut signal_1 = b"signal_1".to_vec();
println!("8. Created first message signal");
// Create message_id for first message
let message_id_1 = Fr::from(0);
let x_1 = hash_to_field(&signal_1);
// Create witness for first message
let rln_witness_1 = rln_witness_from_values(
identity_secret_hash,
&merkle_proof,
x_1,
external_nullifier,
user_message_limit,
message_id_1,
)?;
// Serialize witness
let serialized_1 = serialize_witness(&rln_witness_1)?;
let mut input_buffer_1 = Cursor::new(serialized_1);
// Output data: [ proof<128> | root<32> | external_nullifier<32> | x<32> | y<32> | nullifier<32>]
let mut output_buffer_1 = Cursor::new(Vec::new());
// Generate proof for first message with witness in stateless mode
rln.generate_rln_proof_with_witness(&mut input_buffer_1, &mut output_buffer_1)?;
println!("9. Generated proof for first message");
// Input proof data: [ proof<128> | root<32> | external_nullifier<32> | x<32> | y<32> | nullifier<32> | signal_len<8> | signal<var>]
let mut proof_data_1 = output_buffer_1.into_inner();
proof_data_1.append(&mut normalize_usize(signal_1.len()));
proof_data_1.append(&mut signal_1);
// Verify first proof with stateless verification
let mut input_buffer = Cursor::new(proof_data_1.clone());
let verified = rln.verify_with_roots(&mut input_buffer, &mut root_buffer)?;
println!(
"10. Stateless verification of first message: {}",
if verified { "VALID" } else { "INVALID" }
);
// Generate second message signal (now the second in sequence)
let mut signal_2 = b"signal_2".to_vec();
println!("11. Created second message signal");
// Create message_id for second message (now second in sequence)
// Using a valid message_id=1 which is at the limit of our message_limit=1
let message_id_2 = Fr::from(1);
let x_2 = hash_to_field(&signal_2);
// Create witness for second message
let rln_witness_2 = rln_witness_from_values(
identity_secret_hash,
&merkle_proof,
x_2,
external_nullifier,
user_message_limit,
message_id_2,
)?;
// Serialize witness
let serialized_2 = serialize_witness(&rln_witness_2)?;
let mut input_buffer_2 = Cursor::new(serialized_2);
// Output data: [ proof<128> | root<32> | external_nullifier<32> | x<32> | y<32> | nullifier<32>]
let mut output_buffer_2 = Cursor::new(Vec::new());
// Generate proof for second message with witness in stateless mode
rln.generate_rln_proof_with_witness(&mut input_buffer_2, &mut output_buffer_2)?;
println!("12. Generated proof for second message with message_id=1");
// Input proof data: [ proof<128> | root<32> | external_nullifier<32> | x<32> | y<32> | nullifier<32> | signal_len<8> | signal<var>]
let mut proof_data_2 = output_buffer_2.into_inner();
proof_data_2.append(&mut normalize_usize(signal_2.len()));
proof_data_2.append(&mut signal_2);
// Verify second proof with stateless verification
let mut input_buffer = Cursor::new(proof_data_2.clone());
let verified = rln.verify_with_roots(&mut input_buffer, &mut root_buffer)?;
println!(
"13. Stateless verification of second message: {}",
if verified {
"VALID (message_id=1 is within our message_limit=1)"
} else {
"INVALID (message_id must satisfy 0 <= message_id < MESSAGE_LIMIT)"
}
);
// Generate third message signal (now third in sequence)
let mut signal_3 = b"signal_3".to_vec();
println!("14. Created third message signal (duplicate message_id demonstration)");
// Duplicate message_id! Creating a double-post situation for third message
let message_id_3 = Fr::from(0); // Reusing message_id=0 which was already used for the first message
let x_3 = hash_to_field(&signal_3);
// Create witness for third message
let rln_witness_3 = rln_witness_from_values(
identity_secret_hash,
&merkle_proof,
x_3,
external_nullifier,
user_message_limit,
message_id_3,
)?;
// Serialize witness
let serialized_3 = serialize_witness(&rln_witness_3)?;
let mut input_buffer_3 = Cursor::new(serialized_3);
// Output data: [ proof<128> | root<32> | external_nullifier<32> | x<32> | y<32> | nullifier<32>]
let mut output_buffer_3 = Cursor::new(Vec::new());
// Generate proof for third message with witness in stateless mode
rln.generate_rln_proof_with_witness(&mut input_buffer_3, &mut output_buffer_3)?;
println!("15. Generated proof for third message (with duplicate message_id=0)");
// Input proof data: [ proof<128> | root<32> | external_nullifier<32> | x<32> | y<32> | nullifier<32> | signal_len<8> | signal<var>]
let mut proof_data_3 = output_buffer_3.into_inner();
proof_data_3.append(&mut normalize_usize(signal_3.len()));
proof_data_3.append(&mut signal_3);
// Verify third proof with stateless verification
let mut input_buffer = Cursor::new(proof_data_3.clone());
let verified = rln.verify_with_roots(&mut input_buffer, &mut root_buffer)?;
println!(
"16. Stateless verification of third message: {}",
if verified { "VALID" } else { "INVALID" }
);
// Recover identity from double-posting
let mut input_1 = Cursor::new(proof_data_1);
let mut input_3 = Cursor::new(proof_data_3);
let mut output = Cursor::new(Vec::new());
rln.recover_id_secret(&mut input_1, &mut input_3, &mut output)?;
let recovered_data = output.into_inner();
let (recovered_identity, _) = bytes_le_to_fr(&recovered_data);
print!("17. Revealed identity secret hash: {}", recovered_identity);
println!(
" is{}MATCH the original",
if identity_secret_hash == recovered_identity {
" "
} else {
" NOT "
}
);
Ok(())
}

View File

@ -1,9 +1,18 @@
use std::{fs::File, io::Read, path::Path};
use std::{
fs::File,
io::{Cursor, Read},
path::Path,
};
use clap::Parser;
use color_eyre::{Report, Result};
use color_eyre::{eyre::Report, Result};
use commands::Commands;
use rln::public::RLN;
use config::{Config, InnerConfig};
use rln::{
public::RLN,
utils::{bytes_le_to_fr, bytes_le_to_vec_fr},
};
use serde_json::json;
use state::State;
mod commands;
@ -20,21 +29,24 @@ struct Cli {
fn main() -> Result<()> {
let cli = Cli::parse();
let mut state = State::load_state()?;
let mut state = match &cli.command {
Some(Commands::New { .. }) | Some(Commands::NewWithParams { .. }) => State::default(),
_ => State::load_state()?,
};
match &cli.command {
Some(Commands::New {
tree_height,
config,
}) => {
let resources = File::open(config)?;
state.rln = Some(RLN::new(*tree_height, resources)?);
match cli.command {
Some(Commands::New { tree_height }) => {
let config = Config::load_config()?;
state.rln = if let Some(InnerConfig { tree_height, .. }) = config.inner {
Some(RLN::new(tree_height, Cursor::new(config.as_bytes()))?)
} else {
Some(RLN::new(tree_height, Cursor::new(json!({}).to_string()))?)
};
Ok(())
}
Some(Commands::NewWithParams {
tree_height,
config,
tree_config_input,
resources_path,
}) => {
let mut resources: Vec<Vec<u8>> = Vec::new();
#[cfg(feature = "arkzkey")]
@ -42,55 +54,68 @@ fn main() -> Result<()> {
#[cfg(not(feature = "arkzkey"))]
let filenames = ["rln_final.zkey", "verification_key.arkvkey"];
for filename in filenames {
let fullpath = config.join(Path::new(filename));
let fullpath = resources_path.join(Path::new(filename));
let mut file = File::open(&fullpath)?;
let metadata = std::fs::metadata(&fullpath)?;
let mut buffer = vec![0; metadata.len() as usize];
file.read_exact(&mut buffer)?;
resources.push(buffer);
let mut output_buffer = vec![0; metadata.len() as usize];
file.read_exact(&mut output_buffer)?;
resources.push(output_buffer);
}
let tree_config_input_file = File::open(tree_config_input)?;
state.rln = Some(RLN::new_with_params(
*tree_height,
resources[0].clone(),
resources[1].clone(),
tree_config_input_file,
)?);
let config = Config::load_config()?;
if let Some(InnerConfig {
tree_height,
tree_config,
}) = config.inner
{
state.rln = Some(RLN::new_with_params(
tree_height,
resources[0].clone(),
resources[1].clone(),
Cursor::new(tree_config.to_string().as_bytes()),
)?)
} else {
state.rln = Some(RLN::new_with_params(
tree_height,
resources[0].clone(),
resources[1].clone(),
Cursor::new(json!({}).to_string()),
)?)
};
Ok(())
}
Some(Commands::SetTree { tree_height }) => {
state
.rln
.ok_or(Report::msg("no RLN instance initialized"))?
.set_tree(*tree_height)?;
.set_tree(tree_height)?;
Ok(())
}
Some(Commands::SetLeaf { index, file }) => {
let input_data = File::open(file)?;
Some(Commands::SetLeaf { index, input }) => {
let input_data = File::open(input)?;
state
.rln
.ok_or(Report::msg("no RLN instance initialized"))?
.set_leaf(*index, input_data)?;
.set_leaf(index, input_data)?;
Ok(())
}
Some(Commands::SetMultipleLeaves { index, file }) => {
let input_data = File::open(file)?;
Some(Commands::SetMultipleLeaves { index, input }) => {
let input_data = File::open(input)?;
state
.rln
.ok_or(Report::msg("no RLN instance initialized"))?
.set_leaves_from(*index, input_data)?;
.set_leaves_from(index, input_data)?;
Ok(())
}
Some(Commands::ResetMultipleLeaves { file }) => {
let input_data = File::open(file)?;
Some(Commands::ResetMultipleLeaves { input }) => {
let input_data = File::open(input)?;
state
.rln
.ok_or(Report::msg("no RLN instance initialized"))?
.init_tree_with_leaves(input_data)?;
Ok(())
}
Some(Commands::SetNextLeaf { file }) => {
let input_data = File::open(file)?;
Some(Commands::SetNextLeaf { input }) => {
let input_data = File::open(input)?;
state
.rln
.ok_or(Report::msg("no RLN instance initialized"))?
@ -101,49 +126,38 @@ fn main() -> Result<()> {
state
.rln
.ok_or(Report::msg("no RLN instance initialized"))?
.delete_leaf(*index)?;
Ok(())
}
Some(Commands::GetRoot) => {
let writer = std::io::stdout();
state
.rln
.ok_or(Report::msg("no RLN instance initialized"))?
.get_root(writer)?;
Ok(())
}
Some(Commands::GetProof { index }) => {
let writer = std::io::stdout();
state
.rln
.ok_or(Report::msg("no RLN instance initialized"))?
.get_proof(*index, writer)?;
.delete_leaf(index)?;
Ok(())
}
Some(Commands::Prove { input }) => {
let input_data = File::open(input)?;
let writer = std::io::stdout();
let mut output_buffer = Cursor::new(Vec::<u8>::new());
state
.rln
.ok_or(Report::msg("no RLN instance initialized"))?
.prove(input_data, writer)?;
.prove(input_data, &mut output_buffer)?;
let proof = output_buffer.into_inner();
println!("proof: {:?}", proof);
Ok(())
}
Some(Commands::Verify { file }) => {
let input_data = File::open(file)?;
state
Some(Commands::Verify { input }) => {
let input_data = File::open(input)?;
let verified = state
.rln
.ok_or(Report::msg("no RLN instance initialized"))?
.verify(input_data)?;
println!("verified: {:?}", verified);
Ok(())
}
Some(Commands::GenerateProof { input }) => {
let input_data = File::open(input)?;
let writer = std::io::stdout();
let mut output_buffer = Cursor::new(Vec::<u8>::new());
state
.rln
.ok_or(Report::msg("no RLN instance initialized"))?
.generate_rln_proof(input_data, writer)?;
.generate_rln_proof(input_data, &mut output_buffer)?;
let proof = output_buffer.into_inner();
println!("proof: {:?}", proof);
Ok(())
}
Some(Commands::VerifyWithRoots { input, roots }) => {
@ -155,6 +169,30 @@ fn main() -> Result<()> {
.verify_with_roots(input_data, roots_data)?;
Ok(())
}
Some(Commands::GetRoot) => {
let mut output_buffer = Cursor::new(Vec::<u8>::new());
state
.rln
.ok_or(Report::msg("no RLN instance initialized"))?
.get_root(&mut output_buffer)
.unwrap();
let (root, _) = bytes_le_to_fr(&output_buffer.into_inner());
println!("root: {root}");
Ok(())
}
Some(Commands::GetProof { index }) => {
let mut output_buffer = Cursor::new(Vec::<u8>::new());
state
.rln
.ok_or(Report::msg("no RLN instance initialized"))?
.get_proof(index, &mut output_buffer)?;
let output_buffer_inner = output_buffer.into_inner();
let (path_elements, _) = bytes_le_to_vec_fr(&output_buffer_inner)?;
for (index, element) in path_elements.iter().enumerate() {
println!("path element {}: {}", index, element);
}
Ok(())
}
None => Ok(()),
}
}

View File

@ -1,6 +1,7 @@
use std::io::Cursor;
use color_eyre::Result;
use rln::public::RLN;
use std::fs::File;
use crate::config::{Config, InnerConfig};
@ -12,9 +13,8 @@ pub(crate) struct State {
impl State {
pub(crate) fn load_state() -> Result<State> {
let config = Config::load_config()?;
let rln = if let Some(InnerConfig { file, tree_height }) = config.inner {
let resources = File::open(&file)?;
Some(RLN::new(tree_height, resources)?)
let rln = if let Some(InnerConfig { tree_height, .. }) = config.inner {
Some(RLN::new(tree_height, Cursor::new(config.as_bytes()))?)
} else {
None
};