mirror of
https://github.com/vacp2p/zerokit.git
synced 2025-02-23 03:38:10 +00:00
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:
parent
8a3e33be41
commit
5e41de256d
1
.gitignore
vendored
1
.gitignore
vendored
@ -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
17
Cargo.lock
generated
@ -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]]
|
||||
|
@ -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
166
rln-cli/README.md
Normal 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
|
||||
```
|
11
rln-cli/example.config.json
Normal file
11
rln-cli/example.config.json
Normal 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
|
||||
}
|
@ -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)]
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
298
rln-cli/src/examples/relay.rs
Normal file
298
rln-cli/src/examples/relay.rs
Normal 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");
|
||||
}
|
214
rln-cli/src/examples/stateless.rs
Normal file
214
rln-cli/src/examples/stateless.rs
Normal 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(())
|
||||
}
|
@ -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(()),
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user