Sfoglia il codice sorgente

bin/darkirc: move shared code to darkirc lib and fix darkirc plugin methods

dasman 1 mese fa
parent
commit
d7ae52d3b3

+ 176 - 9
bin/app/Cargo.lock

@@ -25,7 +25,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"
 dependencies = [
  "cfg-if",
- "cipher",
+ "cipher 0.4.4",
  "cpufeatures 0.2.17",
  "zeroize",
 ]
@@ -775,6 +775,19 @@ version = "1.8.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06"
 
+[[package]]
+name = "bcrypt"
+version = "0.19.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24ae5479c93d3720e4c1dbd6b945b97457c50cb672781104768190371df1a905"
+dependencies = [
+ "base64",
+ "blowfish",
+ "getrandom 0.4.2",
+ "subtle",
+ "zeroize",
+]
+
 [[package]]
 name = "bincode"
 version = "2.0.1"
@@ -910,6 +923,16 @@ dependencies = [
  "piper",
 ]
 
+[[package]]
+name = "blowfish"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62ce3946557b35e71d1bbe07ec385073ce9eda05043f95de134eb578fcf1a298"
+dependencies = [
+ "byteorder",
+ "cipher 0.5.2",
+]
+
 [[package]]
 name = "bridgetree"
 version = "0.7.0"
@@ -1082,7 +1105,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818"
 dependencies = [
  "cfg-if",
- "cipher",
+ "cipher 0.4.4",
  "cpufeatures 0.2.17",
 ]
 
@@ -1094,7 +1117,7 @@ checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35"
 dependencies = [
  "aead",
  "chacha20",
- "cipher",
+ "cipher 0.4.4",
  "poly1305",
  "zeroize",
 ]
@@ -1120,10 +1143,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
 dependencies = [
  "crypto-common 0.1.7",
- "inout",
+ "inout 0.1.4",
  "zeroize",
 ]
 
+[[package]]
+name = "cipher"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e8cf2a2c93cd704877c0858356ed03480ff301ee950b43f1cbe4573b088bfa6c"
+dependencies = [
+ "crypto-common 0.2.2",
+ "inout 0.2.2",
+]
+
 [[package]]
 name = "clap"
 version = "2.34.0"
@@ -1466,6 +1499,37 @@ dependencies = [
  "hybrid-array",
 ]
 
