Skip to content

MLS (RFC 9420) as group crypto layer: alignment, conflicts, conscious divergences #11

Description

@foxcool

The spec lists group key management as an open question (Section 10.3: "MLS vs. custom scheme vs. sender-keys"), and #8 already assumes MLS sub-groups per ACL zone. MLS is now a published standard with production deployments, so the question is no longer "is MLS ready" but "which parts of MLS do we adopt, and which divergences do we accept on purpose".

References: RFC 9420 (MLS protocol), RFC 9750 (MLS architecture), IETF blog post, draft-ietf-mimi-protocol (MIMI over MLS), draft-ietf-keytrans-architecture (Key Transparency).

What already lines up

RFC 9750 splits a messaging system into MLS protocol + Delivery Service (DS) + Authentication Service (AS), and deliberately does not specify DS or AS. That is the same cut as our Layer 2 / Layer 1 / Layer 0, and the same "thin glue" instinct — the IETF chose composition over a full-stack protocol for the same reasons we did.

MLS DMF spec
Protocol is transport-agnostic, DS unspecified ADR-005: protocol defines format, not transport
BasicCredential = opaque identity + signature key; ciphersuite MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519 ADR-001: Ed25519 pubkey as canonical identity. Maps 1:1, no X.509 needed
PrivateMessage encrypts handshake content; DS sees group_id, epoch, sender index only TRUST_PARASITIC: "zero trust to transport, encrypted blob only". An MLS group survives a Telegram bot relay unchanged
Each device is its own leaf with its own key; removal = Remove proposal device_keys[] (Section 5.4) and the device-revocation risk row in 10.2 — solved for free
MLS makes no distinction between human and bot members participant_type is a UI hint only (Section 5.1)
Formally verified, forward secrecy + post-compromise security, log(n) ops, thousands of devices per group Section 10.3 open question; our 100-member interactive limit is far inside MLS's envelope
MIMI room policy draft: roles, membership ops, join rules Section 5.3 AccessPolicy / Role. Vocabulary worth borrowing rather than reinventing

Practical consequence: Section 4.4 gains one more line — "Custom group crypto → MLS (RFC 9420)" — and Section 8.1 loses one, since MLS carries its own ratchet and Double Ratchet becomes redundant for anything that is not legacy 1:1.

What genuinely conflicts

1. Commit ordering vs. ADR-002 / #7. This is the real one.

MLS advances the group through epochs. Application messages within an epoch need no ordering and can be sent concurrently — that part is fine for us. But every Commit (add, remove, key update) must be totally ordered: two members committing in the same epoch produce a fork, and MLS expects the DS to serialize and reject the loser.

ADR-002 says "no distributed consensus needed", ADR-004 says "no global ordering guarantee", and #7 pushes further toward a DAG where there is no single group object at all. We have no DS. Multi-transport fan-out (Section 5.5) means two members can commit through different transports with no common serializer.

This is not a detail we can annotate away. Either a group elects an ordering point (one member, or a designated transport, acting as DS for that group — re-centralization at group scope), or we go off-standard. Matrix has been working on decentralised MLS for years without landing it; it is a known-hard problem, not a gap in our reading.

Worth stating precisely so the trade-off is visible: chatting is unaffected, membership churn is what costs. Groups with rare membership changes are cheap; groups with constant churn are expensive.

2. #8 gets more expensive than it looks.

MLS has no cheap sub-group derivation. Branching produces a fresh group with its own epoch chain, its own state, its own ordering requirement, its own KeyPackage fetches. "ACL change = new zone = new MLS sub-group, expensive but rare" is right about the mechanism and probably optimistic about the cost: N ACL zones = N independent MLS groups to maintain, sync and order. If ACL narrowing is common in practice, per-message ACL and MLS pull against each other.

Deeper mismatch behind it: MLS group state is sequential and stateful; the DAG model in #7 is content-addressed and order-free. Those are different shapes, not different spellings.

3. KeyPackage directory is a missing storage responsibility.

MLS adds an offline member by fetching a KeyPackage they published in advance. Section 5.7 ContentStore (Put/Get/Pin/Unpin) is content-addressed media storage and does not cover a per-identity, consumable, one-time-use key directory. Either ContactCard grows a KeyPackage endpoint, or the storage layer grows a second interface. Constraint 2.2 "MUST NOT require always-online participants" depends on this existing.

4. Implementation reality.

Mature MLS implementations today are Rust (OpenMLS, mls-rs) and C++ (MLSpp). If the reference daemon lands in Go, this is a real cost — either bind to a Rust core or take on an immature stack. Worth a row in 10.2.

What is adjacent and useful

MIMI (draft-ietf-mimi-protocol-05) is provider-to-provider federation over HTTPS, driven by DMA regulation. Ortogonal to our sovereign/parasitic model — but a MIMI client adapter would be a legally-mandated bridge into WhatsApp/Messenger-scale networks. One row in the Section 4.2 adapter table, post-MVP. Notably MIMI picked MLS as its E2E layer, which is the strongest available signal that "MLS as the group crypto layer under an interop protocol" is a workable shape.

KEYTRANS (draft-ietf-keytrans-architecture-08) covers a gap the spec does not currently name at all: how does a client verify that the key behind a name was not swapped? Our resolvers (ENS/DNS/DID) plus WoT are an ad-hoc substitute. Even if we do not adopt KT, the spec should say why not.

The decision to make

Three coherent positions. Picking none is the bad outcome, because #8 currently assumes MLS while ADR-002 assumes no ordering, and both are marked accepted.

A. Adopt MLS, accept per-group ordering. Each private zone elects a serializer (a member, or a designated transport endpoint). Standard-compliant, formally verified crypto, real libraries, MIMI interop path. Cost: re-centralization at group scope, and an availability dependency the rest of the architecture does not have.

B. Hybrid, scoped by churn. MLS for zones where ordering is achievable and membership is stable — DMs, long-lived private groups. Sender-keys or DAG-native scheme for public subtrees and high-churn zones. Degradation is explicit in the UI, consistent with ADR-006. Cost: two crypto stacks, and the boundary between them becomes a security-relevant surface.

C. Conscious divergence. DAG-native group crypto (DCGKA family), documented as a deliberate non-MLS decision. Keeps ADR-002 and #7 intact. Cost: no formal verification, no libraries, no MIMI interop, and we own a novel crypto protocol — which contradicts the Minimalism Mandate in 2.1 fairly directly.

Leaning B for MVP: build against OpenMLS/mls-rs, restrict MLS to zones where a serializer exists, and let everything else degrade visibly rather than pretending it is equally protected. But A is the honest choice if we want interop, and C is the honest choice if ADR-002 is truly non-negotiable. Whichever we take, it should land as an ADR with the ordering trade-off spelled out, not as a silent assumption inside #8.

Concrete spec edits once decided

  • Section 10.3: replace "Group key management: MLS vs custom vs sender-keys" with the actual open question — group state ordering without a DS
  • New ADR: group crypto choice, with the ordering trade-off explicit
  • Section 4.4: "Custom group crypto → MLS (RFC 9420)"
  • Section 5.7: KeyPackage directory as a storage-layer responsibility (or a ContactCard endpoint)
  • Section 8.1: Double Ratchet scoped to legacy 1:1 only
  • Section 6.4: MLS / MIMI / KEYTRANS rows in the related-work table
  • Section 10.2: no mature Go MLS implementation as a risk row
  • Section 10.3: key transparency as a named open question

Cheapest way to settle the ordering question is an experiment, not more reading: three members on OpenMLS, two concurrent Commits in one epoch, measure what breaks and what recovery costs.

Related: #7, #8.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions