diff --git a/Cargo.lock b/Cargo.lock
index 38cf08cd4a10..fd87f967b8d9 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1957,6 +1957,7 @@ dependencies = [
"bs58",
"crypto_box",
"darkfi",
+ "darkfi-sdk",
"darkfi-serial",
"easy-parallel",
"futures",
diff --git a/bin/darkirc/Cargo.toml b/bin/darkirc/Cargo.toml
index cfdc42b0019d..004d69800a26 100644
--- a/bin/darkirc/Cargo.toml
+++ b/bin/darkirc/Cargo.toml
@@ -9,7 +9,8 @@ homepage = "https://dark.fi"
repository = "https://github.com/darkrenaissance/darkfi"
[dependencies]
-darkfi = {path = "../../", features = ["async-daemonize", "event-graph", "net", "util", "system", "rpc"]}
+darkfi = {path = "../../", features = ["async-daemonize", "event-graph", "net", "util", "system", "rpc", "zk"]}
+darkfi-sdk = {path = "../../src/sdk", features = ["async"]}
darkfi-serial = {path = "../../src/serial", features = ["async"]}
libc = "0.2.150"
diff --git a/bin/darkirc/Makefile b/bin/darkirc/Makefile
index eeea17883540..7daaa5729f8b 100644
--- a/bin/darkirc/Makefile
+++ b/bin/darkirc/Makefile
@@ -17,9 +17,17 @@ SRC = \
BIN = ../../darkirc
+ZKAS = ../../zkas
+
+ZKSRC = $(shell find proof -type f -name '*.zk')
+ZKBIN = $(ZKSRC:=.bin)
+
all: $(BIN)
-$(BIN): $(SRC)
+$(ZKBIN): $(ZKAS) $(ZKSRC)
+ $(ZKAS) $(basename $@) -o $@
+
+$(BIN): $(ZKBIN) $(SRC)
RUSTFLAGS="$(RUSTFLAGS)" $(CARGO) build --target=$(RUST_TARGET) --release --package darkirc
cp -f ../../target/$(RUST_TARGET)/release/darkirc $@
diff --git a/bin/darkirc/proof/signal.zk b/bin/darkirc/proof/signal.zk
new file mode 100644
index 000000000000..c089e7f5dd79
--- /dev/null
+++ b/bin/darkirc/proof/signal.zk
@@ -0,0 +1,38 @@
+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);
+}
diff --git a/bin/darkirc/proof/slash.zk b/bin/darkirc/proof/slash.zk
new file mode 100644
index 000000000000..19bc0f192155
--- /dev/null
+++ b/bin/darkirc/proof/slash.zk
@@ -0,0 +1,18 @@
+k = 13;
+field = "pallas";
+
+constant "RlnSlash" {}
+
+witness "RlnSlash" {
+ Base secret_key,
+ MerklePath identity_path,
+ Uint32 identity_leaf_pos,
+}
+
+circuit "RlnSlash" {
+ identity_derivation_path = witness_base(11);
+
+ identity_commit = poseidon_hash(identity_derivation_path, secret_key);
+ root = merkle_root(identity_leaf_pos, identity_path, identity_commit);
+ constrain_instance(root);
+}
diff --git a/bin/darkirc/src/main.rs b/bin/darkirc/src/main.rs
index ac67503879e6..cbaff8040762 100644
--- a/bin/darkirc/src/main.rs
+++ b/bin/darkirc/src/main.rs
@@ -45,6 +45,9 @@ use irc::server::IrcServer;
/// Cryptography utilities
mod crypto;
+// RLN
+//mod rln;
+
/// JSON-RPC methods
mod rpc;
@@ -108,7 +111,7 @@ struct Args {
pub struct DarkIrc {
/// P2P network pointer
p2p: P2pPtr,
- /// Sled DB (also used in event_graph)
+ /// Sled DB (also used in event_graph and for RLN)
sled: sled::Db,
/// Event Graph instance
event_graph: EventGraphPtr,
diff --git a/bin/darkirc/src/rln.rs b/bin/darkirc/src/rln.rs
new file mode 100644
index 000000000000..3235044265a6
--- /dev/null
+++ b/bin/darkirc/src/rln.rs
@@ -0,0 +1,113 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2020-2023 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 .
+ */
+
+//!
+
+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 {
+ 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(100);
+ 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
+ }
+}