From 5e41de256de3924adeb0bfda9b13d4963db24501 Mon Sep 17 00:00:00 2001 From: vinhtc27 Date: Fri, 14 Feb 2025 03:29:21 +0700 Subject: [PATCH] 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 --- .gitignore | 1 + Cargo.lock | 17 +- rln-cli/Cargo.toml | 26 ++- rln-cli/README.md | 166 +++++++++++++++++ rln-cli/example.config.json | 11 ++ rln-cli/src/commands.rs | 28 +-- rln-cli/src/config.rs | 31 ++-- rln-cli/src/examples/relay.rs | 298 ++++++++++++++++++++++++++++++ rln-cli/src/examples/stateless.rs | 214 +++++++++++++++++++++ rln-cli/src/main.rs | 156 ++++++++++------ rln-cli/src/state.rs | 8 +- 11 files changed, 853 insertions(+), 103 deletions(-) create mode 100644 rln-cli/README.md create mode 100644 rln-cli/example.config.json create mode 100644 rln-cli/src/examples/relay.rs create mode 100644 rln-cli/src/examples/stateless.rs diff --git a/.gitignore b/.gitignore index 201b119..cfbf481 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ *.log tmp/ rln/pmtree_db +rln-cli/database # Generated by Cargo # will have compiled files and executables diff --git a/Cargo.lock b/Cargo.lock index 8184c20..daa1635 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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]] diff --git a/rln-cli/Cargo.toml b/rln-cli/Cargo.toml index 790f4a4..97bed78 100644 --- a/rln-cli/Cargo.toml +++ b/rln-cli/Cargo.toml @@ -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"] diff --git a/rln-cli/README.md b/rln-cli/README.md new file mode 100644 index 0000000..dd6d509 --- /dev/null +++ b/rln-cli/README.md @@ -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 -- [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 +``` + +To initialize an RLN instance with custom parameters: + +```bash +cargo run new-with-params --resources-path --tree-height +``` + +To update the Merkle tree height: + +```bash +cargo run set-tree --tree-height +``` + +### Leaf Operations + +To set a single leaf: + +```bash +cargo run set-leaf --index --input +``` + +To set multiple leaves: + +```bash +cargo run set-multiple-leaves --index --input +``` + +To reset multiple leaves: + +```bash +cargo run reset-multiple-leaves --input +``` + +To set the next available leaf: + +```bash +cargo run set-next-leaf --input +``` + +To delete a specific leaf: + +```bash +cargo run delete-leaf --index +``` + +### Proof Operations + +To generate a proof: + +```bash +cargo run prove --input +``` + +To generate an RLN proof: + +```bash +cargo run generate-proof --input +``` + +To verify a proof: + +```bash +cargo run verify --input +``` + +To verify a proof with multiple Merkle roots: + +```bash +cargo run verify-with-roots --input --roots +``` + +### 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 +``` + +## Feature Flags + +The CLI supports optional features. To enable the **arkzkey** feature, run: + +```bash +cargo run --features arkzkey -- [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 +``` diff --git a/rln-cli/example.config.json b/rln-cli/example.config.json new file mode 100644 index 0000000..472eba5 --- /dev/null +++ b/rln-cli/example.config.json @@ -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 +} diff --git a/rln-cli/src/commands.rs b/rln-cli/src/commands.rs index f994683..1b48c64 100644 --- a/rln-cli/src/commands.rs +++ b/rln-cli/src/commands.rs @@ -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)] diff --git a/rln-cli/src/config.rs b/rln-cli/src/config.rs index 5b85dc9..92befa4 100644 --- a/rln-cli/src/config.rs +++ b/rln-cli/src/config.rs @@ -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 { - 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 { + serde_json::to_string(&self.inner).unwrap().into_bytes() } } diff --git a/rln-cli/src/examples/relay.rs b/rln-cli/src/examples/relay.rs new file mode 100644 index 0000000..d7bf856 --- /dev/null +++ b/rln-cli/src/examples/relay.rs @@ -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>, + // Mimic local storage for user identities of each user index + local_identities: HashMap, +} + +impl RLNSystem { + fn new() -> Result { + 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 { + 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> { + 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, 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, + current_proof: Vec, + ) -> 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 -m -s - Send a message with proof"); + println!(" clear - Clear the screen"); + println!(" exit - Exit the program"); +} diff --git a/rln-cli/src/examples/stateless.rs b/rln-cli/src/examples/stateless.rs new file mode 100644 index 0000000..a0938fa --- /dev/null +++ b/rln-cli/src/examples/stateless.rs @@ -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 = ::Config; + +fn main() -> Result<(), Box> { + 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::::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] + 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] + 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] + 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(()) +} diff --git a/rln-cli/src/main.rs b/rln-cli/src/main.rs index dd7563e..77cdb80 100644 --- a/rln-cli/src/main.rs +++ b/rln-cli/src/main.rs @@ -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::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::::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::::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::::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::::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(()), } } diff --git a/rln-cli/src/state.rs b/rln-cli/src/state.rs index b3cb76b..15e5c15 100644 --- a/rln-cli/src/state.rs +++ b/rln-cli/src/state.rs @@ -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 { 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 };