Getting Started with RFC9420
This guide uses the current provider-based API in src/rfc9420.
Installation
Prerequisites
- Python 3.9+
uv(recommended)
pipx install uv
git clone https://github.com/MFDI-hub/PyMLS.git
cd PyMLS
uv sync
Quick check:
uv run python -c "from rfc9420 import GroupConfig, DefaultCryptoProvider, MemoryStorageProvider; from rfc9420.api.session import MLSGroupSession; print('ok')"
Development Setup
uv sync --dev
uv run ruff check .
uv run mypy src
uv run pytest -q
Core Concepts
GroupConfig: binds providers and runtime settings.MLSGroupSession: sync/byte API (create,join,add/update/remove,commit/apply, protect/unprotect).MLSGroup: staged-commit API for advanced flows.PublicGroup: passive handshake validation without secrets.
First Group (Two Members)
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey
from rfc9420 import (
GroupConfig,
DefaultCryptoProvider,
MemoryStorageProvider,
)
from rfc9420.api.session import MLSGroupSession
from rfc9420.extensions.extensions import build_capabilities_data
from rfc9420.messages.key_packages import KeyPackage, LeafNode, LeafNodeSource
from rfc9420.messages.data_structures import Credential, Signature
crypto = DefaultCryptoProvider()
storage = MemoryStorageProvider()
config = GroupConfig(crypto_provider=crypto, storage_provider=storage)
def make_member(identity: bytes):
kem_sk = X25519PrivateKey.generate()
kem_pk = kem_sk.public_key()
init_sk = X25519PrivateKey.generate() # for Welcome decrypt, distinct from leaf key
init_pk = init_sk.public_key()
sig_sk = Ed25519PrivateKey.generate()
sig_pk = sig_sk.public_key()
sig_pk_raw = sig_pk.public_bytes_raw()
caps = build_capabilities_data(
ciphersuite_ids=[crypto.active_ciphersuite.suite_id],
supported_exts=[],
include_grease=False,
)
cred = Credential(identity=identity, public_key=sig_pk_raw)
leaf = LeafNode(
encryption_key=kem_pk.public_bytes_raw(),
signature_key=sig_pk_raw,
credential=cred,
capabilities=caps,
leaf_node_source=LeafNodeSource.KEY_PACKAGE,
parent_hash=b"",
)
kp_tbs = KeyPackage(leaf_node=leaf, init_key=init_pk.public_bytes_raw())
sig = sig_sk.sign(kp_tbs.tbs_serialize())
kp = KeyPackage(
leaf_node=leaf,
init_key=init_pk.public_bytes_raw(),
signature=Signature(sig),
)
return kp, init_sk.private_bytes_raw(), sig_sk.private_bytes_raw()
# Alice creates group
kp_alice, init_sk_alice, sig_sk_alice = make_member(b"alice")
alice = MLSGroupSession.create_with_config(config, b"my-group", kp_alice)
# Bob key package
kp_bob, init_sk_bob, sig_sk_bob = make_member(b"bob")
# Alice proposes + commits Add(Bob)
alice.add_member(kp_bob, sig_sk_alice)
commit_bytes, welcomes = alice.commit(sig_sk_alice)
# Bob joins via Welcome (using init private key)
bob = MLSGroupSession.join_from_welcome_with_config(config, welcomes[0], init_sk_bob)
# App message
ct = alice.protect_application(b"hello bob")
sender, pt = bob.unprotect_application(ct)
print(sender, pt)
Common Operations
Self Update
new_hs = alice.update_self(new_leaf_node, sig_sk_alice)
alice.process_proposal(new_hs, alice.own_leaf_index)
commit_bytes, _ = alice.commit(sig_sk_alice)
Remove Member
rm_hs = alice.remove_member(removed_index=1, signing_key=sig_sk_alice)
alice.process_proposal(rm_hs, alice.own_leaf_index)
commit_bytes, _ = alice.commit(sig_sk_alice)
Persistence
data = alice.serialize()
restored = MLSGroupSession.deserialize_with_config(config, data)