parazyd пре 1 година
родитељ
комит
2492203bf5

+ 44 - 0
bin/darkirc/proof/rlnv2-diff-signal.zk

@@ -0,0 +1,44 @@
+k = 13;
+field = "pallas";
+
+constant "RlnV2_Diff_Signal" {}
+
+witness "RlnV2_Diff_Signal" {
+    Base identity_nullifier,
+    Base identity_trapdoor,
+
+    MerklePath identity_path,
+    Uint32 identity_leaf_pos,
+
+    Base x, # The message hash
+    Base external_nullifier, # Hash(Epoch, RLN identifier)
+
+    Base message_id,
+    Base user_message_limit,
+
+    Base epoch,
+}
+
+circuit "RlnV2_Diff_Signal" {
+    constrain_instance(epoch);
+    constrain_instance(external_nullifier);
+
+    less_than_strict(message_id, user_message_limit);
+
+    # Identity secret hash
+    a_0 = poseidon_hash(identity_nullifier, identity_trapdoor);
+    a_1 = poseidon_hash(a_0, external_nullifier, message_id);
+
+    # y = a_0 + x * a_1
+    x_a_1 = base_mul(x, a_1);
+    y = base_add(a_0, x_a_1);
+    constrain_instance(x);
+    constrain_instance(y);
+
+    internal_nullifier = poseidon_hash(a_1);
+    constrain_instance(internal_nullifier);
+
+    identity_commitment = poseidon_hash(a_0, user_message_limit);
+    root = merkle_root(identity_leaf_pos, identity_path, identity_commitment);
+    constrain_instance(root);
+}

+ 0 - 0
bin/darkirc/proof/slash.zk → bin/darkirc/proof/rlnv2-diff-slash.zk


+ 0 - 38
bin/darkirc/proof/signal.zk

@@ -1,38 +0,0 @@
-k = 13;
-field = "pallas";
-
-constant "RlnSignal" {}
-
-witness "RlnSignal" {
-    Base secret_key,
-    MerklePath identity_path,
-    Uint32 identity_leaf_pos,
-
-    # These are public so have to be properly constructed
-    Base message_hash, # x
-    Base epoch,
-    Base rln_identifier,
-}
-
-circuit "RlnSignal" {
-    constrain_instance(epoch);
-    constrain_instance(rln_identifier);
-    constrain_instance(message_hash);
-
-    # This has to be the same constant used outside
-    identity_derivation_path = witness_base(11);
-    nullifier_derivation_path = witness_base(12);
-
-    identity_commit = poseidon_hash(identity_derivation_path, secret_key);
-    root = merkle_root(identity_leaf_pos, identity_path, identity_commit);
-    constrain_instance(root);
-
-    external_nullifier = poseidon_hash(epoch, rln_identifier);
-    a_1 = poseidon_hash(secret_key, external_nullifier);
-    internal_nullifier = poseidon_hash(nullifier_derivation_path, a_1);
-    constrain_instance(internal_nullifier);
-
-    y_a = base_mul(a_1, message_hash);
-    y = base_add(y_a, secret_key);
-    constrain_instance(y);
-}

+ 3 - 0
bin/darkirc/src/crypto/mod.rs

@@ -23,3 +23,6 @@ pub mod saltbox;
 
 /// bcrypt utilities
 pub mod bcrypt;
+
+/// Rate-Limit nullifiers
+pub mod rln;

+ 160 - 0
bin/darkirc/src/crypto/rln.rs