+[[package]]
+name = "crypto_box"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "16182b4f39a82ec8a6851155cc4c0cda3065bb1db33651726a29e1951de0f009"
+dependencies = [
+ "aead",
+ "chacha20",
+ "crypto_secretbox",
+ "curve25519-dalek",
+ "salsa20",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "crypto_secretbox"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9d6cf87adf719ddf43a805e92c6870a531aedda35ff640442cbaf8674e141e1"
+dependencies = [
+ "aead",
+ "chacha20",
+ "cipher 0.4.4",
+ "generic-array",
+ "poly1305",
+ "salsa20",
+ "subtle",
+ "zeroize",
+]
+
 [[package]]
 name = "csv"
 version = "1.4.0"
@@ -1499,7 +1563,7 @@ version = "0.9.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835"
 dependencies = [
- "cipher",
+ "cipher 0.4.4",
 ]
 
 [[package]]
@@ -1600,6 +1664,7 @@ dependencies = [
  "darkfi-sdk",
  "darkfi-serial",
  "darkfi_money_contract",
+ "darkirc",
  "dirs 5.0.1",
  "drk",
  "easy-parallel",
@@ -1745,6 +1810,41 @@ dependencies = [
  "tracing",
 ]
 
+[[package]]
+name = "darkirc"
+version = "0.5.1"
+dependencies = [
+ "async-trait",
+ "bcrypt",
+ "blake3",
+ "bs58",
+ "crypto_box",
+ "darkfi",
+ "darkfi-sdk",
+ "darkfi-serial",
+ "easy-parallel",
+ "futures",
+ "futures-rustls",
+ "libc",
+ "openssl",
+ "rand 0.8.6",
+ "rusqlite",
+ "rustls-pemfile",
+ "semver",
+ "serde",
+ "signal-hook",
+ "signal-hook-async-std",
+ "sled-overlay",
+ "smol",
+ "structopt",
+ "structopt-toml",
+ "toml 0.9.12+spec-1.1.0",
+ "tracing",
+ "tracing-appender",
+ "tracing-subscriber",
+ "url",
+]
+
 [[package]]
 name = "darling"
 version = "0.14.4"
@@ -2672,6 +2772,21 @@ dependencies = [
  "yeslogic-fontconfig-sys",
 ]
 
+[[package]]
+name = "foreign-types"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
+dependencies = [
+ "foreign-types-shared",
+]
+
+[[package]]
+name = "foreign-types-shared"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
+
 [[package]]
 name = "form_urlencoded"
 version = "1.2.2"
@@ -3593,6 +3708,15 @@ dependencies = [
  "generic-array",
 ]
 
+[[package]]
+name = "inout"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4250ce6452e92010fdf7268ccc5d14faa80bb12fc741938534c58f16804e03c7"
+dependencies = [
+ "hybrid-array",
+]
+
 [[package]]
 name = "instant"
 version = "0.1.13"
@@ -4510,6 +4634,31 @@ version = "0.3.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
 
+[[package]]
+name = "openssl"
+version = "0.10.80"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a45fa2aa886c42762255da344f0a0d313e254066c46aad76f300c3d3da62d967"
+dependencies = [
+ "bitflags 2.13.0",
+ "cfg-if",
+ "foreign-types",
+ "libc",
+ "openssl-macros",
+ "openssl-sys",
+]
+
+[[package]]
+name = "openssl-macros"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+]
+
 [[package]]
 name = "openssl-src"
 version = "300.6.0+3.6.2"
@@ -5705,6 +5854,15 @@ dependencies = [
  "zeroize",
 ]
 
+[[package]]
+name = "rustls-pemfile"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50"
+dependencies = [
+ "rustls-pki-types",
+]
+
 [[package]]
 name = "rustls-pki-types"
 version = "1.14.1"
@@ -5751,6 +5909,15 @@ dependencies = [
  "thiserror 2.0.18",
 ]
 
+[[package]]
+name = "salsa20"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213"
+dependencies = [
+ "cipher 0.4.4",
+]
+
 [[package]]
 name = "same-file"
 version = "1.0.6"
@@ -6274,7 +6441,7 @@ version = "0.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "125c5795103fc93fced42d123c8044180afc55469caa1ab56487c3c5543c898d"
 dependencies = [
- "cipher",
+ "cipher 0.4.4",
  "ssh-encoding-fork-arti",
 ]
 
@@ -7371,7 +7538,7 @@ version = "0.42.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "35f1db46a8bd6307f104dcc0a11f56a5b05f5823cc9d11e1748402daa940d3c7"
 dependencies = [
- "cipher",
+ "cipher 0.4.4",
  "data-encoding",
  "derive-deftly",
  "derive_more",
@@ -7689,7 +7856,7 @@ checksum = "1505b69e589e042276da708f7baa44d9f16ec50074a3f54ebb3bcbbcd6c4d9aa"
 dependencies = [
  "amplify",
  "base64ct",
- "cipher",
+ "cipher 0.4.4",
  "derive-deftly",
  "derive_builder_fork_arti",
  "derive_more",
@@ -7773,7 +7940,7 @@ dependencies = [
  "bytes",
  "caret",
  "cfg-if",
- "cipher",
+ "cipher 0.4.4",
  "coarsetime",
  "derive-deftly",
  "derive_builder_fork_arti",

+ 1 - 0
bin/app/Cargo.toml

@@ -19,6 +19,7 @@ glam = "0.30.9"
 #zmq = "0.10.0"
 #async_zmq = "0.4.0"
 zeromq = { version = "0.4.1", default-features = false, features = ["async-std-runtime", "all-transport"] }
+darkirc = { path = "../darkirc" }
 darkfi = {path = "../../", features = ["async-daemonize", "event-graph", "net", "util", "system", "zk"]}
 darkfi-sdk = {path = "../../src/sdk", features = ["async"]}
 darkfi-serial = {path = "../../src/serial", features = ["async"]}

+ 7 - 0
bin/app/Makefile

@@ -6,6 +6,13 @@ CARGO = cargo
 RELEASE_APK = target/android-artifacts/release/apk/darkfi-app.apk
 DEBUG_APK = target/android-artifacts/debug/apk/darkfi-app.apk
 
+# zkas compiler binary
+ZKAS = ../../zkas
+
+# zkas circuits
+PROOFS_SRC = $(shell find ../../src/event_graph -type f -name '*.zk')
+PROOFS_BIN = $(PROOFS_SRC:=.bin)
+
 #ADB_DEVICE = -s DEVICE_ID
 ADB_DEVICE =
 

+ 2 - 2
bin/app/src/app/schema/chat.rs

@@ -17,11 +17,11 @@
  */
 
 use darkfi_serial::Encodable;
+use irc2::Privmsg;
 use sled_overlay::sled;
 use std::{sync::Arc, time::UNIX_EPOCH};
 
 #[cfg(feature = "enable-plugins")]
-use crate::plugin::darkirc;
 use crate::{
     app::{
         node::{
@@ -958,7 +958,7 @@ pub async fn make(
             let nick = darkirc.get_property_str("nick").unwrap();
             #[cfg(feature = "enable-plugins")]
             {
-                let msg = darkirc::Privmsg::new(channel, nick, text);
+                let msg = Privmsg { version: 0, msg_type: 0, channel, nick, msg: text };
                 let mut data = vec![];
                 timest.encode(&mut data).unwrap();
                 msg.channel.encode(&mut data).unwrap();

+ 147 - 37
bin/app/src/plugin/darkirc.rs

@@ -17,11 +17,13 @@
  */
 
 use std::{
+    collections::HashMap,
     io::Cursor,
     sync::{Arc, Mutex as SyncMutex, OnceLock, Weak},
     time::UNIX_EPOCH,
 };
 
+use async_lock::RwLock;
 use async_trait::async_trait;
 use darkfi::{
     event_graph::{
@@ -41,6 +43,11 @@ use darkfi_serial::{
     deserialize_async, serialize, serialize_async, AsyncEncodable, Decodable, Encodable,
     SerialDecodable, SerialEncodable,
 };
+use irc2::{
+    crypto::saltbox,
+    irc::{server::MAX_NICK_LEN, IrcChannel, IrcContact},
+    pad, unpad, Privmsg,
+};
 use sled_overlay::sled;
 
 use crate::{
@@ -123,30 +130,6 @@ macro_rules! i { ($($arg:tt)*) => { info!(target: "plugin::darkirc", $($arg)*);
 macro_rules! e { ($($arg:tt)*) => { error!(target: "plugin::darkirc", $($arg)*); } }
 macro_rules! w { ($($arg:tt)*) => { warn!(target: "plugin::darkirc", $($arg)*); } }
 
-#[derive(Clone, Debug, SerialEncodable, SerialDecodable)]
-pub struct Privmsg {
-    pub channel: String,
-    pub nick: String,
-    pub msg: String,
-}
-
-impl Privmsg {
-    pub fn new(channel: String, nick: String, msg: String) -> Self {
-        Self { channel, nick, msg }
-    }
-
-    pub fn msg_id(&self, timest: u64) -> MessageId {
-        let mut hasher = blake3::Hasher::new();
-        0u8.encode(&mut hasher).unwrap();
-        0u8.encode(&mut hasher).unwrap();
-        timest.encode(&mut hasher).unwrap();
-        self.channel.encode(&mut hasher).unwrap();
-        self.nick.encode(&mut hasher).unwrap();
-        self.msg.encode(&mut hasher).unwrap();
-        MessageId(hasher.finalize().into())
-    }
-}
-
 struct SeenMsg {
     id: MessageId,
     is_self: bool,
@@ -183,6 +166,11 @@ pub struct DarkIrc {
     seen_msgs: SyncMutex<SeenMessages>,
     nick: PropertyStr,
 
+    /// Configured channels
+    pub channels: RwLock<HashMap<String, IrcChannel>>,
+    /// Configured contacts
+    pub contacts: RwLock<HashMap<String, IrcContact>>,
+
     settings: PluginSettings,
 }
 
@@ -266,7 +254,7 @@ impl DarkIrc {
             db.clone(),
             std::path::PathBuf::new(),
             false,
-            "darkirc_dag",
+            false, // TODO: should be configurable
             1,
             ex.clone(),
         )
@@ -292,6 +280,10 @@ impl DarkIrc {
 
             seen_msgs: SyncMutex::new(SeenMessages::new()),
             nick,
+
+            channels: RwLock::new(HashMap::new()),
+            contacts: RwLock::new(HashMap::new()),
+
             settings,
         });
         self_.clone().start(sg_root, ex).await;
@@ -337,7 +329,8 @@ impl DarkIrc {
             }
 
             i!("Syncing event DAG (attempt #{sync_attempt})");
-            match self.event_graph.dag_sync().await {
+            // TODO: sync_selected args should be configurable
+            match self.event_graph.sync_selected(24, false).await {
                 Ok(()) => break,
                 Err(e) => {
                     // TODO: Maybe at this point we should prune or something?
@@ -381,8 +374,11 @@ impl DarkIrc {
                 }
             };
 
-            let mut timest = ev.timestamp;
-            let msg_id = privmsg.msg_id(timest);
+            // TODO: decrypt messages here:
+            // self.try_decrypt(&mut privmsg, &self.nick.get()).await;
+
+            let mut timest = ev.header.timestamp;
+            let msg_id = msg_id(&privmsg, timest);
             t!(
                 "Relaying ev_id={:?}, ev={ev:?}, msg_id={msg_id}, privmsg={privmsg:?}, timest={timest}",
                 ev.id(),
@@ -396,7 +392,7 @@ impl DarkIrc {
                         is_self = msg.is_self;
 
                         if !msg.is_self || msg.seen_times > 1 {
-                            warn!(target: "plugin::darkirc", "Skipping duplicate seen message: {msg_id}");
+                            w!("Skipping duplicate seen message: {msg_id}");
                             continue
                         }
                     }
@@ -417,11 +413,11 @@ impl DarkIrc {
             // Strip off starting #
             let mut channel = privmsg.channel;
             if channel.is_empty() {
-                warn!(target: "plugin::darkirc", "Received privmsg with empty channel!");
+                w!("Received privmsg with empty channel!");
                 continue
             }
             if channel.chars().next().unwrap() != '#' {
-                warn!(target: "plugin::darkirc", "Skipping encrypted channel: {channel}");
+                w!("Skipping encrypted channel: {channel}");
                 continue
             }
             channel.remove(0);
@@ -481,11 +477,13 @@ impl DarkIrc {
 
         // Send text to channel
         d!("Sending privmsg: {timest} {channel}: <{nick}> {msg}");
-        let msg = Privmsg::new(channel, nick, msg);
+        let msg = Privmsg { version: 0, msg_type: 0, channel, nick, msg };
+        // TODO: messages should be encrypted here with:
+        // self.try_encrypt(&mut msg).await;
         let evgr = self.event_graph.clone();
         let mut event = event_graph::Event::new(serialize_async(&msg).await, &evgr).await;
-        event.timestamp = timest;
-        let msg_id = msg.msg_id(timest);
+        event.header.timestamp = timest;
+        let msg_id = msg_id(&msg, timest);
 
         // Keep track of our own messages so we don't apply timestamp correction to them
         // which messes up the msg id.
@@ -501,12 +499,13 @@ impl DarkIrc {
         msg.msg.encode_async(&mut arg_data).await.unwrap();
 
         // Broadcast the msg
-
-        if let Err(e) = evgr.dag_insert(&[event.clone()]).await {
+        let current_genesis = self.event_graph.current_genesis.read().await;
+        let dag_name = current_genesis.header.timestamp.to_string();
+        if let Err(e) = evgr.dag_insert(&[event.clone()], &dag_name).await {
             error!(target: "darkirc", "Failed inserting new event to DAG: {}", e);
         }
 
-        self.p2p.broadcast(&EventPut(event)).await;
+        self.p2p.broadcast(&EventPut(event, vec![])).await;
     }
 
     async fn apply_settings(self_: Arc<Self>, _: BatchGuardPtr) {
@@ -622,4 +621,115 @@ impl DarkIrc {
         tasks.append(&mut on_modify.tasks);
         self.tasks.set(tasks).unwrap();
     }
+
+    /// Try encrypting a given `Privmsg` if there is such a channel/contact.
+    pub async fn try_encrypt(&self, privmsg: &mut Privmsg) {
+        if let Some((name, channel)) = self.channels.read().await.get_key_value(&privmsg.channel) {
+            if let Some(saltbox) = &channel.saltbox {
+                // We will use a dummy channel value of MAX_NICK_LEN,
+                // since its not used, so all encrypted messages look the same.
+                privmsg.channel = saltbox::encrypt(saltbox, &[0x00; MAX_NICK_LEN]);
+                // We will pad the name to MAX_NICK_LEN so they all look the same
+                privmsg.nick = saltbox::encrypt(saltbox, &pad(&privmsg.nick));
+                privmsg.msg = saltbox::encrypt(saltbox, privmsg.msg.as_bytes());
+                d!("Successfully encrypted message for {name}");
+                return
+            }
+        };
+
+        if let Some((name, contact)) = self.contacts.read().await.get_key_value(&privmsg.channel) {
+            // We will use dummy channel and nick values of MAX_NICK_LEN,
+            // since they are not used, so all encrypted messages look the same.
+            privmsg.channel = saltbox::encrypt(&contact.saltbox, &[0x00; MAX_NICK_LEN]);
+            // We will encrypt the dummy nick value using our own self saltbox,
+            // so we can identify our messages.
+            privmsg.nick = saltbox::encrypt(&contact.self_saltbox, &[0x00; MAX_NICK_LEN]);
+            privmsg.msg = saltbox::encrypt(&contact.saltbox, privmsg.msg.as_bytes());
+            d!("Successfully encrypted message for {name}");
+        };
+    }
+
+    /// Try decrypting a given potentially encrypted `Privmsg` object.
+    pub async fn try_decrypt(&self, privmsg: &mut Privmsg, self_nickname: &str) {
+        // If all fields have base58, then we can consider decrypting.
+        let channel_ciphertext = match bs58::decode(&privmsg.channel).into_vec() {
+            Ok(v) => v,
+            Err(_) => return,
+        };
+
+        let nick_ciphertext = match bs58::decode(&privmsg.nick).into_vec() {
+            Ok(v) => v,
+            Err(_) => return,
+        };
+
+        let msg_ciphertext = match bs58::decode(&privmsg.msg).into_vec() {
+            Ok(v) => v,
+            Err(_) => return,
+        };
+
+        // Now go through all 3 ciphertexts. We'll use intermediate buffers
+        // for decryption, if all passes, we will return a modified
+        // (i.e. decrypted) privmsg, otherwise we return the original.
+        for (name, channel) in self.channels.read().await.iter() {
+            let Some(saltbox) = &channel.saltbox else { continue };
+
+            if saltbox::try_decrypt(saltbox, &channel_ciphertext).is_none() {
+                continue
+            };
+
+            let Some(mut nick_dec) = saltbox::try_decrypt(saltbox, &nick_ciphertext) else {
+                w!("Could not decrypt nick ciphertext for channel: {name}");
+                continue
+            };
+
+            let Some(msg_dec) = saltbox::try_decrypt(saltbox, &msg_ciphertext) else {
+                w!("Could not decrypt message ciphertext for channel: {name}");
+                continue
+            };
+
+            unpad(&mut nick_dec);
+
+            privmsg.channel = name.to_string();
+            privmsg.nick = String::from_utf8_lossy(&nick_dec).into();
+            privmsg.msg = String::from_utf8_lossy(&msg_dec).into();
+            d!("Successfully decrypted message for {name}");
+            return
+        }
+
+        for (name, contact) in self.contacts.read().await.iter() {
+            if saltbox::try_decrypt(&contact.saltbox, &channel_ciphertext).is_none() {
+                continue
+            };
+
+            // Since everyone encrypts the dummy nick value with their self saltbox,
+            // we try to decrypt using our, to identify our messages.
+            let nick = if saltbox::try_decrypt(&contact.self_saltbox, &nick_ciphertext).is_some() {
+                String::from(self_nickname)
+            } else {
+                name.to_string()
+            };
+
+            let Some(msg_dec) = saltbox::try_decrypt(&contact.saltbox, &msg_ciphertext) else {
+                w!("Could not decrypt message ciphertext for contact: {name}");
+                continue
+            };
+
+            privmsg.channel = name.to_string();
+            privmsg.nick = nick;
+            privmsg.msg = String::from_utf8_lossy(&msg_dec).into();
+            d!("Successfully decrypted message from {name}");
+            return
+        }
+    }
+}
+
+pub fn msg_id(privmsg: &Privmsg, timest: u64) -> MessageId {
+    let mut hasher = blake3::Hasher::new();
+    0u8.encode(&mut hasher).unwrap();
+    0u8.encode(&mut hasher).unwrap();
+    timest.encode(&mut hasher).unwrap();
+    privmsg.channel.encode(&mut hasher).unwrap();
+    privmsg.nick.encode(&mut hasher).unwrap();
+    privmsg.msg.encode(&mut hasher).unwrap();
+    MessageId(hasher.finalize().into())
 }

+ 13 - 2
bin/app/src/text/mod.rs

@@ -65,7 +65,18 @@ pub fn make_layout(
     width: Option<f32>,
     underlines: &[Range<usize>],
 ) -> parley::Layout<Color> {
-    make_layout2(text, text_color, font_size, lineheight, window_scale, width, underlines, &[], "start", "normal")
+    make_layout2(
+        text,
+        text_color,
+        font_size,
+        lineheight,
+        window_scale,
+        width,
+        underlines,
+        &[],
+        "start",
+        "normal",
+    )
 }
 
 pub fn make_layout2(
@@ -117,7 +128,7 @@ pub fn make_layout2(
 
         let mut layout: parley::Layout<Color> = builder.build(text);
         layout.break_all_lines(width);
-        layout.align(width, text_align, parley::AlignmentOptions::default());
+        layout.align(text_align, parley::AlignmentOptions::default());
         layout
     })
 }

+ 2 - 2
bin/darkirc/Cargo.toml

@@ -9,8 +9,8 @@ homepage = "https://dark.fi"
 repository = "https://codeberg.org/darkrenaissance/darkfi"
 
 [lib]
-name = "darkirc"
-crate-type = ["cdylib"]
+name = "irc2"
+crate-type = ["cdylib", "rlib"]
 path = "src/lib.rs"
 
 [[bin]]

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

@@ -51,9 +51,12 @@ use tracing::{debug, error, warn};
 
 use super::{
     server::{IrcServer, MAX_MSG_LEN},
-    NickServ, Privmsg, SERVER_NAME,
+    NickServ, SERVER_NAME,
+};
+use crate::{
+    crypto::rln::{RlnIdentity, RLN2_SIGNAL_ZKBIN},
+    Privmsg,
 };
-use crate::crypto::rln::{RlnIdentity, RLN2_SIGNAL_ZKBIN};
 
 const PENALTY_LIMIT: usize = 5;
 

+ 6 - 17
bin/darkirc/src/irc/mod.rs

@@ -19,37 +19,26 @@
 use std::{collections::HashSet, sync::Arc};
 
 use crypto_box::ChaChaBox;
-use darkfi_serial::{async_trait, SerialDecodable, SerialEncodable};
 
 /// IRC client state
-pub(crate) mod client;
+pub mod client;
 
 /// IRC server implementation
-pub(crate) mod server;
+pub mod server;
 
 /// IRC command handler
-pub(crate) mod command;
+pub mod command;
 
 /// Services implementations
-pub(crate) mod services;
-pub(crate) use services::nickserv::NickServ;
+pub mod services;
+pub use services::nickserv::NickServ;
 
 /// IRC numerics and server replies
-pub(crate) mod rpl;
+pub mod rpl;
 
 /// Hardcoded server name
 const SERVER_NAME: &str = "irc.dark.fi";
 
-/// IRC PRIVMSG (new version)
-#[derive(Clone, Debug, SerialEncodable, SerialDecodable)]
-pub struct Privmsg {
-    pub version: u8,
-    pub msg_type: u8,
-    pub channel: String,
-    pub nick: String,
-    pub msg: String,
-}
-
 /// IRC channel definition
 #[derive(Clone)]
 pub struct IrcChannel {

+ 5 - 17
bin/darkirc/src/irc/server.rs

@@ -48,18 +48,19 @@ use url::Url;
 use super::{
     client::Client,
     services::nickserv::{ACCOUNTS_DB_PREFIX, ACCOUNTS_KEY_RLN_IDENTITY},
-    IrcChannel, IrcContact, Privmsg,
+    IrcChannel, IrcContact,
 };
 use crate::{
     crypto::{
         rln::{RlnIdentity, RLN2_REGISTER_ZKBIN, RLN2_SIGNAL_ZKBIN},
         saltbox,
     },
+    pad,
     settings::{
         parse_autojoin_channels, parse_configured_channels, parse_configured_contacts,
         parse_rln_identity,
     },
-    DarkIrc,
+    unpad, DarkIrc, Privmsg,
 };
 
 /// Max channel/nick length
@@ -369,19 +370,6 @@ impl IrcServer {
         Ok(())
     }
 
-    fn pad(string: &str) -> Vec<u8> {
-        let mut bytes = string.as_bytes().to_vec();
-        bytes.resize(MAX_NICK_LEN, 0x00);
-        bytes
-    }
-
-    fn unpad(vec: &mut Vec<u8>) {
-        if let Some(i) = vec.iter().rposition(|x| *x != 0) {
-            let new_len = i + 1;
-            vec.truncate(new_len);
-        }
-    }
-
     /// Try encrypting a given `Privmsg` if there is such a channel/contact.
     pub async fn try_encrypt(&self, privmsg: &mut Privmsg) {
         if let Some((name, channel)) = self.channels.read().await.get_key_value(&privmsg.channel) {
@@ -390,7 +378,7 @@ impl IrcServer {
                 // since its not used, so all encrypted messages look the same.
                 privmsg.channel = saltbox::encrypt(saltbox, &[0x00; MAX_NICK_LEN]);
                 // We will pad the name to MAX_NICK_LEN so they all look the same
-                privmsg.nick = saltbox::encrypt(saltbox, &Self::pad(&privmsg.nick));
+                privmsg.nick = saltbox::encrypt(saltbox, &pad(&privmsg.nick));
                 privmsg.msg = saltbox::encrypt(saltbox, privmsg.msg.as_bytes());
                 debug!("Successfully encrypted message for {name}");
                 return
@@ -447,7 +435,7 @@ impl IrcServer {
                 continue
             };
 
-            Self::unpad(&mut nick_dec);
+            unpad(&mut nick_dec);
 
             privmsg.channel = name.to_string();
             privmsg.nick = String::from_utf8_lossy(&nick_dec).into();

+ 85 - 0
bin/darkirc/src/lib.rs

@@ -15,3 +15,88 @@
  * 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 sled_overlay::sled;
+use smol::lock::Mutex;
+use std::{collections::HashSet, path::PathBuf};
+
+use darkfi::{
+    event_graph::EventGraphPtr, net::P2pPtr, rpc::jsonrpc::JsonSubscriber, system::StoppableTaskPtr,
+};
+use darkfi_serial::{async_trait, SerialDecodable, SerialEncodable};
+
+/// IRC server and client handler implementation
+pub mod irc;
+use irc::server::IrcServer;
+
+use crate::irc::server::MAX_NICK_LEN;
+
+/// Cryptography utilities
+pub mod crypto;
+
+/// JSON-RPC methods
+pub mod rpc;
+
+/// Settings utilities
+pub mod settings;
+
+/// IRC PRIVMSG
+#[derive(Clone, Debug, SerialEncodable, SerialDecodable)]
+pub struct Privmsg {
+    pub version: u8,
+    pub msg_type: u8,
+    pub channel: String,
+    pub nick: String,
+    pub msg: String,
+}
+
+pub struct DarkIrc {
+    /// P2P network pointer
+    p2p: P2pPtr,
+    /// Sled DB (also used in event_graph and for RLN)
+    sled: sled::Db,
+    /// Event Graph instance
+    event_graph: EventGraphPtr,
+    /// JSON-RPC connection tracker
+    rpc_connections: Mutex<HashSet<StoppableTaskPtr>>,
+    /// dnet JSON-RPC subscriber
+    dnet_sub: JsonSubscriber,
+    /// deg JSON-RPC subscriber
+    deg_sub: JsonSubscriber,
+    /// Replay logs (DB) path
+    replay_datastore: PathBuf,
+}
+
+impl DarkIrc {
+    pub fn new(
+        p2p: P2pPtr,
+        sled: sled::Db,
+        event_graph: EventGraphPtr,
+        dnet_sub: JsonSubscriber,
+        deg_sub: JsonSubscriber,
+        replay_datastore: PathBuf,
+    ) -> Self {
+        Self {
+            p2p,
+            sled,
+            event_graph,
+            rpc_connections: Mutex::new(HashSet::new()),
+            dnet_sub,
+            deg_sub,
+            replay_datastore,
+        }
+    }
+}
+
+pub fn pad(string: &str) -> Vec<u8> {
+    let mut bytes = string.as_bytes().to_vec();
+    bytes.resize(MAX_NICK_LEN, 0x00);
+    bytes
+}
+
+pub fn unpad(vec: &mut Vec<u8>) {
+    if let Some(i) = vec.iter().rposition(|x| *x != 0) {
+        let new_len = i + 1;
+        vec.truncate(new_len);
+    }
+}

+ 10 - 56
bin/darkirc/src/main.rs

@@ -16,7 +16,7 @@
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
 
-use std::{collections::HashSet, io::Write, path::PathBuf, sync::Arc};
+use std::{io::Write, sync::Arc};
 
 use darkfi::{
     async_daemonize, cli_desc,
@@ -27,16 +27,22 @@ use darkfi::{
         server::{listen_and_serve, RequestHandler},
         settings::{RpcSettings, RpcSettingsOpt},
     },
-    system::{sleep, StoppableTask, StoppableTaskPtr, Subscription},
+    system::{sleep, StoppableTask, Subscription},
     util::path::{expand_path, get_config_path},
     Error, Result,
 };
 use darkfi_sdk::crypto::pasta_prelude::PrimeField;
 
+use irc2::{
+    crypto::{bcrypt::bcrypt_hash_password, rln::RlnIdentity},
+    irc::server::IrcServer,
+    settings::list_configured_contacts,
+    DarkIrc,
+};
+
 use rand::rngs::OsRng;
-use settings::list_configured_contacts;
 use sled_overlay::sled;
-use smol::{fs, lock::Mutex, stream::StreamExt, Executor};
+use smol::{fs, stream::StreamExt, Executor};
 use structopt_toml::{serde::Deserialize, structopt::StructOpt, StructOptToml};
 use tracing::{debug, error, info};
 use url::Url;
@@ -44,20 +50,6 @@ use url::Url;
 const CONFIG_FILE: &str = "darkirc_config.toml";
 const CONFIG_FILE_CONTENTS: &str = include_str!("../darkirc_config.toml");
 
-/// IRC server and client handler implementation
-mod irc;
-use irc::server::IrcServer;
-
-/// Cryptography utilities
-mod crypto;
-use crypto::{bcrypt::bcrypt_hash_password, rln::RlnIdentity};
-
-/// JSON-RPC methods
-mod rpc;
-
-/// Settings utilities
-mod settings;
-
 fn panic_hook(panic_info: &std::panic::PanicHookInfo) {
     error!("panic occurred: {panic_info}");
     error!("{}", std::backtrace::Backtrace::force_capture());
@@ -155,44 +147,6 @@ struct Args {
     rpc: RpcSettingsOpt,
 }
 
-pub struct DarkIrc {
-    /// P2P network pointer
-    p2p: P2pPtr,
-    /// Sled DB (also used in event_graph and for RLN)
-    sled: sled::Db,
-    /// Event Graph instance
-    event_graph: EventGraphPtr,
-    /// JSON-RPC connection tracker
-    rpc_connections: Mutex<HashSet<StoppableTaskPtr>>,
-    /// dnet JSON-RPC subscriber
-    dnet_sub: JsonSubscriber,
-    /// deg JSON-RPC subscriber
-    deg_sub: JsonSubscriber,
-    /// Replay logs (DB) path
-    replay_datastore: PathBuf,
-}
-
-impl DarkIrc {
-    fn new(
-        p2p: P2pPtr,
-        sled: sled::Db,
-        event_graph: EventGraphPtr,
-        dnet_sub: JsonSubscriber,
-        deg_sub: JsonSubscriber,
-        replay_datastore: PathBuf,
-    ) -> Self {
-        Self {
-            p2p,
-            sled,
-            event_graph,
-            rpc_connections: Mutex::new(HashSet::new()),
-            dnet_sub,
-            deg_sub,
-            replay_datastore,
-        }
-    }
-}
-
 // #[global_allocator]
 // static GLOBAL: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc;