pure-mls
pure-mls is a pure-Python implementation of the Messaging Layer Security (MLS) protocol (RFC 9420) — the protocol logic (TreeKEM, key schedule, wire format) is written in Python, while cryptographic primitives are delegated to the cryptography library. It implements the cryptographic state machine for secure group messaging.
⚠️ Status: experimental, not audited for production. It passes the offline IETF known-answer vectors below, but several protocol-level guarantees (e.g.
tree_hashinterop, full parent-hash validation) are still being hardened. Do not use it to protect real secrets yet.
🚀 Features & Interoperability
What is actually verified today (offline, in CI):
- RFC 9420 primitives: HPKE / HKDF / key-schedule and
DeriveKeyPairmatch the IETFcrypto-basicsandkey-scheduleknown-answer vectors. - Passive-client Welcome + PSK: passes the IETF
passive-client-welcomeandpsk_secretvectors (Welcome produced by a reference implementation, joined bypure-mls). - Transports: local end-to-end demos over WebSockets, MQTT, WebRTC and gRPC (network-marked, skipped by default).
Not yet backed: live bidirectional OpenMLS interop (round-trips in both directions) is not wired up.
🧠 Philosophy: "Sound of Silence"
The goal is protocol purity:
- All MLS protocol logic (TreeKEM, key schedule, TLS wire format) is pure Python — no protocol logic hidden in native code.
- The single dependency,
cryptography, provides the vetted low-level primitives (Ed25519, X25519, AES-GCM, HKDF) rather than reimplementing them. - Operates natively in any Python 3.12+ environment.
- Built on principles of Plausible Deniability and Zero-Knowledge.
The Linter Protocol
We strictly enforce the "Sound of Silence" code standard via ruff in the pyproject.toml file:
- Zero-Warning State: 100% clean status under Ruff's most rigorous rules.
- Pure Tabulations (
\t) for minimal character footprint (W191allowance). - Zero dead code allowed.
🗺️ Architecture (Project Map)
pure-mls/
├── README.md # This file
├── CHANGELOG.md # Version history registry
├── pyproject.toml # Dependencies (uv) and Sound of Silence config (Ruff)
├── src/
│ └── pure_mls/
│ ├── group.py # [API] State Machine (MLSGroup)
│ ├── tree.py # Nodes and RatchetTree structure
│ ├── crypto.py # Parent/subtree hashes and HPKE info helpers
│ ├── tls.py # RFC 9420 / TLS 1.3 Wire Format Primitives
│ ├── extensions.py # MLS Extensions Framework
│ ├── proposals.py # Group Operations (Add, Update, Remove)
│ ├── epoch.py # Immutable states (Epochs)
│ ├── keys.py # Ed25519 Identities and X25519 KEMs (+ DeriveKeyPair)
│ ├── hkdf.py # HKDF / ExpandWithLabel primitives
│ ├── keyschedule.py # Secret Derivation (Key Schedule §8)
│ ├── secret_tree.py # Per-leaf per-generation SecretTree (§9)
│ ├── hpke.py # Hybrid Public Key Encryption Base Mode
│ ├── codecs.py # Dialect / codec plugin system (wire-format variants)
│ ├── storage.py # AES-256-GCM encrypted state persistence
│ └── cli.py # Command-line interface
└── tests/
├── test_ietf_vectors.py # RFC 9420 IETF known-answer vectors (offline)
├── test_group.py # State Machine unit tests
├── test_e2e_websockets.py # E2E local Websockets (network-marked)
├── test_e2e_mqtt.py # E2E local broker (IoT, network-marked)
├── test_e2e_webrtc.py # E2E Data Channels P2P (aiortc, network-marked)
└── test_e2e_grpc.py # E2E Backend Swarm (gRPC, network-marked)
📚 Documentation & Guides
We believe in making cryptography accessible. For a fast, pragmatic, and irreverent introduction to MLS, check our Primate Survival Guide:
For a deeper dive into the architecture, mathematics, and philosophy of the protocol, explore the Human Journey:
Contributors: We welcome translations! Feel free to PR your language following the 02_MLS_JOURNEY_XX.md format.
🔌 API Quickstart
The central state machine is MLSGroup. Install it in your brain:
from pure_mls.group import MLSGroup
from pure_mls.keys import SignatureKey, KemKey
# 1. Each participant generates their own persistent identity keys
alice_sig, alice_kem = SignatureKey(), KemKey()
bob_sig, bob_kem = SignatureKey(), KemKey()
# 2. Alice initializes the Sovereign Group
alice_group = MLSGroup.create(b"grupo-soberano", alice_sig, alice_kem)
# 3. Alice receives Bob's `KeyPackage` (his public keys + identity) over the network
bob_kp = MLSGroup.create_key_package(bob_sig, bob_kem)
alice_next, welcome, update = alice_group.add_member(bob_kp)
# 4. Bob decrypts the Welcome (sealed with HPKE to his KEM key) and joins
bob_group = MLSGroup.join(welcome, bob_sig, bob_kem)
# 5. Alice removes Bob from the group
alice_next_epoch, remove_commit = alice_next.remove_member(bob_group.committer_index)
# The Underlying Mathematical Truth:
assert alice_next.application_key == bob_group.application_key
assert alice_next_epoch.application_key != bob_group.application_key # Bob is out!
License
This project is licensed under the GNU General Public License v3.0 (GPLv3).
Metadata
Release files for pure-mls 4.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| pure_mls-4.0.0.tar.gz | 47.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| pure_mls-4.0.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 102.8 kB
Release files / pure_mls-4.0.0.tar.gz
| Download URL | pure_mls-4.0.0.tar.gz |
|---|---|
| Size | 47.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
7d958e7576cf2798766355ed55d943df2003c83c4aa6b8bb20a2950f43cb1aeb
|
|
BLAKE2b-256 checksum How to use checksums |
77a98f1cf19bb361546f032bfbdfd955627303911e7d1f491da065bd823ce187
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Release files / pure_mls-4.0.0-py3-none-any.whl
| Download URL | pure_mls-4.0.0-py3-none-any.whl |
|---|---|
| Size | 55.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
ff33e25afaa3221eab06dbb59a78bf7e5d6e45cff042e1dc8768646b67903b06
|
|
BLAKE2b-256 checksum How to use checksums |
2aee05b8f79fc996a52f7493979fef5e5a6927c17869b55d286216a2021572cc
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|