de-mls 4.0.0

Decentralized MLS — end-to-end encrypted group messaging with consensus-based membership management over gossipsub-like networks
Documentation

De-MLS

Crates.io License: MIT License: Apache 2.0

Decentralized MLS — end-to-end encrypted group messaging with consensus-based membership over gossipsub-like networks. de-mls implements the Decentralized MLS Off-Chain Consensus protocol on top of OpenMLS: MLS cryptography for the secure channel, and a hashgraph-like consensus service for proposal voting and steward election.

The library's product is a single per-conversation handle — Conversation — modeled on OpenMLS's MlsGroup. It owns every protocol decision (MLS encryption, proposal voting, steward commits, freeze timing); transport and identity stay on your side of the boundary. It runs synchronously and is generic over its consensus, scoring, and steward plug-ins.

Looking for a runnable app? An example integration — a gateway, a Waku delivery service, and a Dioxus desktop client wired onto this library — lives in the de-mls-poc repository.

What you own vs. what de-mls owns

You provide: identity (opaque member-id bytes and the map from a member to its transport address), the transport itself, the OpenMLS provider (crypto + storage), key-package minting, and the registry of conversations.

de-mls owns: the protocol — MLS commits, proposal voting, steward election, and freeze timing — along with the per-conversation state behind it: proposal queues, deduplication, the steward list, peer scores, and the Conversation state machine.

The Conversation API

use de_mls::Conversation;
use de_mls::defaults::{DefaultConsensusPlugin, DefaultPeerScoring, DefaultStewardList};

// Create a conversation you steward, or join one from a welcome:
let mut convo: Conversation<DefaultConsensusPlugin, DefaultPeerScoring, DefaultStewardList> =
    Conversation::create(id, &provider, credential, suite, &signer,
                         scoring, steward, consensus, app_id, config, member_id)?;

// let joined = Conversation::join(&provider, welcome_bytes, sync_bytes, …, &signer)?;  // Ok(None) = not for us

// Drive it once per wakeup cycle, then drain its products:
convo.process_inbound(&provider, &sender, &payload, &signer)?; // feed inbound bytes
convo.poll(&provider, &signer);                                // tick timers / freeze / commits
for event in convo.drain_events()   { /* AppMessage, WelcomeReady, PhaseChange, … */ }
for out   in convo.drain_outbound() { /* publish out.payload on your transport */ }

The conversation is pull-based: it buffers outbound for you to publish, and reports its next deadline via next_wakeup_in(), advancing when you call poll(). Membership and chat are plain methods: add_member / sponsor_member, remove_member, vote, send_message, leave.

Default plug-in implementations (consensus over hashgraph-like-consensus, in-memory peer-score storage, the deterministic steward list) live in de_mls::defaults — adopt them wholesale or swap any one.

A complete, runnable construction — creator and joiner built straight from direct arguments — is in tests/standalone_construction.rs.

Build & test

cargo build -p de-mls
cargo test  -p de-mls --release
cargo clippy -p de-mls --tests -- -D warnings
RUSTDOCFLAGS='-Dwarnings' cargo doc -p de-mls --lib --no-deps --document-private-items

Building requires the Rust toolchain (edition 2024) and protoc, which build.rs uses to compile the protobuf definitions.

Documentation

Contributing

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

License

Licensed under either of MIT or Apache-2.0 at your option.