Skip to content

marmot-protocol/mdk

Repository files navigation

🦫 MDK - Marmot Development Kit

A Rust implementation of the Marmot Protocol for secure, decentralized group messaging

CI Coverage License: MIT

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.

πŸš€ Features

  • πŸ”’ 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

πŸ“¦ Architecture

MDK is organized into several crates for modularity and flexibility:

Core Crates

  • mdk-core: Main library with MLS implementation and Nostr integration
  • mdk-storage-traits: Storage abstraction layer and trait definitions

Storage Providers

  • mdk-memory-storage: In-memory storage for testing and development
  • mdk-sqlite-storage: SQLite-based persistent storage with migrations

πŸ” OpenMLS Integration

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.

What OpenMLS Provides

  • 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)

How MDK Uses OpenMLS

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
}

Key OpenMLS Components in MDK

  • 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)

Ciphersuite and Extensions

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.

πŸ”§ Installation

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 storage

Feature Flags

  • mip04: Enable encrypted media support (images, files)
[dependencies]
mdk-core = { version = "0.5.0", features = ["mip04"] }

πŸš€ Quick Start

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(())
}

πŸ’Ύ Storage Options

In-Memory Storage (Development)

use mdk_memory_storage::MdkMemoryStorage;

let mdk = MDK::new(MdkMemoryStorage::default());

SQLite Storage (Production)

use mdk_sqlite_storage::MdkSqliteStorage;

let storage = MdkSqliteStorage::new("path/to/database.db").await?;
let mdk = MDK::new(storage);

Custom Storage

Implement the MdkStorageProvider trait for custom storage backends:

use mdk_storage_traits::MdkStorageProvider;

struct MyCustomStorage;

impl MdkStorageProvider for MyCustomStorage {
    // Implement required methods
}

πŸ–ΌοΈ Encrypted Media (MIP-04)

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)?;

πŸ” Examples

Check out the examples/ directory for complete working examples:

Run examples with:

cargo run --example mls_memory
cargo run --example mls_sqlite

πŸ§ͺ Testing

We 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-all

You 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-core

Test Coverage

Check test coverage across all crates:

# Generate coverage summary
just coverage

# Generate HTML coverage report
just coverage-html

See docs/DEVELOPMENT.md for detailed coverage documentation.

πŸ“š Documentation

πŸ› οΈ Development

Prerequisites

  • Rust 1.90.0 or later
  • SQLite (for sqlite storage tests)
  • just (recommended for development)

Building

git clone https://github.com/marmot-protocol/mdk.git
cd mdk
cargo build

Development Commands (justfile)

We use just as a command runner for development tasks. Install it with:

# macOS
brew install just

# Other platforms: https://github.com/casey/just#installation

Available 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 combinations

The justfile ensures consistent testing across different feature combinations, which is especially important for optional features like mip04.

🀝 Contributing

We welcome contributions! Please see our contributing guidelines and:

  1. Fork the repository
  2. Create a feature branch
  3. Make your changes
  4. Add tests
  5. Run the full test suite: just check-full
  6. 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 documentation

Security

For security issues, please email j@jeffg.me instead of opening a public issue.

⚠️ Status

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

πŸ“„ License

This project is licensed under the MIT License - see the LICENSE file for details.

πŸ™ Acknowledgments

  • 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

πŸ”— Related Projects

  • 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.

About

Marmot Development Kit

Resources

License

Security policy

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 7

Languages