De-MLS

Crates.io License: MIT License: Apache 2.0

Decentralized MLS proof-of-concept that coordinates secure group membership through off-chain consensus and a Waku relay.
This repository now ships a native desktop client built with Dioxus that drives the MLS core directly.

What's Included

Crate / Path Description
de-mls (src/) Core library — MLS groups, consensus, and the delivery service layer
crates/de_mls_gateway Bridges UI commands (AppCmd) to the core runtime and streams AppEvents back
crates/ui_bridge Bootstrap glue that hosts the async command loop for desktop clients
apps/de_mls_desktop_ui Dioxus desktop UI with login, chat, stewardship, and voting flows
tests/ Integration tests for the MLS state machine and consensus paths

Prerequisites

  • Rust (stable toolchain)
  • Nim (for building libwaku) — brew install nim

Building libwaku

The project uses a local libwaku.dylib built from logos-messaging-nim:

make          # clones, builds, and copies libwaku.dylib into ./libs/

build.rs links against libs/libwaku.dylib (or libwaku.so on Linux) and embeds an rpath automatically.

Feature Flags

Feature Default Description
waku off Enables the Waku relay transport (WakuDeliveryService) and links to libwaku

The core library (de_mls) compiles and tests without libwaku present. The waku feature is required only when you need the concrete WakuDeliveryService implementation — the gateway and desktop crates enable it automatically.

# Use the transport-agnostic types only (no libwaku needed):
de_mls = { path = "..." }

# Enable the Waku transport (requires libwaku):
de_mls = { path = "...", features = ["waku"] }

Delivery Service (src/ds/)

The delivery service (DS) is the transport layer that sits between the MLS core and the network. It is fully synchronous — no tokio dependency — so it can be used from any Rust context.

Module layout

src/ds/
├── mod.rs              Public API re-exports
├── transport.rs        DeliveryService trait, OutboundPacket, InboundPacket
├── error.rs            DeliveryServiceError
├── topic_filter.rs     TopicFilter — async HashSet-based allowlist for inbound routing
└── waku/               Waku relay implementation (libwaku FFI)
    ├── mod.rs          WakuDeliveryService, WakuConfig, content-topic helpers
    ├── sys.rs          Raw C FFI bindings (trampoline pattern)
    └── wrapper.rs      Safe WakuNodeCtx wrapper (Drop calls waku_stop)

Key types

Type Feature Description
DeliveryService Trait — send() and subscribe(), both synchronous
OutboundPacket Payload + group id + subtopic + app id (self-message filter)
InboundPacket Payload + group id + subtopic + app id + timestamp
TopicFilter Async allowlist used by the gateway to filter inbound packets by group
WakuDeliveryService waku Concrete impl — runs an embedded Waku node on a background std::thread
WakuConfig waku Node port, discv5 settings
WakuStartResult waku Returned by start() — contains the service + optional local ENR

Basic usage

use de_mls::ds::{WakuDeliveryService, WakuConfig, DeliveryService, OutboundPacket};

let result = WakuDeliveryService::start(WakuConfig {
    node_port: 60000,
    discv5: true,
    discv5_udp_port: 61000,
    ..Default::default()
})?;

let ds = result.service;

// Subscribe (can be called multiple times for fan-out).
let rx = ds.subscribe();
std::thread::spawn(move || {
    while let Ok(pkt) = rx.recv() {
        println!("{} bytes from group {}", pkt.payload.len(), pkt.group_id);
    }
});

// Send a message.
ds.send(OutboundPacket::new(
    b"hello".to_vec(), "app_msg", "my-group", b"instance-id",
))?;

// Shut down explicitly, or just drop all clones.
ds.shutdown();

Threading model

WakuDeliveryService::start() spawns a "waku-node" thread that owns the libwaku context. Outbound messages are queued via std::sync::mpsc; inbound events are fanned out to all subscribers. The background thread is wrapped in catch_unwind so a panic in the FFI layer won't crash the process.

In async code (e.g. the gateway), wrap ds.send() in tokio::task::spawn_blocking to avoid blocking the tokio runtime.

Content topics

Messages are routed by Waku content topics with the format:

/{group_name}/{version}/{subtopic}/proto

Two subtopics are used: app_msg (application messages) and welcome (MLS Welcome messages for group joins). The pubsub topic is fixed to /waku/2/rs/15/1 (cluster 15, shard 1).

Self-message filtering

Each group handle generates a random UUID (app_id) stored in the Waku message meta field. On receive, the application compares packet.app_id against the local handle's id and drops self-originated messages. Waku relay (gossipsub) does not filter self-messages natively.

Quick Start

Environment Variables

Variable Required Description
NODE_PORT Yes TCP port for the embedded Waku node
DISCV5_BOOTSTRAP_ENRS No Comma-separated ENR strings for discv5 bootstrap

discv5 peer discovery is always enabled. The discv5 UDP port is derived automatically (NODE_PORT + 1000). Use a unique NODE_PORT per local client so the embedded Waku nodes do not collide.

Running Multiple Nodes For Example

Nodes on the same clusterId/shard discover each other automatically via discv5. No external relay required — just run multiple local nodes.

Node 1 (bootstrap node):

NODE_PORT=60001 cargo run -p de_mls_desktop_ui

Copy the Local ENR: enr:-QE... line from the logs.

Nodes 24 (bootstrap off node 1):

NODE_PORT=60002 DISCV5_BOOTSTRAP_ENRS="enr:-QE..." cargo run -p de_mls_desktop_ui
NODE_PORT=60003 DISCV5_BOOTSTRAP_ENRS="enr:-QE..." cargo run -p de_mls_desktop_ui
NODE_PORT=60004 DISCV5_BOOTSTRAP_ENRS="enr:-QE..." cargo run -p de_mls_desktop_ui

All nodes discover each other via the DHT and form a gossipsub relay mesh automatically.

Using the Desktop UI

  • Login screen paste an Ethereum-compatible secp256k1 private key (hex, with or without 0x) and click Enter.
    On success the app derives your wallet address, stores it in session state, and navigates to the home layout.

  • Header bar shows the derived address and allows runtime log-level changes (errortrace).
    Log files rotate daily under apps/de_mls_desktop_ui/logs/.

  • Groups panel lists every MLS group returned by the gateway.
    Use Create or Join to open a modal, enter the group name, and the UI automatically refreshes the list and opens the group.

  • Chat panel displays live conversation messages for the active group.
    Compose text messages at the bottom; the UI also offers:

    • Leave group to request a self-ban (the backend fills in your address)
    • Request ban to request ban for another user Member lists are fetched automatically when a group is opened so you can pick existing members from the ban modal.
  • Consensus panel keeps stewards and members aligned:

    • Shows whether you are a steward for the active group
    • Lists pending steward requests collected during the current epoch
    • Surfaces the proposal currently open for voting with YES/NO buttons
    • Stores the latest proposal decisions with timestamps for quick auditing

Development Tips

  • cargo test -p de_mls runs core tests (no libwaku required)
  • cargo test -p de_mls --features waku includes Waku transport tests (needs libwaku in libs/)
  • cargo fmt --all --check / cargo clippy keep formatting and linting consistent with the codebase
  • RUST_BACKTRACE=full helpful when debugging state-machine transitions during development

Logs for the desktop UI live in apps/de_mls_desktop_ui/logs/; core logs are emitted to stdout as well.

Contributing

Issues and pull requests are welcome. Please include reproduction steps, relevant logs, and test coverage where possible.

Description
No description provided
Readme 32 MiB
Languages
Rust 96%
CSS 3.5%
Makefile 0.5%