A Rust implementation of the Marmot Protocol for secure, decentralized group messaging
MDK is a Rust library that implements the Marmot Protocol, bringing together the MLS (Messaging Layer Security) Protocol with Nostr's decentralized network to enable secure group messaging without centralized servers.
- π End-to-End Encryption: Messages encrypted using the MLS protocol with forward secrecy and post-compromise security
- π Decentralized: Built on Nostr's distributed relay network - no central servers required
- π₯ Group Messaging: Secure group creation, member management, and messaging
- π Key Management: Automatic key package generation, rotation, and distribution
- πΎ Flexible Storage: Pluggable storage backends (in-memory, SQLite, custom)
- π± Media Support: Optional encrypted media sharing (images, files) with MIP-04
- π‘οΈ Metadata Protection: Hides communication patterns and group membership
- β‘ Performance: Efficient cryptographic operations and message processing
MDK is organized into several crates for modularity and flexibility:
mdk-core: Main library with MLS implementation and Nostr integrationmdk-storage-traits: Storage abstraction layer and trait definitions
mdk-memory-storage: In-memory storage for testing and developmentmdk-sqlite-storage: SQLite-based persistent storage with migrations
MDK is built on top of OpenMLS, a robust Rust implementation of the MLS protocol. OpenMLS provides the cryptographic foundation while MDK adds Nostr-specific functionality and abstractions.
- MLS Protocol Implementation: Full RFC 9420 compliance with all cryptographic operations
- Group Management: Creating, updating, and managing MLS groups
- Key Management: Automatic key rotation, forward secrecy, and post-compromise security
- Message Processing: Encryption, decryption, and authentication of group messages
- Extensibility: Support for MLS extensions (which MDK uses for Nostr integration)
use openmls::prelude::*;
use openmls_rust_crypto::RustCrypto;
// MDK wraps OpenMLS with Nostr-specific functionality
pub struct MdkProvider<Storage> {
crypto: RustCrypto, // OpenMLS crypto provider
storage: Storage, // Custom storage abstraction
}
impl<Storage> OpenMlsProvider for MdkProvider<Storage> {
type CryptoProvider = RustCrypto;
type RandProvider = RustCrypto;
type StorageProvider = Storage::OpenMlsStorageProvider;
// ... implementation details
}MlsGroup: Core group management (wrapped by MDK's group handling)KeyPackage: Identity and key distribution (published as Nostr events)Welcome: Group invitation messages (sent via Nostr's gift-wrap)MlsMessageOut: Encrypted group messages (published as Nostr events)Credential: Identity verification (integrated with Nostr keys)
MDK configures OpenMLS with specific settings for Nostr compatibility:
// Default ciphersuite for all MDK groups
const DEFAULT_CIPHERSUITE: Ciphersuite = Ciphersuite::MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519;
// Required extensions for Nostr integration
const REQUIRED_EXTENSIONS: &[ExtensionType] = &[
ExtensionType::ApplicationId,
ExtensionType::RatchetTree,
ExtensionType::RequiredCapabilities,
ExtensionType::Unknown(0xF2EE), // Marmot Group Data Extension
];This ensures all MDK groups use compatible cryptography and include necessary Nostr-specific metadata.
Add MDK to your Cargo.toml:
[dependencies]
mdk-core = "0.5.0"
mdk-memory-storage = "0.5.0" # For in-memory storage
# OR
mdk-sqlite-storage = "0.5.0" # For persistent SQLite storagemip04: Enable encrypted media support (images, files)
[dependencies]
mdk-core = { version = "0.5.0", features = ["mip04"] }Here's a basic example of creating a group and sending messages:
use mdk_core::prelude::*;
use mdk_memory_storage::MdkMemoryStorage;
use nostr::{Keys, Kind, RelayUrl};
use nostr::event::builder::EventBuilder;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Generate identities
let alice_keys = Keys::generate();
let bob_keys = Keys::generate();
// Create MDK instances
let alice_mdk = MDK::new(MdkMemoryStorage::default());
let bob_mdk = MDK::new(MdkMemoryStorage::default());
let relay_url = RelayUrl::parse("wss://relay.example.com")?;
// Bob creates a key package
let (bob_key_package, tags) = bob_mdk
.create_key_package_for_event(&bob_keys.public_key(), [relay_url.clone()])?;
let bob_key_package_event = EventBuilder::new(Kind::MlsKeyPackage, bob_key_package)
.tags(tags)
.build(bob_keys.public_key())
.sign(&bob_keys)
.await?;
// Alice creates a group with Bob
let config = NostrGroupConfigData::new(
"Alice & Bob".to_string(),
"Private chat".to_string(),
None, // image_hash
None, // image_key
None, // image_nonce
vec![relay_url],
vec![alice_keys.public_key(), bob_keys.public_key()],
);
let group_result = alice_mdk.create_group(
&alice_keys.public_key(),
vec![bob_key_package_event],
config,
)?;
// Bob processes the welcome message
let welcome_rumor = &group_result.welcome_rumors[0];
bob_mdk.process_welcome(&nostr::EventId::all_zeros(), welcome_rumor)?;
let welcomes = bob_mdk.get_pending_welcomes()?;
bob_mdk.accept_welcome(&welcomes[0])?;
// Alice sends a message
let message_rumor = EventBuilder::new(Kind::Custom(9), "Hello Bob!")
.build(alice_keys.public_key());
let message_event = alice_mdk.create_message(
&group_result.group.mls_group_id,
message_rumor
)?;
// Bob processes the message
bob_mdk.process_message(&message_event)?;
println!("Message sent and received successfully!");
Ok(())
}use mdk_memory_storage::MdkMemoryStorage;
let mdk = MDK::new(MdkMemoryStorage::default());use mdk_sqlite_storage::MdkSqliteStorage;
let storage = MdkSqliteStorage::new("path/to/database.db").await?;
let mdk = MDK::new(storage);Implement the MdkStorageProvider trait for custom storage backends:
use mdk_storage_traits::MdkStorageProvider;
struct MyCustomStorage;
impl MdkStorageProvider for MyCustomStorage {
// Implement required methods
}Enable the mip04 feature for encrypted media support:
#[cfg(feature = "mip04")]
use mdk_core::encrypted_media::*;
// Encrypt and upload media
let media_manager = EncryptedMediaManager::new();
let encrypted_media = media_manager.encrypt_image(&image_bytes)?;Check out the examples/ directory for complete working examples:
mls_memory.rs: Full group messaging workflow with in-memory storagemls_sqlite.rs: Persistent storage example with SQLite
Run examples with:
cargo run --example mls_memory
cargo run --example mls_sqliteWe recommend using just for running tests and development tasks:
# Run all tests with all features (recommended)
just test
# Run tests without optional features
just test-no-features
# Run tests with only mip04 feature
just test-mip04
# Run all test combinations (like CI)
just test-allYou can also use cargo directly:
# Run all tests
cargo test
# Run tests with encrypted media support
cargo test --features mip04
# Run tests for specific crate
cargo test -p mdk-coreCheck test coverage across all crates:
# Generate coverage summary
just coverage
# Generate HTML coverage report
just coverage-htmlSee docs/DEVELOPMENT.md for detailed coverage documentation.
- API Documentation: docs.rs/mdk-core
- Marmot Protocol: github.com/marmot-protocol/marmot
- MLS Specification: RFC 9420
- OpenMLS Library: github.com/openmls/openmls - The MLS implementation we build upon
- Nostr Protocol: github.com/nostr-protocol/nostr
- Rust 1.90.0 or later
- SQLite (for sqlite storage tests)
- just (recommended for development)
git clone https://github.com/marmot-protocol/mdk.git
cd mdk
cargo buildWe use just as a command runner for development tasks. Install it with:
# macOS
brew install just
# Other platforms: https://github.com/casey/just#installationAvailable commands:
# List all available commands
just
# Testing
just test # Run tests with all features
just test-no-features # Run tests without optional features
just test-mip04 # Run tests with only mip04 feature
just test-all # Run all test combinations (like CI)
# Code Quality
just lint # Run clippy with all features
just lint-no-features # Run clippy without optional features
just lint-mip04 # Run clippy with only mip04 feature
just lint-all # Run clippy for all feature combinations
just fmt # Check code formatting
just docs # Check documentation
# Comprehensive Checks
just check # Run all checks (like CI)
just check-full # Full comprehensive check with all feature combinationsThe justfile ensures consistent testing across different feature combinations, which is especially important for optional features like mip04.
We welcome contributions! Please see our contributing guidelines and:
- Fork the repository
- Create a feature branch
- Make your changes
- Add tests
- Run the full test suite:
just check-full - Submit a pull request
Before submitting a PR, ensure all checks pass:
# Run comprehensive checks (recommended)
just check-full
# Or run individual checks
just fmt # Check formatting
just lint-all # Check all clippy combinations
just test-all # Run all test combinations
just docs # Check documentationFor security issues, please email j@jeffg.me instead of opening a public issue.
MDK is currently in ALPHA status. While functional, the API may change in breaking ways. Use in production is not recommended until the library reaches stable status.
Current implementations are suitable for:
- Research and development
- Proof-of-concept applications
- Contributing to protocol development
- Educational purposes
This project is licensed under the MIT License - see the LICENSE file for details.
- OpenMLS: The robust MLS protocol implementation that powers MDK's cryptographic operations
- rust-nostr: Comprehensive Nostr protocol support and event handling
- OpenMLS Team: For their excellent work on MLS protocol implementation and storage abstractions
- MLS Working Group: For developing the MLS specification (RFC 9420)
- Nostr Community: For creating a truly decentralized communication protocol
- whitenoise: Flutter app using whitenoise
- whitenoise-rs: Rust crate that implements Marmot for the White Noise app
- marmot-ts: TypeScript implementation of Marmot
Built with β€οΈ for a more private and decentralized future.