| Internet-Draft | mocha-mls-keying | July 2026 |
| Jennings & Nandakumar | Expires 7 January 2027 | [Page] |
This document specifies MLS key management for MOCHA (MoQ Open Communication & Hosting Architecture). It defines how MLS groups are created, how members are added and removed, how key material is distributed over MOQT tracks, and how the MLS Delivery Service is realized in a fully distributed manner using MOQT publish/subscribe. For large channels, Partial MLS is used to avoid requiring all members to process every membership change.¶
This note is to be removed before publishing as an RFC.¶
Status information for this document may be found at https://datatracker.ietf.org/doc/draft-jennings-moq-mocha-mls-keying/.¶
Discussion of this document takes place on the Media Over QUIC Working Group mailing list (mailto:moq@ietf.org), which is archived at https://mailarchive.ietf.org/arch/browse/moq/. Subscribe at https://www.ietf.org/mailman/listinfo/moq/.¶
This Internet-Draft is submitted in full conformance with the provisions of BCP 78 and BCP 79.¶
Internet-Drafts are working documents of the Internet Engineering Task Force (IETF). Note that other groups may also distribute working documents as Internet-Drafts. The list of current Internet-Drafts is at https://datatracker.ietf.org/drafts/current/.¶
Internet-Drafts are draft documents valid for a maximum of six months and may be updated, replaced, or obsoleted by other documents at any time. It is inappropriate to use Internet-Drafts as reference material or to cite them other than as "work in progress."¶
This Internet-Draft will expire on 7 January 2027.¶
Copyright (c) 2026 IETF Trust and the persons identified as the document authors. All rights reserved.¶
This document is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (https://trustee.ietf.org/license-info) in effect on the date of publication of this document. Please review these documents carefully, as they describe your rights and restrictions with respect to this document. Code Components extracted from this document must include Revised BSD License text as described in Section 4.e of the Trust Legal Provisions and are provided without warranty as described in the Revised BSD License.¶
Keying for end-to-end encryption in MOCHA is provided by the Message Layer Security (MLS) protocol [MLS]. MLS requires a Delivery Service to distribute protocol messages (KeyPackages, Commits, Welcome messages) between group members. Traditional MLS deployments use a centralized server for this role.¶
This document specifies how MOCHA realizes the MLS Delivery Service in a fully distributed manner using MOQT's publish/subscribe primitives. Each member publishes MLS protocol messages on its own MOQT track, and other members subscribe to receive them. This eliminates the single point of failure and trust concentration inherent in a centralized Delivery Service.¶
For large channels where full MLS group membership becomes impractical, this document specifies the use of Partial MLS [MLS-PARTIAL], which allows subgroups of members to participate without processing every commit in the full group.¶
Credentials used within MLS (signing keys, identity assertions) are obtained via the mechanisms defined in [MOCHA-IDENTITY].¶
See BCP 14 [RFC2119] [RFC8174].¶
This document uses terminology defined in [MOCHA-ARCH] and [MLS]. The following additional terms are used:¶
An MLS group instance associated with a MOCHA channel. All members of the channel who participate in E2EE are members of this group.¶
An MLS structure published by a device to advertise its willingness to be added to a group. Contains the device's public keys and supported ciphersuites.¶
An MLS message that advances the group epoch. Commits carry Add, Remove, or Update proposals.¶
An MLS message sent to a newly added member containing the group state needed to participate from the current epoch.¶
A version of the MLS group state. Each Commit advances the epoch by one.¶
The designated group member responsible for collecting Proposals and publishing Commits. Only the committer advances the group epoch.¶
MLS protocol messages for a channel use the following namespace tuple:¶
("mocha_v1", <Provider>, <OrgID>, "mls", <TeamID>,
<ChannelID>, <MLSParticipantID>)
¶
KeyPackage advertisements use a separate namespace:¶
("mocha_v1", <Provider>, <OrgID>, "keypackage", <TeamID>,
<ChannelID>, <MLSParticipantID>)
¶
The MLSParticipantID is derived from the participant's MLS credential and uniquely identifies a participant within the channel's MLS group.¶
Each device publishes MLS protocol messages (Proposals, Commits, Welcomes) on its own track within the MLS namespace:¶
mls_v1_<HDevID>¶
Each device publishes its KeyPackage on its own track within the keypackage namespace:¶
keypackage_v1_<HDevID>¶
Each MLS protocol message (Proposal, Commit, or Welcome) is published as a single MOQT object. Objects within a group are sequentially numbered. The MOQT Group ID corresponds to the MLS epoch number, providing natural alignment between MLS state transitions and MOQT group boundaries.¶
Each MOQT object on an MLS track carries a 1-byte type prefix followed by the raw MLS message bytes:¶
+------+-----------------------------+ | Type | MLS Message (raw bytes) | | (1B) | | +------+-----------------------------+¶
The Type field values are:¶
| Value | Description |
|---|---|
| 0x01 | Proposal |
| 0x02 | Commit |
| 0x03 | Welcome |
| 0x04 | KeyPackage |
This prefix allows receivers to quickly dispatch messages without parsing the MLS wire format. The MLS Message field contains the unmodified MLSMessage struct as defined in [MLS], Section 6.¶
Traditional MLS requires a Delivery Service that orders Commits and ensures all members see the same sequence of group operations. In MOCHA, this role is distributed across the MOQT relay network.¶
The key insight is that MOQT relays already provide:¶
Reliable delivery of published objects to all subscribers¶
Caching for late-joining members¶
Fan-out to all subscribers of a track¶
By assigning each member its own MLS track within the channel namespace, the relay network collectively acts as the Delivery Service without any single entity controlling message ordering or having access to plaintext key material.¶
MLS requires that Commits are applied in a consistent order by all members. MOCHA uses a leader-follower model to achieve this without a centralized server:¶
Any member MAY publish Proposals (Add, Remove, Update) on its own MLS track at any time.¶
A single designated member (the "committer") collects pending Proposals and publishes Commits that advance the epoch.¶
All other members ("followers") subscribe to the committer's track and apply Commits as they arrive.¶
This separation ensures exactly one Commit per epoch transition with no conflicts or rollbacks.¶
The committer MAY batch multiple pending Proposals into a single Commit to minimize epoch churn. For example, if several Add or Remove Proposals arrive in quick succession, the committer can coalesce them into one Commit that advances the epoch once rather than producing a separate epoch transition for each Proposal.¶
The Provider designates the initial committer when the channel is created. The channel metadata returned by the Provider includes the creator's HDevID as the initial committer. Since channel creation requires the Provider to be reachable, the Provider serializes this assignment — no race condition is possible.¶
If the MLS group requires re-creation (all members have left, or the group state is explicitly reset), the first authorized member to join and observe no active keep-alive track assumes the committer role and creates a new epoch 0. If two members join simultaneously and both attempt epoch 0, relays deliver both but members MUST accept only the first valid Welcome they process and ignore subsequent attempts. The losing bootstrapper observes the established group and falls back to follower behavior.¶
Because this fallback uses Provider-authorized channel membership rather than always electing the lowest HDevID, the attack surface is reduced — only devices the Provider has authorized for the channel can assume the committer role.¶
The current committer MAY designate a successor by including a committer_transfer extension in a Commit. The successor's HDevID is recorded in the group state.¶
If the committer becomes unavailable (temporary disconnection or permanent departure), the group must elect a new committer to continue processing Proposals.¶
Unavailability is detected when:¶
No valid keep-alive (Section 4.6) is received within the deployment-configured timeout (e.g., 30 seconds).¶
The committer is explicitly removed from the group.¶
When unavailability is detected, the remaining members elect a new committer using the following deterministic rule:¶
All active members (those with a currently published roster entry with status "active") are eligible.¶
If the departing committer's last Commit contained a committer_transfer extension, the designated HDevID becomes the new committer. Otherwise, the eligible member with the lowest HDevID (lexicographic ordering) becomes the new committer.¶
The new committer publishes a Commit containing all pending Proposals plus its own Update proposal to signal committer succession.¶
This ensures all members independently arrive at the same new committer without explicit coordination. The timeout SHOULD be long enough to tolerate transient network disruptions but short enough to avoid blocking group operations.¶
Followers subscribe to all MLS tracks in the channel namespace via SUBSCRIBE_NAMESPACE. They:¶
The committer periodically publishes keep-alive objects to prove liveness and current epoch membership. All members MUST subscribe to the committer's keep-alive track:¶
keepalive_v1_<HDevID>¶
The committer sets the following Track Property when publishing to the keep-alive track:¶
keepalive_interval: The expected interval in seconds between keep-alive objects. Members use this value as the basis for failover timeout detection.¶
{
"epoch": 5,
"ts": 1715788800,
"interval": 10,
"nonce": "<16 bytes, hex-encoded>",
"mac": "<HMAC output, hex-encoded>"
}
¶
The interval field is the keep-alive interval in seconds,
matching the Track Property value. This allows new joiners
who FETCH the latest object to learn the timeout without
having received the original SUBSCRIBE Track Properties.¶
The MAC is computed as:¶
mac = HMAC(confirmation_key,
big_endian_uint64(epoch) ||
big_endian_uint64(ts) ||
nonce_bytes)
¶
where confirmation_key is the MLS confirmation key for the current epoch ([MLS], Section 8). All group members hold this key as part of the MLS key schedule.¶
Upon receiving a keep-alive object, each member:¶
Reads the epoch, ts, and nonce fields from the object.¶
Computes the HMAC using its own confirmation_key for the indicated epoch.¶
Compares the computed MAC against the received mac field.¶
If verification succeeds, the committer is confirmed as live and in possession of current epoch secrets.¶
If verification fails, the member SHOULD treat the committer as potentially compromised or out of sync and initiate failover (Section 4.4).¶
The committer MUST publish a keep-alive at the interval advertised in the keepalive_interval Track Property and the object's interval field. Members trigger failover if no valid keep-alive is received within twice the advertised interval (Section 4.4).¶
The nonce field (random bytes generated per keep-alive) ensures each object produces a unique MAC, preventing relays from replaying a previously observed keep-alive within the same epoch.¶
Each device publishes its current KeyPackage on its keypackage track within the channel's keypackage namespace:¶
Namespace: ("mocha_v1", <Provider>, <OrgID>, "keypackage",
<TeamID>, <ChannelID>, <MLSParticipantID>)
Track: keypackage_v1_<HDevID>
¶
The committer MUST subscribe to the KeyPackage namespace (SUBSCRIBE_NAMESPACE) to be notified when new devices publish KeyPackages. Upon receiving a new KeyPackage, the committer creates an Add Proposal and includes it in its next Commit.¶
Other members MAY also subscribe to the KeyPackage namespace and publish Add Proposals on their own MLS tracks, but only the committer produces the Commit that incorporates the Proposal.¶
KeyPackages are rotated by publishing a new object on the track. Each KeyPackage is single-use; after being consumed in an Add Proposal, the device MUST publish a fresh KeyPackage.¶
When a member issues a Commit that adds a new participant, it publishes the Welcome message as an object on its own MLS track. The Welcome is addressed to the new member by including the target HDevID in an object property, allowing the new member to identify which object contains its Welcome.¶
When a channel is created, the creator (designated as the initial committer by the Provider) initializes a new MLS group:¶
Creator generates an MLS group with a fresh group ID derived from the channel namespace.¶
Creator publishes (PUBLISH) its KeyPackage on track
keypackage_v1_<HDevID> within the keypackage namespace:
("mocha_v1", <Provider>, <OrgID>, "keypackage",
<TeamID>, <ChannelID>, <MLSParticipantID>).¶
Creator publishes the initial Commit (epoch 0 to epoch 1)
on track mls_v1_<HDevID> within the MLS namespace:
("mocha_v1", <Provider>, <OrgID>, "mls",
<TeamID>, <ChannelID>, <MLSParticipantID>).¶
Creator publishes its initial keep-alive on track
keepalive_v1_<HDevID> within the MLS namespace.¶
Creator subscribes (SUBSCRIBE_NAMESPACE) to the keypackage namespace to receive KeyPackages from new joiners.¶
Creator subscribes (SUBSCRIBE_NAMESPACE) to the MLS namespace to receive Proposals from other members.¶
To add a member to the channel:¶
The new device publishes (PUBLISH) its KeyPackage on track
keypackage_v1_<HDevID> within the keypackage namespace.¶
The committer, subscribed to the keypackage namespace, receives the new KeyPackage via the relay.¶
The committer creates an MLS Commit containing an Add Proposal referencing the received KeyPackage. Only the current committer MUST produce Commits.¶
The committer publishes the Commit and the Welcome message
on track mls_v1_<HDevID> within the MLS namespace.¶
All existing members, subscribed to the MLS namespace, receive the Commit and advance to the new epoch. Members MUST cache received Proposals locally until the corresponding Commit from the committer is received.¶
The new member subscribes (SUBSCRIBE) to the committer's
track mls_v1_<committer-HDevID>, receives the Welcome,
and initializes its group state at the new epoch.¶
The new member subscribes (SUBSCRIBE_NAMESPACE) to the MLS namespace to receive future Proposals and Commits.¶
The new member subscribes (SUBSCRIBE) to the committer's
keep-alive track keepalive_v1_<committer-HDevID>.¶
Alternatively, any member subscribed to the keypackage
namespace MAY create an Add Proposal and publish it on its
own track mls_v1_<HDevID>. The committer incorporates such
Proposals into its next Commit.¶
To remove a member:¶
The remover creates an MLS Remove Proposal for the target
member's leaf index and publishes (PUBLISH) it on track
mls_v1_<HDevID> within the MLS namespace.¶
The committer, subscribed to the MLS namespace, receives the Remove Proposal and creates an MLS Commit incorporating it. Only the current committer MUST produce Commits.¶
The committer publishes the Commit on its track
mls_v1_<HDevID> within the MLS namespace.¶
All remaining members, subscribed to the MLS namespace, receive the Commit and advance to the new epoch. Members MUST cache received Proposals locally until the corresponding Commit from the committer is received.¶
The removed member can no longer decrypt messages sent in subsequent epochs.¶
Any member can rotate its own key material:¶
The member creates an MLS Update Proposal with fresh key
material and publishes (PUBLISH) it on track
mls_v1_<HDevID> within the MLS namespace.¶
The committer, subscribed to the MLS namespace, receives the Update Proposal and creates an MLS Commit incorporating it. Only the current committer MUST produce Commits.¶
The committer publishes the Commit on its track
mls_v1_<HDevID> within the MLS namespace.¶
All members, subscribed to the MLS namespace, receive the Commit and advance to the new epoch with updated key material. Members MUST cache received Proposals locally until the corresponding Commit from the committer is received.¶
For channels with many members (hundreds or thousands), full MLS becomes impractical because every member must download and process every Commit. Partial MLS [MLS-PARTIAL] allows members to participate in the group without tracking the full ratchet tree.¶
Channels SHOULD use Partial MLS when the member count exceeds a deployment-configured threshold (e.g., 100 members). Channels MAY start with full MLS and transition to Partial MLS as membership grows.¶
In Partial MLS mode:¶
A subset of members (the "core group") maintains the full MLS tree state and processes all Commits.¶
Other members ("partial members") hold only their own leaf state and the path to the root. They can encrypt and decrypt application messages using the group's application secret, but do not process every Commit.¶
When a partial member needs to send a Commit (e.g., to update its own keys), it publishes a partial Commit that the core group integrates into the full tree.¶
The core group periodically publishes "sync" objects that provide partial members with updated path secrets, allowing them to remain in sync without processing the full Commit history.¶
A dedicated sync track carries periodic state updates for partial members:¶
sync_v1_<HDevID>¶
Core group members publish sync objects containing the path secrets needed by partial members to derive current application keys.¶
MLS-derived keys are used with MoQ Secure Objects [SecureObjects] to encrypt MOCHA message payloads.¶
The MLS application secret for each epoch is used to derive per-track encryption keys:¶
track_key = HKDF-Expand(application_secret,
"mocha_track" || track_name,
key_length)
track_nonce = HKDF-Expand(application_secret,
"mocha_nonce" || track_name,
nonce_length)
¶
The KID carried in MoQ Secure Objects encodes the MLS epoch and sender leaf index, allowing receivers to select the correct decryption key:¶
KID = (epoch << 24) | leaf_index¶
The KID is encoded as a MoQ Varint (64 bits). This yields 40 bits for the epoch (practically unlimited) and 24 bits for the leaf_index, supporting up to 16,777,215 members in a single MLS group. Channels exceeding this limit use Partial MLS (Section 6).¶
Receivers use the epoch to identify which application secret to derive keys from, and the leaf index to identify the sender's position in the tree.¶
MLS requires each member to present a credential binding their identity to their signing key. In MOCHA, credentials are obtained through the identity system defined in [MOCHA-IDENTITY].¶
The MLS credential contains:¶
The member's userId (as used in MOCHA message sender fields)¶
The member's signing public key¶
A signature from the Provider's Token Service binding the userId to the public key¶
Credential validation follows the trust model established by the Provider's identity infrastructure. Members MUST verify credentials against the Provider's public key before accepting any MLS protocol message.¶
This document makes no requests of IANA.¶