Prechádzať zdrojové kódy

research/rln-fau: Basic code for RLN ident

x 1 mesiac pred
rodič
commit
7c97fa8ca6

+ 3 - 0
script/research/rln-fau/.gitignore

@@ -0,0 +1,3 @@
+*.bin
+target/
+Cargo.lock

+ 12 - 0
script/research/rln-fau/Cargo.toml

@@ -0,0 +1,12 @@
+[package]
+name = "rln-fau"
+version = "0.5.0"
+authors = ["Dyne.org foundation <foundation@dyne.org>"]
+license = "AGPL-3.0-only"
+edition = "2021"
+
+[workspace]
+
+[dependencies]
+bs58 = "0.5.1"
+darkfi-serial = {path = "../../../src/serial", features = ["collections"]}

+ 134 - 0
script/research/rln-fau/src/main.rs

@@ -0,0 +1,134 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2020-2026 Dyne.org foundation
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+use std::{collections::HashMap, env, fs, process};
+
+use darkfi_serial::{deserialize, serialize};
+
+const COMMITMENTS_PATH: &str = "darkirc_rln_commits.bin";
+const GENESIS_USER_MSG_LIMIT: u64 = 100;
+
+type Commitment = [u8; 32];
+type Secret = [u8; 32];
+type IdentityRecord = (Secret, Secret, bool);
+type Commitments = HashMap<Commitment, IdentityRecord>;
+
+fn main() {
+    if let Err(err) = run() {
+        eprintln!("error: {err}");
+        process::exit(1);
+    }
+}
+
+/// Dispatch the requested command.
+fn run() -> Result<(), String> {
+    let mut args = env::args().skip(1);
+    let Some(command) = args.next() else {
+        usage();
+        return Err("missing command".to_string());
+    };
+
+    if args.next().is_some() {
+        usage();
+        return Err("too many arguments".to_string());
+    }
+
+    match command.as_str() {
+        "reset" => reset(),
+        "produce" => produce(),
+        "help" | "-h" | "--help" => {
+            usage();
+            Ok(())
+        }
+        _ => {
+            usage();
+            Err(format!("unknown command `{command}`"))
+        }
+    }
+}
+
+/// Reset every generated identity so it can be produced again.
+fn reset() -> Result<(), String> {
+    let mut commitments = load_commitments()?;
+    for (_, _, used) in commitments.values_mut() {
+        *used = false;
+    }
+
+    let count = commitments.len();
+    write_commitments(&commitments)?;
+    println!("reset {count} commitments");
+    Ok(())
+}
+
+/// Mark the first available identity as used and print its registration command.
+fn produce() -> Result<(), String> {
+    let mut commitments = load_commitments()?;
+    let mut available = commitments
+        .iter()
+        .filter_map(|(commitment, (_, _, used))| (!*used).then_some(*commitment))
+        .collect::<Vec<_>>();
+    available.sort_unstable();
+
+    let Some(commitment) = available.into_iter().next() else {
+        return Err("no unused commitments available".to_string());
+    };
+
+    let (nullifier, trapdoor) = {
+        let record = commitments
+            .get_mut(&commitment)
+            .ok_or_else(|| "selected commitment disappeared".to_string())?;
+        record.2 = true;
+        (record.0, record.1)
+    };
+
+    write_commitments(&commitments)?;
+    print_credentials(nullifier, trapdoor);
+    Ok(())
+}
+
+/// Load the commitments map from the DarkIRC genesis identity file.
+fn load_commitments() -> Result<Commitments, String> {
+    let bytes = fs::read(COMMITMENTS_PATH)
+        .map_err(|err| format!("failed to read `{COMMITMENTS_PATH}`: {err}"))?;
+    deserialize(&bytes).map_err(|err| format!("failed to deserialize `{COMMITMENTS_PATH}`: {err}"))
+}
+
+/// Write the commitments map back to the DarkIRC genesis identity file.
+fn write_commitments(commitments: &Commitments) -> Result<(), String> {
+    fs::write(COMMITMENTS_PATH, serialize(commitments))
+        .map_err(|err| format!("failed to write `{COMMITMENTS_PATH}`: {err}"))
+}
+
+/// Print the fields needed by DarkIRC NickServ registration.
+fn print_credentials(nullifier: Secret, trapdoor: Secret) {
+    let nullifier = bs58::encode(nullifier).into_string();
+    let trapdoor = bs58::encode(trapdoor).into_string();
+
+    println!("nullifier = {nullifier}");
+    println!("trapdoor = {trapdoor}");
+    println!("user_msg_limit = {GENESIS_USER_MSG_LIMIT}");
+    println!();
+    println!(
+        "/msg NickServ REGISTER <account_name> {nullifier} {trapdoor} {GENESIS_USER_MSG_LIMIT}"
+    );
+}
+
+/// Print command usage.
+fn usage() {
+    println!("Usage: rln-fau <reset|produce>");
+}