@@ -0,0 +1,160 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2020-2025 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::time::UNIX_EPOCH;
+
+use darkfi::{
+    event_graph::Event,
+    zk::{
+        halo2::{Field, Value},
+        Proof, ProvingKey, Witness, ZkCircuit,
+    },
+    zkas::ZkBinary,
+    Result,
+};
+use darkfi_sdk::{
+    bridgetree::Position,
+    crypto::{pasta_prelude::FromUniformBytes, poseidon_hash, MerkleTree},
+    pasta::pallas,
+};
+use log::info;
+use rand::{rngs::OsRng, CryptoRng, RngCore};
+
+pub const RLN_APP_IDENTIFIER: pallas::Base = pallas::Base::from_raw([4242, 0, 0, 0]);
+pub const RLN_TRAPDOOR_DERIVATION_PATH: pallas::Base = pallas::Base::from_raw([4211, 0, 0, 0]);
+pub const RLN_NULLIFIER_DERIVATION_PATH: pallas::Base = pallas::Base::from_raw([4212, 0, 0, 0]);
+
+/// RLN epoch genesis
+pub const RLN_GENESIS: u64 = 1738688400;
+/// RLN epoch length in seconds
+pub const RLN_EPOCH_LEN: u64 = 600; // 10 min
+
+pub const RLN2_SIGNAL_ZKBIN: &[u8] = include_bytes!("../../proof/rlnv2-diff-signal.zk.bin");
+pub const RLN2_SLASH_ZKBIN: &[u8] = include_bytes!("../../proof/rlnv2-diff-slash.zk.bin");
+
+/// Find closest epoch to given timestamp
+pub fn closest_epoch(timestamp: u64) -> u64 {
+    let time_diff = timestamp - RLN_GENESIS;
+    let epoch_idx = time_diff as f64 / RLN_EPOCH_LEN as f64;
+    let rounded = epoch_idx.round() as i64;
+    RLN_GENESIS + (rounded * RLN_EPOCH_LEN as i64) as u64
+}
+
+/// Hash message/event modulo `Fp`
+pub fn hash_event(event: &Event) -> pallas::Base {
+    let mut buf = [0u8; 64];
+    buf[..blake3::OUT_LEN].copy_from_slice(event.id().as_bytes());
+    pallas::Base::from_uniform_bytes(&buf)
+}
+
+#[derive(Copy, Clone)]
+pub struct RlnIdentity {
+    pub nullifier: pallas::Base,
+    pub trapdoor: pallas::Base,
+    pub user_message_limit: u64,
+    /// This should increment during a single epoch and reset on new epochs
+    pub message_id: u64,
+    /// Last known epoch
+    pub last_epoch: u64,
+}
+
+impl RlnIdentity {
+    pub fn new(mut rng: (impl CryptoRng + RngCore)) -> Self {
+        Self {
+            nullifier: poseidon_hash([
+                RLN_NULLIFIER_DERIVATION_PATH,
+                pallas::Base::random(&mut rng),
+            ]),
+            trapdoor: poseidon_hash([RLN_TRAPDOOR_DERIVATION_PATH, pallas::Base::random(&mut rng)]),
+            user_message_limit: 100,
+            message_id: 1,
+            last_epoch: closest_epoch(UNIX_EPOCH.elapsed().unwrap().as_secs()),
+        }
+    }
+
+    pub fn commitment(&self) -> pallas::Base {
+        poseidon_hash([
+            poseidon_hash([self.nullifier, self.trapdoor]),
+            pallas::Base::from(self.user_message_limit),
+        ])
+    }
+
+    pub fn create_signal_proof(
+        &self,
+        event: &Event,
+        identity_tree: &MerkleTree,
+        identity_pos: Position,
+        proving_key: ProvingKey,
+    ) -> Result<(Proof, Vec<pallas::Base>)> {
+        // 1. Construct share
+        let epoch = pallas::Base::from(closest_epoch(event.timestamp));
+        let message_id = pallas::Base::from(self.message_id);
+        let external_nullifier = poseidon_hash([epoch, RLN_APP_IDENTIFIER]);
+        let a_0 = poseidon_hash([self.nullifier, self.trapdoor]);
+        let a_1 = poseidon_hash([a_0, external_nullifier, message_id]);
+        let x = hash_event(event);
+        let y = a_0 + x * a_1;
+
+        let internal_nullifier = poseidon_hash([a_1]);
+
+        // 2. Create Merkle proof
+        let identity_root = identity_tree.root(0).unwrap();
+        let identity_path = identity_tree.witness(identity_pos, 0).unwrap();
+
+        // 3. Create ZK proof
+        let witnesses = vec![
+            Witness::Base(Value::known(self.nullifier)),
+            Witness::Base(Value::known(self.trapdoor)),
+            Witness::MerklePath(Value::known(identity_path.clone().try_into().unwrap())),
+            Witness::Uint32(Value::known(u64::from(identity_pos).try_into().unwrap())),
+            Witness::Base(Value::known(x)),
+            Witness::Base(Value::known(external_nullifier)),
+            Witness::Base(Value::known(message_id)),
+            Witness::Base(Value::known(pallas::Base::from(self.user_message_limit))),
+            Witness::Base(Value::known(epoch)),
+        ];
+
+        let public_inputs =
+            vec![epoch, external_nullifier, x, y, internal_nullifier, identity_root.inner()];
+
+        info!(target: "crypto::rln::create_proof", "[RLN] Creating proof for event {}", event.id());
+        let signal_zkbin = ZkBinary::decode(RLN2_SIGNAL_ZKBIN)?;
+        let signal_circuit = ZkCircuit::new(witnesses, &signal_zkbin);
+
+        let proof = Proof::create(&proving_key, &[signal_circuit], &public_inputs, &mut OsRng)?;
+        Ok((proof, vec![y, internal_nullifier]))
+    }
+}
+
+/// Recover a secret from given secret shares
+pub fn sss_recover(shares: &[(pallas::Base, pallas::Base)]) -> pallas::Base {
+    let mut secret = pallas::Base::zero();
+    for (j, share_j) in shares.iter().enumerate() {
+        let mut prod = pallas::Base::one();
+        for (i, share_i) in shares.iter().enumerate() {
+            if i != j {
+                prod *= share_i.0 * (share_i.0 - share_j.0).invert().unwrap();
+            }
+        }
+
+        prod *= share_j.1;
+        secret += prod;
+    }
+
+    secret
+}

+ 77 - 5
bin/darkirc/src/irc/client.rs

@@ -18,6 +18,7 @@
 
 use std::{
     collections::{HashMap, HashSet, VecDeque},
+    io::Cursor,
     sync::{
         atomic::{AtomicBool, AtomicUsize, Ordering::SeqCst},
         Arc,
@@ -27,9 +28,16 @@ use std::{
 use darkfi::{
     event_graph::{proto::EventPut, Event, NULL_ID},
     system::Subscription,
+    zk::{empty_witnesses, Proof, ProvingKey, ZkCircuit},
+    zkas::ZkBinary,
     Error, Result,
 };
-use darkfi_serial::serialize_async;
+use darkfi_sdk::{
+    bridgetree::Position,
+    crypto::{pasta_prelude::PrimeField, MerkleTree},
+    pasta::pallas,
+};
+use darkfi_serial::{deserialize_async, serialize_async};
 use futures::FutureExt;
 use log::{debug, error, warn};
 use sled_overlay::sled;
@@ -44,6 +52,7 @@ use super::{
     server::{IrcServer, MAX_MSG_LEN},
     Msg, NickServ, OldPrivmsg, SERVER_NAME,
 };
+use crate::crypto::rln::{closest_epoch, RlnIdentity, RLN2_SIGNAL_ZKBIN};
 
 const PENALTY_LIMIT: usize = 5;
 
@@ -178,8 +187,7 @@ impl Client {
                                 let event_id = event.id();
                                 *self.last_sent.write().await = event_id;
 
-                                // If it fails for some reason, for now, we just note it
-                                // and pass.
+                                // If it fails for some reason, for now, we just note it and pass.
                                 if let Err(e) = self.server.darkirc.event_graph.dag_insert(&[event.clone()]).await {
                                     error!("[IRC CLIENT] Failed inserting new event to DAG: {}", e);
                                 } else {
@@ -189,8 +197,32 @@ impl Client {
                                         return Err(e)
                                     }
 
-                                    // Otherwise, broadcast it
-                                    self.server.darkirc.p2p.broadcast(&EventPut(event)).await;
+                                    // If we have a RLN identity, now we'll build a ZK proof.
+                                    // Also I really want GOTO in Rust... Fags.
+                                    if let Some(mut rln_identity) = *self.server.rln_identity.write().await {
+                                        // If the current epoch is different, we can reset the message counter
+                                        if rln_identity.last_epoch != closest_epoch(event.timestamp) {
+                                            rln_identity.last_epoch = closest_epoch(event.timestamp);
+                                            rln_identity.message_id = 0;
+                                        }
+
+                                        rln_identity.message_id += 1;
+
+                                        let (_proof, _public_inputs) = match self.create_rln_signal_proof(&rln_identity, &event).await {
+                                            Ok(v) => v,
+                                            Err(e) => {
+                                                // TODO: Send a message to the IRC client telling that sending went wrong
+                                                error!("[IRC CLIENT] Failed creating RLN signal proof: {}", e);
+                                                // Just use an empty "proof"
+                                                (Proof::new(vec![]), vec![])
+                                            }
+                                        };
+
+                                        self.server.darkirc.p2p.broadcast(&EventPut(event)).await;
+                                    } else {
+                                        // Broadcast it
+                                        self.server.darkirc.p2p.broadcast(&EventPut(event)).await;
+                                    }
                                 }
                             }
                         }
@@ -488,4 +520,44 @@ impl Client {
 
         Ok(db.contains_key(event_id.as_bytes())?)
     }
+
+    /// Abstraction for RLN signal proof creation
+    async fn create_rln_signal_proof(
+        &self,
+        rln_identity: &RlnIdentity,
+        event: &Event,
+    ) -> Result<(Proof, Vec<pallas::Base>)> {
+        let identity_commitment = rln_identity.commitment();
+
+        // Fetch the commitment's leaf position in the Merkle tree
+        let Some(identity_pos) =
+            self.server.rln_identity_store.get(identity_commitment.to_repr())?
+        else {
+            return Err(Error::DatabaseError(
+                "Identity not found in commitment tree store".to_string(),
+            ))
+        };
+        let identity_pos: Position = deserialize_async(&identity_pos).await?;
+
+        // Fetch the latest commitment Merkle tree
+        let Some(identity_tree) = self.server.server_store.get("rln_identity_tree")? else {
+            return Err(Error::DatabaseError(
+                "RLN Identity tree not found in server store".to_string(),
+            ))
+        };
+        let identity_tree: MerkleTree = deserialize_async(&identity_tree).await?;
+
+        // Retrieve the ZK proving key from the db
+        let signal_zkbin = ZkBinary::decode(RLN2_SIGNAL_ZKBIN)?;
+        let signal_circuit = ZkCircuit::new(empty_witnesses(&signal_zkbin)?, &signal_zkbin);
+        let Some(proving_key) = self.server.server_store.get("rlnv2-diff-signal-pk")? else {
+            return Err(Error::DatabaseError(
+                "RLN signal proving key not found in server store".to_string(),
+            ))
+        };
+        let mut reader = Cursor::new(proving_key);
+        let proving_key = ProvingKey::read(&mut reader, signal_circuit)?;
+
+        rln_identity.create_signal_proof(event, &identity_tree, identity_pos, proving_key)
+    }
 }

+ 77 - 2
bin/darkirc/src/irc/server.rs

@@ -22,13 +22,18 @@ use darkfi::{
     event_graph::Event,
     system::{StoppableTask, StoppableTaskPtr, Subscription},
     util::path::expand_path,
+    zk::{empty_witnesses, ProvingKey, VerifyingKey, ZkCircuit},
+    zkas::ZkBinary,
     Error, Result,
 };
+use darkfi_sdk::crypto::MerkleTree;
+use darkfi_serial::serialize_async;
 use futures_rustls::{
     rustls::{self, pki_types::PrivateKeyDer},
     TlsAcceptor,
 };
 use log::{debug, error, info, warn};
+use sled_overlay::sled;
 use smol::{
     fs,
     lock::{Mutex, RwLock},
@@ -40,8 +45,14 @@ use url::Url;
 
 use super::{client::Client, ChaChaBox, IrcChannel, IrcContact, Priv, Privmsg};
 use crate::{
-    crypto::saltbox,
-    settings::{parse_autojoin_channels, parse_configured_channels, parse_configured_contacts},
+    crypto::{
+        rln::{RlnIdentity, RLN2_SIGNAL_ZKBIN, RLN2_SLASH_ZKBIN},
+        saltbox,
+    },
+    settings::{
+        parse_autojoin_channels, parse_configured_channels, parse_configured_contacts,
+        parse_rln_identity,
+    },
     DarkIrc,
 };
 
@@ -67,12 +78,18 @@ pub struct IrcServer {
     pub channels: RwLock<HashMap<String, IrcChannel>>,
     /// Configured IRC contacts
     pub contacts: RwLock<HashMap<String, IrcContact>>,
+    /// Configured RLN identity
+    pub rln_identity: RwLock<Option<RlnIdentity>>,
     /// Saltbox used to encrypt our nick in direct messages
     saltbox: RwLock<Option<Arc<ChaChaBox>>>,
     /// Active client connections
     clients: Mutex<HashMap<u16, StoppableTaskPtr>>,
     /// IRC server Password
     pub password: String,
+    /// Persistent server storage
+    pub server_store: sled::Tree,
+    /// RLN identity storage
+    pub rln_identity_store: sled::Tree,
 }
 
 impl IrcServer {
@@ -127,6 +144,57 @@ impl IrcServer {
             _ => None,
         };
 
+        // Open persistent dbs
+        let server_store = darkirc.sled.open_tree("server_store")?;
+        let rln_identity_store = darkirc.sled.open_tree("rln_identity_store")?;
+
+        // Generate RLN proving and verifying keys, if needed
+        if server_store.get("rlnv2-diff-signal-pk")?.is_none() {
+            info!(target: "irc::server", "[RLN] Creating RlnV2_Diff_Signal ProvingKey");
+            let zkbin = ZkBinary::decode(RLN2_SIGNAL_ZKBIN)?;
+            let circuit = ZkCircuit::new(empty_witnesses(&zkbin).unwrap(), &zkbin);
+            let provingkey = ProvingKey::build(zkbin.k, &circuit);
+            let mut buf = vec![];
+            provingkey.write(&mut buf)?;
+            server_store.insert("rlnv2-diff-signal-pk", buf)?;
+        }
+
+        if server_store.get("rlnv2-diff-signal-vk")?.is_none() {
+            info!(target: "irc::server", "[RLN] Creating RlnV2_Diff_Signal VerifyingKey");
+            let zkbin = ZkBinary::decode(RLN2_SIGNAL_ZKBIN)?;
+            let circuit = ZkCircuit::new(empty_witnesses(&zkbin).unwrap(), &zkbin);
+            let verifyingkey = VerifyingKey::build(zkbin.k, &circuit);
+            let mut buf = vec![];
+            verifyingkey.write(&mut buf)?;
+            server_store.insert("rlnv2-diff-signal-vk", buf)?;
+        }
+
+        if server_store.get("rlnv2-diff-slash-pk")?.is_none() {
+            info!(target: "irc::server", "[RLN] Creating RlnV2_Diff_Slash ProvingKey");
+            let zkbin = ZkBinary::decode(RLN2_SLASH_ZKBIN)?;
+            let circuit = ZkCircuit::new(empty_witnesses(&zkbin).unwrap(), &zkbin);
+            let provingkey = ProvingKey::build(zkbin.k, &circuit);
+            let mut buf = vec![];
+            provingkey.write(&mut buf)?;
+            server_store.insert("rlnv2-diff-slash-pk", buf)?;
+        }
+
+        if server_store.get("rlnv2-diff-slash-vk")?.is_none() {
+            info!(target: "irc::server", "[RLN] Creating RlnV2_Diff_Slash VerifyingKey");
+            let zkbin = ZkBinary::decode(RLN2_SIGNAL_ZKBIN)?;
+            let circuit = ZkCircuit::new(empty_witnesses(&zkbin).unwrap(), &zkbin);
+            let verifyingkey = VerifyingKey::build(zkbin.k, &circuit);
+            let mut buf = vec![];
+            verifyingkey.write(&mut buf)?;
+            server_store.insert("rlnv2-diff-slash-vk", buf)?;
+        }
+
+        // Initialize RLN Incremental Merkle tree if necessary
+        if server_store.get("rln_identity_tree")?.is_none() {
+            let tree = MerkleTree::new(0);
+            server_store.insert("rln_identity_tree", serialize_async(&tree).await)?;
+        }
+
         let self_ = Arc::new(Self {
             darkirc,
             config_path,
@@ -136,8 +204,11 @@ impl IrcServer {
             channels: RwLock::new(HashMap::new()),
             contacts: RwLock::new(HashMap::new()),
             saltbox: RwLock::new(None),
+            rln_identity: RwLock::new(None),
             clients: Mutex::new(HashMap::new()),
             password,
+            server_store,
+            rln_identity_store,
         });
 
         // Load any channel/contact configuration.
@@ -166,12 +237,16 @@ impl IrcServer {
         // Parse configured contacts
         let (contacts, saltbox) = parse_configured_contacts(&contents)?;
 
+        // Parse RLN identity
+        let rln_identity = parse_rln_identity(&contents)?;
+
         // FIXME: This will remove clients' joined channels. They need to stay.
         // Only if everything is fine, replace.
         *self.autojoin.write().await = autojoin;
         *self.channels.write().await = channels;
         *self.contacts.write().await = contacts;
         *self.saltbox.write().await = saltbox;
+        *self.rln_identity.write().await = rln_identity;
 
         Ok(())
     }

+ 17 - 4
bin/darkirc/src/main.rs

@@ -30,6 +30,7 @@ use darkfi::{
     util::path::{expand_path, get_config_path},
     Error, Result,
 };
+use darkfi_sdk::crypto::pasta_prelude::PrimeField;
 
 use log::{debug, error, info};
 use rand::rngs::OsRng;
@@ -48,10 +49,7 @@ use irc::server::IrcServer;
 
 /// Cryptography utilities
 mod crypto;
-use crypto::bcrypt::bcrypt_hash_password;
-
-// RLN
-//mod rln;
+use crypto::{bcrypt::bcrypt_hash_password, rln::RlnIdentity};
 
 /// JSON-RPC methods
 mod rpc;
@@ -119,6 +117,10 @@ struct Args {
     #[structopt(long = "get-chacha-pubkey")]
     chacha_secret: Option<String>,
 
+    /// Generate a new RLN identity
+    #[structopt(long)]
+    gen_rln_identity: bool,
+
     /// Flag to skip syncing the DAG (no history).
     #[structopt(long)]
     skip_dag_sync: bool,
@@ -204,6 +206,17 @@ async fn realmain(args: Args, ex: Arc<Executor<'static>>) -> Result<()> {
         return Ok(())
     }
 
+    if args.gen_rln_identity {
+        let identity = RlnIdentity::new(&mut OsRng);
+        let nullifier = bs58::encode(identity.nullifier.to_repr()).into_string();
+        let trapdoor = bs58::encode(identity.trapdoor.to_repr()).into_string();
+        println!("Place this in your config file:\n");
+        println!("[rln]");
+        println!("nullifier = \"{}\"", nullifier);
+        println!("trapdoor = \"{}\"", trapdoor);
+        return Ok(())
+    }
+
     if let Some(chacha_secret) = args.chacha_secret {
         let bytes = match bs58::decode(chacha_secret).into_vec() {
             Ok(v) => v,

+ 0 - 113
bin/darkirc/src/rln.rs

@@ -1,113 +0,0 @@
-/* This file is part of DarkFi (https://dark.fi)
- *
- * Copyright (C) 2020-2025 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/>.
- */
-
-//! <https://darkrenaissance.github.io/darkfi/crypto/rln.html>
-
-use darkfi::{
-    zk::{empty_witnesses, halo2::Field, ProvingKey, VerifyingKey, ZkCircuit},
-    zkas::ZkBinary,
-    Result,
-};
-use darkfi_sdk::{crypto::MerkleTree, pasta::pallas};
-use darkfi_serial::serialize_async;
-use log::info;
-
-const RLN_IDENTIFIER: pallas::Base = pallas::Base::from_raw([0, 0, 42, 42]);
-const IDENTITY_DERIVATION_PATH: pallas::Base = pallas::Base::from_raw([0, 0, 42, 11]);
-const NULLIFIER_DERIVATION_PATH: pallas::Base = pallas::Base::from_raw([0, 0, 42, 12]);
-
-/// Rate-Limit-Nullifiers
-///
-/// This mechanism is used for spam protection on the IRC network.
-pub struct Rln {
-    /// DB holding identity commitments and the membership Merkle tree
-    /// The scheme is `(k=identity_commitment, v=leaf_position)`
-    identities: sled::Tree,
-    /// DB holding identity roots
-    identity_roots: sled::Tree,
-    /// DB holding banned roots
-    banned_roots: sled::Tree,
-    /// Proving key for the signalling circuit
-    signal_pk: ProvingKey,
-    /// Verifying key for the signalling circuit
-    signal_vk: VerifyingKey,
-    /// Proving key for the slashing circuit
-    slash_pk: ProvingKey,
-    /// Verifying key for the slashing circuit
-    slash_vk: VerifyingKey,
-}
-
-impl Rln {
-    /// Create a new Rln instance
-    pub async fn new(sled_db: &sled::Db) -> Result<Self> {
-        let identities = sled_db.open_tree("identities")?;
-        let identity_roots = sled_db.open_tree("identity_roots")?;
-        let banned_roots = sled_db.open_tree("banned_roots")?;
-
-        if !identities.contains_key(b"identity_tree")? {
-            info!("Creating RLN membership tree");
-            let membership_tree = MerkleTree::new(1);
-            identities.insert(b"identity_tree", serialize_async(&membership_tree).await)?;
-        }
-
-        let signal_zkbin = include_bytes!("../proof/signal.zk.bin");
-        let slash_zkbin = include_bytes!("../proof/slash.zk.bin");
-
-        info!("Building RLN signal proving key");
-        let signal_zkbin = ZkBinary::decode(signal_zkbin).unwrap();
-        let signal_circuit = ZkCircuit::new(empty_witnesses(&signal_zkbin)?, &signal_zkbin);
-        let signal_pk = ProvingKey::build(signal_zkbin.k, &signal_circuit);
-        info!("Building RLN signal verifying key");
-        let signal_vk = VerifyingKey::build(signal_zkbin.k, &signal_circuit);
-
-        info!("Building RLN slash proving key");
-        let slash_zkbin = ZkBinary::decode(slash_zkbin).unwrap();
-        let slash_circuit = ZkCircuit::new(empty_witnesses(&slash_zkbin)?, &slash_zkbin);
-        let slash_pk = ProvingKey::build(slash_zkbin.k, &slash_circuit);
-        info!("Building RLN slash verifying key");
-        let slash_vk = VerifyingKey::build(slash_zkbin.k, &slash_circuit);
-
-        Ok(Self {
-            identities,
-            identity_roots,
-            banned_roots,
-            signal_pk,
-            signal_vk,
-            slash_pk,
-            slash_vk,
-        })
-    }
-
-    /// Recover a secret from given secret shares
-    pub fn sss_recover(shares: &[(pallas::Base, pallas::Base)]) -> pallas::Base {
-        let mut secret = pallas::Base::zero();
-        for (j, share_j) in shares.iter().enumerate() {
-            let mut prod = pallas::Base::one();
-            for (i, share_i) in shares.iter().enumerate() {
-                if i != j {
-                    prod *= share_i.0 * (share_i.0 - share_j.0).invert().unwrap();
-                }
-            }
-
-            prod *= share_j.1;
-            secret += prod;
-        }
-
-        secret
-    }
-}

+ 88 - 1
bin/darkirc/src/settings.rs

@@ -19,13 +19,18 @@
 use std::{
     collections::{HashMap, HashSet},
     sync::Arc,
+    time::UNIX_EPOCH,
 };
 
 use crypto_box::{ChaChaBox, PublicKey};
 use darkfi::{Error::ParseFailed, Result};
+use darkfi_sdk::{crypto::pasta_prelude::PrimeField, pasta::pallas};
 use log::info;
 
-use crate::irc::{IrcChannel, IrcContact};
+use crate::{
+    crypto::rln::{closest_epoch, RlnIdentity},
+    irc::{IrcChannel, IrcContact},
+};
 
 /// Parse configured autojoin channels from a TOML map.
 ///
@@ -165,6 +170,88 @@ pub fn parse_configured_contacts(
     Ok((ret, Some(Arc::new(crypto_box::ChaChaBox::new(&secret.public_key(), &secret)))))
 }
 
+/// Parse configured RLN identity from a TOML map.
+///
+/// ```toml
+/// [rln]
+/// nullifier = "6EGKCm3FdSK3fySbjY19pxG49aB34poXhaepsW5NMxFB"
+/// trapdoor = "dCbf5fD2w3K9eYHA2ppgio3ui12tSMZXnEGm8dHS5x6"
+/// user_message_limit = 100
+/// ```
+pub fn parse_rln_identity(data: &toml::Value) -> Result<Option<RlnIdentity>> {
+    let Some(table) = data.as_table() else { return Err(ParseFailed("TOML not a map")) };
+    let Some(rlninfo) = table.get("rln") else { return Ok(None) };
+
+    let Some(nullifier) = rlninfo.get("nullifier") else {
+        return Err(ParseFailed("RLN identity nullifier missing"))
+    };
+
+    let Some(trapdoor) = rlninfo.get("trapdoor") else {
+        return Err(ParseFailed("RLN identity trapdoor missing"))
+    };
+
+    let Some(msglimit) = rlninfo.get("user_message_limit") else {
+        return Err(ParseFailed("RLN user message limit missing"))
+    };
+
+    // Decode
+    let identity_nullifier = if let Some(nullifier) = nullifier.as_str() {
+        let Ok(nullifier_bytes) = bs58::decode(nullifier).into_vec() else {
+            return Err(ParseFailed("RLN nullifier not valid base58"))
+        };
+
+        if nullifier_bytes.len() != 32 {
+            return Err(ParseFailed("RLN nullifier not 32 bytes long"))
+        }
+
+        let Some(identity_nullifier) =
+            pallas::Base::from_repr(nullifier_bytes.try_into().unwrap()).into()
+        else {
+            return Err(ParseFailed("RLN nullifier not a pallas base field element"))
+        };
+
+        identity_nullifier
+    } else {
+        return Err(ParseFailed("RLN nullifier not a string"))
+    };
+
+    let identity_trapdoor = if let Some(trapdoor) = trapdoor.as_str() {
+        let Ok(trapdoor_bytes) = bs58::decode(trapdoor).into_vec() else {
+            return Err(ParseFailed("RLN trapdoor not valid base58"))
+        };
+
+        if trapdoor_bytes.len() != 32 {
+            return Err(ParseFailed("RLN trapdoor not 32 bytes long"))
+        }
+
+        let Some(identity_trapdoor) =
+            pallas::Base::from_repr(trapdoor_bytes.try_into().unwrap()).into()
+        else {
+            return Err(ParseFailed("RLN trapdoor not a pallas base field element"))
+        };
+
+        identity_trapdoor
+    } else {
+        return Err(ParseFailed("RLN trapdoor not a string"))
+    };
+
+    let user_message_limit = if let Some(msglimit) = msglimit.as_float() {
+        msglimit as u64
+    } else {
+        return Err(ParseFailed("RLN user message limit not a number"))
+    };
+
+    Ok(Some(RlnIdentity {
+        nullifier: identity_nullifier,
+        trapdoor: identity_trapdoor,
+        user_message_limit,
+        // TODO: FIXME: We should probably keep track of these rather than
+        // resetting here
+        message_id: 1,
+        last_epoch: closest_epoch(UNIX_EPOCH.elapsed().unwrap().as_secs()),
+    }))
+}
+
 /// Parse a TOML string for any configured channels and return
 /// a map containing said configurations.
 ///