process the Commit (i.e., not including any members being added or removed by the Commit). <del> The sender of a Commit SHOULD NOT include any Update proposals that the sender themselves generated.  If sender generated one or more Update proposals during an epoch, then it SHOULD instead update its leaf and direct path by sending "path" field in the Commit. </del> If there are multiple proposals that apply to the same leaf, the committer chooses one and includes only that one in the Commit, <del> considering the rest invalid.  The committer MUST prefer any Remove received, or the most recent Update for the leaf if there are no Removes.  If there are multiple Add proposals containing KeyPackages with the same tuple "(credential.identity, endpoint_id)" the committer again chooses one to include and considers the rest </del>  invalid.  Add proposals that contain KeyPackages with an "(credential.identity, endpoint_id)" tuple that matches that of an existing KeyPackage in the group MUST be considered invalid.  The

When computing the tree hash, should there be an indicator bit for whether the value being hashed represents a leaf or parent node?
This doesn't seem like a terrible idea. You could either add a field to LeafNodeHashInput and ParentNodeHashInput, or define an enum and do the same, or make a . For example:
Any Update proposal for the sender is redundant if the Commit includes a .
This is true, but they're also harmless, since the information in the will overwrite all of their effects. Note that: A requirement to this effect would only be binding on a committer. The receiver of a Commit MUST process all of the referenced proposals, as prescribed in the Commit. A is required if a Commit covers Updates or Removes; if a Commit covers a self-Update, then it must have a . So there's no reasn to condition on "if a is included". This seems fine for some advisory text, but not an absolute prohibition.
The way I read Section 11.2 is that the receiver should verify that the committer has followed the rules, e.g. that the commit contains all valid proposals, etc., even if that is not explicit in the list of things the receiver of a commit should do. So any requirement on the committer would also affect the receiver. So if we mandate that a committer can't include self-updates if the path is populated, then it would be implicit that the receiver checks that the committer actually followed the rules when processing a commit. I'm not sure I understand your second point. My reasoning would be that if a committer wants to do an update, they would just include a path rather than an update proposal, which is redundant anyway.
Sorry NAME I missed that there was still conversation going on here before merging . On the latter point: All I'm saying is that there is no case where there is both (a) a self-update and (b) no . So you're always in the bad scenario :) In our Wire chat, you made a good point that repeated proposals for the same leaf are invalid, and self-Updates are effectively repeats of the . Reopening, will file a fresh PR to adjust the text.

process the Commit (i.e., not including any members being added or removed by the Commit).  If there are multiple proposals that apply to the same leaf, the committer chooses one and includes only that one in the Commit,  <ins> considering the rest invalid.  The committer MUST NOT include any Update proposals generated by the committer, since they would be duplicative with the "path" field in the Commit.  The committer MUST prefer any Remove received, or the most recent Update for the leaf if there are no Removes.  If there are multiple Add proposals containing KeyPackages with the same tuple "(credential.identity, endpoint_id)" the committer again chooses one to include and considers the rest </ins> invalid.  Add proposals that contain KeyPackages with an "(credential.identity, endpoint_id)" tuple that matches that of an existing KeyPackage in the group MUST be considered invalid.  The


state with other clients.  A client is defined by the cryptographic keys it holds. <del> A collection of clients with shared cryptographic state. </del>  A client that is included in the shared state of a group, hence has access to the group's secrets. A signing key pair used to authenticate the sender of a message.  Terminology specific to tree computations is described in ratchet- trees. 3. <del> This protocol is designed to execute in the context of a Service Provider (SP) as described in I-D.ietf-mls-architecture.  In particular, we assume the SP provides the following services: </del>  <del> A signature key provider which allows clients to authenticate protocol messages in a group. </del>  <del> A broadcast channel, for each group, which will relay a message to all members of a group.  For the most part, we assume that this channel delivers messages in the same order to all participants. (See sequencing for further considerations.) </del>  <del> A directory to which clients can publish key packages and download key packages for other participants. </del>  4. <del> The goal of this protocol is to allow a group of clients to exchange confidential and authenticated messages.  It does so by deriving a sequence of secrets and keys known only to members.  Those should be secret against an active network adversary and should have both forward secrecy and post-compromise security with respect to compromise of any members. We describe the information stored by each client as _state_, which includes both public and private data.  An initial state is set up by a group creator, which is a group containing only itself.  The creator then sends _Add_ proposals for each client in the initial set of members, followed by a _Commit_ message which incorporates all of the _Adds_ into the group state.  Finally, the group creator generates a _Welcome_ message corresponding to the Commit and sends this directly to all the new members, who can use the information it contains to set up their own group state and derive a shared secret. Members exchange Commit messages for post-compromise security, to add new members, and to remove existing members.  These messages produce new shared secrets which are causally linked to their predecessors, forming a logical Directed Acyclic Graph (DAG) of states. The protocol algorithms we specify here follow.  Each algorithm specifies both (i) how a client performs the operation and (ii) how other clients update their state based on it. </del>  There are three major operations in the lifecycle of a group: the group with fresh entropy.  In this section, we show each proposal being committed immediately, but in more advanced deployment cases an application might gather several proposals before committing them all <del> at once. </del>  Before the initialization of a group, clients publish InitKeys (as KeyPackage objects) to a directory provided by the Service Provider. Subsequent additions of group members proceed in the same way.  Any member of the group can download a KeyPackage for a new client and <del> broadcast an Add message that the current group can use to update their state, and a Welcome message that the new client can use to initialize its state and join the group. </del>  To enforce the forward secrecy and post-compromise security of <del> messages, each member periodically updates their leaf secret.  Any member can update this information at any time by generating a fresh KeyPackage and sending an Update message followed by a Commit message.  Once all members have processed both, the group's secrets will be unknown to an attacker that had compromised the sender's prior leaf secret. </del>  Update messages should be sent at regular intervals of time as long as the group is active, and members that don't update should to determine an appropriate amount of time between Updates. Members are removed from the group in a similar way.  Any member of <del> the group can send a Remove proposal followed by a Commit message, which adds new entropy to the group state that's known to all except the removed member.  Note that this does not necessarily imply that any member is actually allowed to evict other members; groups can enforce access control policies on top of these basic mechanism. </del>  5.

First part of fixing
This issue accompanies the e-mail [1] pointing out editorial issues with the draft and suggesting improvements. TL;DR: I don't think the draft currently is what is needed to implement MLS. It needs an editorial overhaul with a better structure. [1] URL
If we want an implementation document, which is a legitimate ask, we should write an implementation document.
What's the purpose of the document if not for implementation? In order for this draft to become an RFC it needs implementations. I don't see a way this will happen with the draft as is.
Specifying a functional behavior and providing a guide for implementation are two very different things. Re: what you suggested in your email: Internal representations of the tree, the way you number the indices, how you implement them or the fact that you store some hashes in the nodes are completely irrelevant to this document. Also, there is nothing formal about this appendix. On the other hand the information you need from this document is how to perform computations : things such as providing the arguments to the nodeencap/nodedecap functions which are required to generate a correct payload and a standard way to serialize abstract structures... etc.
Right, the document is doing neither right now.
We have two separable issues here: We need better explanation of the overall structure of the protocol (as in Franziskus' email), and we could rearrange the text to be clearer. For the first, I have filed . That provides a pretty complete overview of the protocol, covering all of the syntax and high-level operations. For the second, we should do a second PR after the other outstanding PRs have landed. Looking at the table of contents, I would propose we refactor in the following way: For top-level sections: Move to after and For subsections: Move the tree-relevant subsections of into — including , , and Split into two: Put the GroupContext definition part in a new subsection under Put the transcript updating part in a new subsection under
I have an alternative organization of the first sections: Add the one sentence definition of before the last paragraph of Swap and This seems to all flow nicely with very few forward references. A more radical rewrite might Move forward before but I don't think that is necessary. I do like Richard's proposal to split
I'm considering this fixed after the series of reorg PRs that NAME NAME and I just finished.
some comments on the overview section from a beginner's point of view. Hi all, While trying to implement the MLS protocol from scratch I ran into a couple issues and I think there’s a general problem regarding the structure of the draft. Note that these are all editorial issues. Right now I see no straightforward way to implement the spec as there’s no clear structure. To implement even the basic tree structure it’s necessary to jump through the entire document. There’s also a lot of prose and examples and very little implementable parts. Let me give some examples: Section 5 (Ratchet Trees) reads like a non-normative section (or paper). The really interesting bit related to trees is in Appendix A. I suggest moving the formal description of the tree from Appendix A to this section. It’s impossible to implement this from the hand full examples here. There’s no description in here on how the tree actually works. For example, adding a node to the tree is defined in 10.1.1 (Add). The ratchet tree nodes as described in Section 5 don't hold a KeyPackage. But Section 7 says “As the KeyPackage is a structure which is stored in the Ratchet Tree and updated depending on the evolution of this tree”. The tree nodes have a parent hash field, which is of course part of the ratchet tree but shouldn’t be necessary for the description of the tree structure. Also, the description of the tree hash (Section 7.5) follows the definition of the parenthash extension in Section 7.4 such that it is not entirely clear which parenthash is used there (extension or node value). Section 5 also talks about commit messages without linking to them or introducing them. Having to talk about commit messages in this section is also a little confusing. The term “handshake message” is first used in Section 7.8 (Key Schedule) but never introduced. This should probably happen in 10 (Group Evolution), which doesn’t talk about handshake messages at all until 10.3 (Ratchet Tree Extension). There are probably plenty more issues like this. But I don’t think addressing any of these issues individually makes sense. I therefore suggest having an overhaul of the document structure. The way I see MLS there are a set of high-level structures and components that should be clearly described and built upon each other. There’s a data structure (tree) that is used to represent a group. Then there is the MLS group that implements a specific semantic on the tree. Operations to modify the group are specified in the handshake protocol. And application messages define messages encrypted with keys derived in the handshake protocol with the key schedule. The tree is a basic left-balanced binary tree and defines functions to create a tree, and add and remove nodes. A group is the main part of MLS, a tree with nodes of a certain type. So this part should describe what nodes look like and how the tree is used to compute tree invariants and keys. The handshake protocol defines all messages needed to perform operations on the MLS group. This is certainly not exhaustive and would need further refinement. But it’s the high-level structure I’d expect from the document. I’m sure other structures would work as well. But what we currently have isn’t what is needed to implement MLS imho. Cheers, Franziskus

state with other clients.  A client is defined by the cryptographic keys it holds.  <ins> A linear sequence of epochs in which each epoch depends on its predecessor. A state of a group in which a specific set of authenticated clients hold shared cryptographic state. </ins> A client that is included in the shared state of a group, hence has access to the group's secrets. A signing key pair used to authenticate the sender of a message. <ins> An MLSPlaintext or MLSCiphertext message carrying an MLS Proposal or Commit object, as opposed to application data. An MLSCiphertext message carrying application data. </ins> Terminology specific to tree computations is described in ratchet- trees. 3.  <ins> MLS is designed to operate in the context described in I-D.ietf-mls- architecture.  In particular, we assume that the following services are provided: A Delivery Service that routes MLS messages among the participants in the protocol.  The following types of delivery are typically required: Pre-publication of KeyPackage objects for clients </ins>  <ins> Broadcast delivery of Proposal and Commit messages to members of a group </ins>  <ins> Unicast delivery of Welcome messages to new members of a group </ins>  <ins> An Authentication Service that enables group members to authenticate the credentials presented by other group members. </ins> 4.  <ins> The core functionality of MLS is continuous group authenticated key exchange (AKE).  As with other authenticated key exchange protocols (such as TLS), the participants in the protocol agree on a common secret value, and each participant can verify the identity of the other participants.  MLS provides group AKE in the sense that there can be more than two participants in the protocol, and continuous group AKE in the sense that the set of participants in the protocol can change over time. The core organizing principles of MLS are _groups_ and _epochs_.  A group represents a logical collection of clents that share a common secret value at any given time.  The history of a group is divided into a linear sequence of epochs.  In each epoch, a set of authenticated _members_ agree on an _epoch secret_ that is known only to the members of the group in that epoch.  The set of members involved in the group can change from one epoch to the next, and MLS assures that only the members in the current epoch have access to the epoch secret.  From the epoch secret, members derive further shared secrets for message encryption, group membership authentication, etc. The creator of an MLS group creates the group's first epoch unilaterally, with no protocol interactions.  Thereafter, the members of the group advance their shared cryptographic state from one epoch to another by exchanging MLS messages: A _KeyPackage_ object describes a client's capabilities and provides keys that can be used to add the client to a group. A _Proposal_ message proposes a change to be made in the next epoch, such as adding or removing a member A _Commit_ message initiates a new epoch by instructing members of the group to implement a collection of proposals A _Welcome_ message provides a new member to the group with the information to initialize their state for the epoch in which they were added A _PublicGroupState_ object provides potential new group members with information about the state of a group at an epoch, to allow a new member to add themselves to the group. KeyPackage, Welcome, and PublicGroupState messages are used to initiate a group or introduce new members, so they are exchanged between group members and clients not yet in the group. Proposal and Commit messages are sent from one member of a group to the others.  MLS provides a common framing layer for sending messages within a group, including Proposal and Commit as well as application data.  An _MLSPlaintext_ message provides sender authentication for unencrypted Proposal and Commit messages.  An _MLSCiphertext_ message provides encryption and authentication for both Proposal/Commit message and application data. 4.1. There are two types of cryptographic state at the core of MLS: A _key schedule_ that represents the shared secret state of the group and its evolution from one epoch to the next. A _ratchet tree_ that represents the membership of the group, providing group members a way to authenticate each other and efficiently encrypt messages to subsets of the group.  Each epoch has a distinct ratchet tree. Each member of the group maintains a view of these two facets of the group's state.  MLS messages are used to intialize these views and keep them in sync as the group transitions between epochs. Each new epoch is initiated with a Commit message.  The Commit instructs existing members of the group to update their views of ratchet tree by applying a set of Proposals, and uses the updated ratchet tree to distribute fresh entropy to the group.  This fresh entropy is provided only to members in the new epoch, not to members who have been removed, so it maintains the confidentiality of the epoch secret (in other words, it provides post-compromise security with respect to those members). For each Commit that adds member(s) to the group, there is a corresponding Welcome message.  The Welcome message provides new members with the information they need to initialize their views of the key schedule and ratchet tree, so that these views are equivalent to the views held by other members of the group in this epoch. In addition to defining how one epoch secret leads to the next, the key schedule also defines a collection of secrets that are derived from the epoch secret.  For example: An _encryption secret_ that is used to initialize a _secret tree_, which provides keys for encrypting handshake and application messages and providing forward secrecy for these messages within an epoch. A _confirmation key_ that is used to confirm that all members agree on the shared state of the group. A _resumption secret_ that members can use to prove their membership in the group, e.g., in the case of branching a subgroup. Finally, an _init secret_ is derived that is used to initialize the next epoch. 4.2. </ins> There are three major operations in the lifecycle of a group: the group with fresh entropy.  In this section, we show each proposal being committed immediately, but in more advanced deployment cases an application might gather several proposals before committing them all  <ins> at once.  In the illustrations below, we show the Proposal and Commit messages directly, while in reality they would be sent encapsulated in MLSPlaintext or MLSCiphertext objects. </ins> Before the initialization of a group, clients publish InitKeys (as KeyPackage objects) to a directory provided by the Service Provider. Subsequent additions of group members proceed in the same way.  Any member of the group can download a KeyPackage for a new client and  <ins> broadcast Add and Commit message that the current group can use to update their state, and a Welcome message that the new client can use to initialize its state and join the group. </ins> To enforce the forward secrecy and post-compromise security of  <ins> messages, each member periodically updates the keys that represent them to the group.  A member does this by sending a Commit (possibly with no proposals), or by sending an Update message that is committed by another member.  Once the other members of the group have processed these messages, the group's secrets will be unknown to an attacker that had compromised the sender's prior leaf secret. </ins> Update messages should be sent at regular intervals of time as long as the group is active, and members that don't update should to determine an appropriate amount of time between Updates. Members are removed from the group in a similar way.  Any member of  <ins> the group can send a Remove proposal followed by a Commit message. The Commit message provides new entropy to all members of the group except the removed member.  This new entropy is added to the epoch secret for the new epoch, so that it is not known to the removed member.  Note that this does not necessarily imply that any member is actually allowed to evict other members; groups can enforce access control policies on top of these basic mechanism. </ins> 5.


Blank the intermediate nodes along the path from L to the root Truncate the tree by removing leaves from the right side of the <del> tree until the rightmost leaf node is not blank. </del>  11.1.4.

As NAME noted , the current truncation algorithm breaks parent hashes. This PR implements the "careful truncation" algorithm he proposes.
I think this should be good to go?
Merging this for now to un-break the spec. I will work with Marta to see if we can make a follow-up that removes redundant nodes, so that we can get back to more aggressive truncation. Hi everyone, while testing changes to our tree implementation, we found what we believe is a bug in the specification. More concretely, the tree truncation mandated by the spec is too aggressive and can destroy parent hashes in an otherwise valid tree. For debugging purposes, we had a check that would validate parent hashes of every node every time a commit is processed, which is how we found the bug. Currently, the spec mandates that upon a remove operation, the leaves are removed until the right-most leaf in non-blank. However, this can lead to a situation, where non-blank parent nodes of a non-blank leaf are removed. This removal potentially interrupts valid "parent-hash-chains". More concretely, if the affected leaf node was the last node to commit a path in its subtree, then overly aggressive truncation can lead to the removal of parent nodes in that path. This in turn leads to an error when the parent hash of the parent of the removed parent node is verified. Note that if the parent hash chain is interrupted in this way, the situation is indistinguishable from one where the parent hash of a node is simply invalid. Consider the following example: 15 | / | 7 | _ /  | 3 11 | /  /  | 1 5 9 13 | /  /  /  /  | A B C D E F G - I 1 1 1 1 1 1 1 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 G performs an update, leading to a valid parent hash chain along its direct path (13, 11, 7, 15). A removes I, leading to the following state, since G is the right-most non-blank leaf. 7 | /  3 11 /  /  1 5 9 | /  /  /  | A B C D E F G 1 1 1 0 1 2 3 4 5 6 7 8 9 0 1 2 Since we have removed 13 in the process of removing (leaf) node 14, verification of the parent hash of node 11 will now fail, since none of its children carries its hash in its parent hash field. This will become apparent if a new member is added, either by one of the nodes A-D or by any node without a path. As solution, we propose that the right-most leaf is only removed in the process of truncation if both it and its parent are blank. Raphael pointed out that this "careful" truncation leads to sparser trees and thus makes the protocol overall less efficient (larger tree structures need to be stored, longer direct paths with more secrets and key pairs to derive, etc.). We can fix this by having two forms of truncation: A "careful" one, as described above and the one that is already in the spec right now with one caveat. The careful one replaces the one that is currently described in the spec and is thus performed with each removal operation. The one that we have right now is performed before a committer (or the receiver of a commit) applies a direct path, except that it can only remove nodes that are in the direct path of the committer. This should allow us to get the smallest tree that we can without compromising valid parent hash chains. Cheers, Konrad

Blank the intermediate nodes along the path from L to the root Truncate the tree by removing leaves from the right side of the  <ins> tree as long as all of the following conditions hold (since non- blank intermediate nodes hold information that is necessary for verifying parent hashes): The rightmost leaf is blank The parent of the rightmost leaf is either blank or the root of the tree </ins> 11.1.4.


those in the GroupInfo object, according to the definitions of those extensions.  Note that the latter two requirements mean that all MLS extensions are mandatory, in the sense that an extension in use by the group MUST be supported by all members of the group.

A GroupInfo (or a PublicGroupState) should only ever contain one RatchetTreeExtension. Either that or it should be specified what happens if there is more than one.
Seems like we should be able to make a general proclamation that extension IDs MUST be unique in a given vector of extensions, whether that's GroupContext, KeyPackage, GroupInfo, GroupContextExtensions, etc.
Sounds good to me. I'll draft a PR.
SGTM, one minor rephrasing for more clarity.

those in the GroupInfo object, according to the definitions of those extensions. <ins> Any field containing a list of extensions MUST NOT have more than one extension of any given type. </ins> Note that the latter two requirements mean that all MLS extensions are mandatory, in the sense that an extension in use by the group MUST be supported by all members of the group.


find out the plaintext by correlation between the question and the length. <del> Similarly to TLS 1.3, if padding is used, the MLS messages MUST be padded with zero-valued bytes before AEAD encryption.  Upon AEAD decryption, the length field of the plaintext is used to compute the number of bytes to be removed from the plaintext to get the correct data.  As the padding mechanism is used to improve protection against traffic analysis, removal of the padding SHOULD be implemented in a "constant-time" manner at the MLS layer and above layers to prevent timing side-channels that would provide attackers with information on the size of the plaintext.  The padding length length_of_padding can be chosen at the time of the message encryption by the sender. Recipients can calculate the padding size from knowing the total size of the ApplicationPlaintext and the length of the content. </del>  14.2.

The padding section was out of date based on prior changes.
In section 14.1 Message Encryption and Decryption there is a paragraph describing the reasons why you would want to use padding, and part of that mentions the following As the padding mechanism is used to improve protection against traffic analysis, removal of the padding SHOULD be implemented in a "constant-time" manner at the MLS layer and above layers to prevent timing side-channels that would provide attackers with information on the size of the plaintext. The padding length lengthofpadding can be chosen at the time of the message encryption by the sender. This seems out of date given that padding is a separate field that doesn't need to be explicitly stripped off at decrypt time and there is no other mention of in the document.
That seems right. We used to have a more clever, more economical padding scheme mirroring what TLS does. But that got rationalized a while ago. These are probably dangling references. Want to send a PR?
Sounds good, pushed PR URL

find out the plaintext by correlation between the question and the length.  <ins> The content and length of the "padding" field in "MLSCiphertextContent" can be chosen at the time of message encryption by the sender.  It is recommended that padding data is comprised of zero-valued bytes and follows an established deterministic padding scheme. </ins> 14.2.


12.  This protocol includes a mechanism for negotiating extension parameters similar to the one in TLS RFC8446.  In TLS, extension negotiation is one-to-one: The client offers extensions in its security level of the curves. The mandatory-to-implement ciphersuite for MLS 1.0 is <del> "MLS10_128_DHKEMX25519_AES128GCM_SHA256_Ed25519" which uses Curve25519 for key exchange, AES-128-GCM for HPKE, HKDF over SHA2-256, and Ed25519 for signatures. </del>  Values with the first byte 255 (decimal) are reserved for Private Use.

proposes to remove redundancy between version fields and ciphersuites by removing the version fields. This PR removes the version from the ciphersuite instead.
NAME suggests adding some advisory text in the extensibility section to the effect that future versions of the protocol should consider whether they want to keep all defined ciphersuites.
NAME here is some text we put in RFC 8447 (IANA registries for TLS) that was also included in the IANA registry to warn about cipher suites getting weaker over time. This could be modified to mention culling at different version. WARNING: : Cryptographic algorithms and parameters will be broken or weakened over time. Blindly implementing cipher suites listed here is not advised. Implementers and users need to check that the cryptographic algorithms listed continue to provide the expected level of security.
Affiliation -> Meta Platforms Address -> EMAIL
In , the field of is removed. I'm guessing the reason was that is was redundant with the ciphersuite, since the MLS version is at the time included in the ciphersuite. However, in , MLS versions were removed from ciphersuites, making the protocol version not redundant anymore with the ciphersuite. We should probably put the MLS version back there?
I just discovered that this is then fixed by adding the MLS version to in , closing.

12. <ins> The base MLS protocol can be extended in a few ways.  New ciphersuites can be added to enable the use of new cryptographic algorithms.  New types of proposals can be used to perform new actions within an epoch.  Extension fields can be used to add additional information to the protocol.  In this section, we discuss some constraints on these extensibility mechanisms that are necessary to ensure broad interoperability. 12.1. As discussed in ciphersuites, MLS allows the participants in a group to negotiate the cryptographic algorithms used within the group. This extensibility is important for maintaining the security of the protocol over time RFC7696.  It also creates a risk of interoperability failure due to clients not supporting a common ciphersuite. The ciphersuite registry defined in mls-ciphersuites attempts to strike a balance on this point.  On the one hand, the base policy for the registry is Specification Required, a fairly low bar designed to avoid the need for standards work in cases where different ciphers are needed for niche applications.  There is a higher bar (Standards Action) for ciphers to set the Recommended field in the registry. This higher bar is there in part to ensure that the interoperability implications of new ciphersuites are considered. MLS ciphersuites are defined independent of MLS versions, so that in principle the same ciphersuite can be used across versions. Standards work defining new versions of MLS should consider whether it is desirable for the new version to be compatible with existing ciphersuites, or whether the new version should rule out some ciphersuites.  For example, a new version could follow the example of HTTP/2, which restricted the set of allowed TLS ciphers (see Section 9.2.2 of RFC7540. 12.2. Commit messages do not have an extension field because the set of protocols is extensible.  As discussed in commit, Proposals with a non-default proposal type MUST NOT be included in a commit unless the proposal type is supported by all the members of the group that will process the Commit. 12.3. </ins> This protocol includes a mechanism for negotiating extension parameters similar to the one in TLS RFC8446.  In TLS, extension negotiation is one-to-one: The client offers extensions in its security level of the curves. The mandatory-to-implement ciphersuite for MLS 1.0 is  <ins> "MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519" which uses Curve25519 for key exchange, AES-128-GCM for HPKE, HKDF over SHA2-256, and Ed25519 for signatures. </ins> Values with the first byte 255 (decimal) are reserved for Private Use.


the group. The Commit MUST NOT combine proposals sent within different epochs. <del> In the event that a valid proposal is omitted from the next Commit, the sender of the proposal SHOULD retransmit it in the new epoch. </del>  A member of the group MAY send a Commit that references no proposals at all, which would thus have an empty "proposals" vector.  Such a

Addresses the comments in ( ) Since a member performing a commit including all valid proposals within an epoch is best effort, it makes sense to downgrade the requirement from MUST to SHOULD. IMO, MUST should be reserved for instances where the receiver would throw an error in the condition the sender did not 100% comply with the rule. Since in this case it is not a hard error under certain conditions, such as the member performing the commit having not received a proposal in transit while generating the commit, SHOULD is a better choice.
I'm not sure this PR addresses the issues raised in , especially regarding the redundancy of proposals. I would suggest the following: The sender of a commit SHOULD use all valid non-redundant proposals it has received In the event that a valid non-redundant proposal is omitted from the next Commit, the sender of the proposal SHOULD retransmit it in the new epoch
Re: non-redundant: I'm not sure we need to say "valid non-redundant". We can just include non-redundancy in the definition of "valid". Re: retransmit: I would frame this as "If a group member sent a proposal within an epoch that was not included in a Commit that initiates the next epoch, then the proposer MAY retransmit the proposal in the new epoch."
I believe the existing language in the "if there are multiple proposals ..." paragraph covers the non-redundant aspect of the requirements. How about this for retransmit? I can push a new commit if we think this helps "The Commit MUST NOT combine proposals sent within different epochs. In the event that a valid proposal is omitted from the next Commit, and that proposal is still valid in the current epoch, the sender of the proposal MAY retransmit it."
I think that works.
Pushed a commit to add the copy in my comment
Discussion on working call: Agreement to soften receiver requirement, keep MUST on sender NAME to update, then we can merge
Just posted new wording, let me know what you think
Looks good to me, thanks NAME
There are several rules in terms of selecting proposals and issuing commits against them. I have distilled the existing wording in the document into the following rules If there are multiple updates for the same leaf, cherry pick one. If there is a remove and an update/add for the same key package, always choose remove If there are multiple group context extension updates, cherry pick one The sender of a commit MUST use all valid proposals it has received In the event that a valid proposal is omitted from the next Commit, the sender of the proposal SHOULD retransmit it in the new epoch It seems that rule 4 and rule 5 conflict with each other in that one is suggesting that all proposals must be consumed or there should be a hard error, and the other assumes it is possible for a commit to purposefully leave out a proposal and it can optionally be retransmitted. I have been trying to write code that reasonably handles the cherry picking logic around multiple proposals referencing the same key package or multiple changes to group context while also enforcing the "must use all valid proposals received" rule, and it does introduce some complexity that maybe would be avoided by just eliminating that rule?
Good point. One situation where rule 5 would make sense is if we prohibit proposals by reference in external commits to solve the problems outlined in . Rule 5 would otherwise make sense in situations, where there's no agreement on whether a proposal is valid. One case where this is necessarily the case is the following: A remove proposal was sent, but the client that is to be removed is the committer in this epoch. The client can't commit their own removal, but the proposal is still technically valid from the proposer's point of view. There might be other situations, for example, where the application imposes some sort of policy that different clients interpret differently. This should be the application's problem, though.
I think what I'm trying to boil this down to is, should my implementation throw an error if all proposals are not consumed by the process of applying the commit I just received? I'm still thinking enforcing that would be too difficult to apply everywhere.
NAME I don't think you can be that strict, just because of the sender/receiver asymmetry. Just because you received a Proposal doesn't mean that the committer did. I would view the rules in the doc as local guidance for the committer in constructing a Commit. Given that they don't reflect externally-visible behavior, they should probably be downgraded to SHOULD.
NAME that makes sense to me, I'll put up a PR to change to SHOULD

the group. The Commit MUST NOT combine proposals sent within different epochs.  <ins> Due to the asynchronous nature of proposals, receivers of a Commit SHOULD NOT enforce that all valid proposals sent within the current epoch are referenced by the next Commit.  In the event that a valid proposal is omitted from the next Commit, and that proposal is still valid in the current epoch, the sender of the proposal MAY retransmit it. </ins> A member of the group MAY send a Commit that references no proposals at all, which would thus have an empty "proposals" vector.  Such a


The Messaging Layer Security (MLS) Protocol <del> draft-barnes-mls-protocol-latest </del>  Abstract RFC EDITOR: PLEASE REMOVE THE FOLLOWING PARAGRAPH The source for this draft is maintained in GitHub.  Suggested changes should be submitted <del> as pull requests at https://github.com/ekr/mls-protocol. </del>  Instructions are on that page as well.  Editorial changes can be managed in GitHub, but any substantive change should be discussed on the MLS mailing list.

Note: If you have an existing clone, you will need to delete at the root level of the clone in order for to work. ( will still work fine)

The Messaging Layer Security (MLS) Protocol  <ins> draft-ietf-mls-protocol-latest </ins> Abstract RFC EDITOR: PLEASE REMOVE THE FOLLOWING PARAGRAPH The source for this draft is maintained in GitHub.  Suggested changes should be submitted  <ins> as pull requests at https://github.com/mlswg/mls-protocol. </ins> Instructions are on that page as well.  Editorial changes can be managed in GitHub, but any substantive change should be discussed on the MLS mailing list.


"index" field in "PSKLabel" corresponds to the index of the PSK in the "psk" array, while the "count" field contains the total number of PSKs.  In other words, the PSKs are chained together with KDF.Extract <del> invocations, as follows: </del>  In particular, if there are no PreSharedKey proposals in a given Commit, then the resulting "psk_secret" is "psk_secret_[0]", the all-

Adjust ASCII art to fit within 72 character limit. Did not fix tables or code in appendices.

"index" field in "PSKLabel" corresponds to the index of the PSK in the "psk" array, while the "count" field contains the total number of PSKs.  In other words, the PSKs are chained together with KDF.Extract  <ins> invocations (labelled "Extract" for brevity in the diagram), as follows: </ins> In particular, if there are no PreSharedKey proposals in a given Commit, then the resulting "psk_secret" is "psk_secret_[0]", the all-


A signed object describing a client's identity and capabilities, and including a hybrid public-key encryption (HPKE I-D.irtf-cfrg- <del> hpke) public key that can be used to encrypt to that client. A key package that is prepublished by a client, which other clients can use to introduce the client to a new group. </del>  A signing key pair used to authenticate the sender of a message. messages directly, while in reality they would be sent encapsulated in MLSPlaintext or MLSCiphertext objects. <del> Before the initialization of a group, clients publish InitKeys (as KeyPackage objects) to a directory provided by the Service Provider. </del>  When a client A wants to establish a group with B and C, it first initializes a group state containing only itself and downloads algorithm to be used for signatures in MLSPlaintext and the tree signatures.  It MUST be the same as the signature algorithm specified in the credential field of the KeyPackage objects in the leaves of <del> the tree (including the InitKeys used to add new members). </del>  To disambiguate different signatures used in MLS, each signed value is prefixed by a label as shown below: along with an "endpoint_id" that, combined with the client's identity, serve to uniquely identify a client in a group. <del> When used as InitKeys, KeyPackages are intended to be used only once and SHOULD NOT be reused except in case of last resort.  (See initkey-reuse).  Clients MAY generate and publish multiple InitKeys to support multiple ciphersuites. </del>  The value for hpke_init_key MUST be a public key for the asymmetric encryption scheme defined by cipher_suite, and it MUST be unique In this process, the joiner sends a new "init_secret" value to the group using the HPKE export method.  The joiner then uses that <del> "init_secret" with information provided in the GroupInfo and an external Commit to initialize their copy of the key schedule for the new epoch. </del>  Members of the group receive the "kem_output" in an ExternalInit proposal and preform the corresponding calculation to retrieve the "init_secret" value. <del> In both cases, the "info" input to HPKE is set to the GroupInfo for the previous epoch, encoded using the TLS serialization. </del>  8.2. to encrypt or decrypt a message. Post-compromise security is also provided for new groups by members <del> regularly generating new InitKeys and uploading them to the Delivery Service, such that compromised key material won't be used when the member is added to a new group. </del>  15.4. <del> InitKeys are intended to be used only once.  That is, once an InitKey has been used to introduce the corresponding client to a group, it SHOULD be deleted from the InitKey publication system.  Reuse of InitKeys can lead to replay attacks. </del>  <del> An application MAY allow for reuse of a "last resort" InitKey in order to prevent denial of service attacks.  Since an InitKey is </del>  needed to add a client to a new group, an attacker could prevent a client being added to new groups by exhausting all available <del> InitKeys. </del>  15.5.

Complete the renaming of InitKeys -> KeyPackages; and GroupInfo -> PublicGroupState
Comment by NAME Hmmm :P
Cf. RFC 8446 We could provide some more citations to the analyses that are now in the literature if you think that would help.
CC NAME NAME NAME for citations
They are also in this architecture document PR: URL
Oh good, I'll just add a citation over to mls-architecture.

A signed object describing a client's identity and capabilities, and including a hybrid public-key encryption (HPKE I-D.irtf-cfrg-  <ins> hpke) public key that can be used to encrypt to that client, and which other clients can use to introduce the client to a new group. </ins> A signing key pair used to authenticate the sender of a message. messages directly, while in reality they would be sent encapsulated in MLSPlaintext or MLSCiphertext objects.  <ins> Before the initialization of a group, clients publish KeyPackages to a directory provided by the Service Provider. </ins> When a client A wants to establish a group with B and C, it first initializes a group state containing only itself and downloads algorithm to be used for signatures in MLSPlaintext and the tree signatures.  It MUST be the same as the signature algorithm specified in the credential field of the KeyPackage objects in the leaves of  <ins> the tree (including those used to add new members). </ins> To disambiguate different signatures used in MLS, each signed value is prefixed by a label as shown below: along with an "endpoint_id" that, combined with the client's identity, serve to uniquely identify a client in a group.  <ins> KeyPackages are intended to be used only once and SHOULD NOT be reused except in case of last resort.  (See keypackage-reuse). Clients MAY generate and publish multiple KeyPackages to support multiple ciphersuites. </ins> The value for hpke_init_key MUST be a public key for the asymmetric encryption scheme defined by cipher_suite, and it MUST be unique In this process, the joiner sends a new "init_secret" value to the group using the HPKE export method.  The joiner then uses that  <ins> "init_secret" with information provided in the PublicGroupState and an external Commit to initialize their copy of the key schedule for the new epoch. </ins> Members of the group receive the "kem_output" in an ExternalInit proposal and preform the corresponding calculation to retrieve the "init_secret" value.  <ins> In both cases, the "info" input to HPKE is set to the PublicGroupState for the previous epoch, encoded using the TLS serialization. </ins> 8.2. to encrypt or decrypt a message. Post-compromise security is also provided for new groups by members  <ins> regularly generating new KeyPackages and uploading them to the Delivery Service, such that compromised key material won't be used when the member is added to a new group. </ins> 15.4.  <ins> KeyPackages are intended to be used only once.  That is, once a KeyPackage has been used to introduce the corresponding client to a group, it SHOULD be deleted from the KeyPackage publication system. Reuse of KeyPackages can lead to replay attacks. </ins>  <ins> An application MAY allow for reuse of a "last resort" KeyPackage in order to prevent denial of service attacks.  Since a KeyPackage is </ins> needed to add a client to a new group, an attacker could prevent a client being added to new groups by exhausting all available  <ins> KeyPackages. </ins> 15.5.


state with other clients.  A client is defined by the cryptographic keys it holds. <del> A linear sequence of epochs in which each epoch depends on its </del>  predecessor. A state of a group in which a specific set of authenticated

The definition of an MLS group in the 2nd paragraph of the Protocol Overview is really good. Let's use it as the definition in the Terminology section as well.

state with other clients.  A client is defined by the cryptographic keys it holds.  <ins> A group represents a logical collection of clents that share a common secret value at any given time.  Its state is represented as a linear sequence of epochs in which each epoch depends on its </ins> predecessor. A state of a group in which a specific set of authenticated


A node in the tree may also be _blank_, indicating that no value is present at that node.  The _resolution_ of a node is an ordered list of non-blank nodes that collectively cover all non-blank descendants <del> of the node.  The resolution of a node is effectively a depth-first, left-first enumeration of the nearest non-blank nodes below the node: </del>  The resolution of a non-blank node comprises the node itself, followed by its list of unmerged leaves, if any

In the most basic case, the resolution of a node is the node, so let's say that.
Fwiw this is stated immediately below your note:
Bren2010 said: >Fwiw this is stated immediately below your note: Yes. And it took me three readings to understand that the most complicated scenario was being described first and the most basic scenario is not explicitly stated. Most readers will not understand in what cases there will be unmerged leaves by this point.
Looks good with one minor edit.

A node in the tree may also be _blank_, indicating that no value is present at that node.  The _resolution_ of a node is an ordered list of non-blank nodes that collectively cover all non-blank descendants  <ins> of the node.  The resolution of a non-blank node with no unmerged leaves is just the node itself.  More generally, the resolution of a node is effectively a depth-first, left-first enumeration of the nearest non-blank nodes below the node: </ins> The resolution of a non-blank node comprises the node itself, followed by its list of unmerged leaves, if any


External Commits work like regular Commits, with a few differences:  The proposals included by value in an External Commit MUST meet the following conditions:

cc NAME
Comment by NAME Citation needed? I think most phones have AES(_GCM) in hardware too ? Also compared to HPKE operations and the times for epoch/group reinit, does this performance really matter? (eg waking up from sleep to increment the epoch would make the symmetric encryption cost pretty irrelevant. I would remove this sentence.
I'll remove the "mobile processors" bit and phrase it a little more evenhandedly ("can choose ChaCha or GCM"), but I think it's fair to say that there might be environments where ChaCha/Poly is more performant than AES-GCM.
Thanks. I do think you might be right that "there are", but I also do not think "mobile" is that :)
The spec currently doesn't distinguish much between an external and a member commit w.r.t. how proposals by reference are handled. In theory, the server could keep track of proposals and send the external committer all proposals that were sent up until the time they want to commit externally. However, there are a few difficulties in committing proposals by reference in an external commit. Membership tags: The external committer doesn't have the ability to verify membership tags on proposals. This is probably not a show-stopper, as proposals still need to be signed by a group member, but if nothing else, this should be noted in the spec. Leaf positioning: The spec was changed such that the committer is added into the tree without an Add proposal being present. If there are other Add (and Remove) proposals present, it is unclear in what order they should be processed. Should the committer be added when the path is processed? Or should it still be processed as if it were an Add? There are probably other things that can go wrong when committing referenced proposals externally, but those are the first that come to mind. The simplest solution would be to simply ignore proposals by reference, although this would break the current principle that a commit must always include all valid proposals that were sent during the epoch. The consequence would be that Parties that sent proposals will have to re-send them if the epoch is ended by an external commit. It should be noted, that a Preconfigured sender will have no way to detect if their proposal was committed or not.
I'm not sure your worries here are very real: Membership tags: I don't think the membership tag issue matters, because the existing members will reject the proposal (and thus the commit) if the membership tag is invalid; at worst the joiner gets themselves wedged into a group by themselves. And as you say, they're signed, and they keys can be tied back to the tree hash in the GroupInfo. Leaf positioning: We need to clear this up regardless of proposals-by-reference. It seems like basically having a synthetic Add at the beginning or end of the proposal list would work fine. Preconfigured proposer: This is a problem only when Commits are encrypted, so it's not a problem in the external commit case (since an external Commit cannot be encrypted). Nonetheless, the point that an external joiner can't fully verify a Proposal seems to argue pretty strongly for forbidding proposals by reference. I don't think this even violates the semantic of "include all valid proposals received in the epoch", since (1) that requirement is receiver oriented (it's not enough for the proposals to have been sent) and (2) the external joiner can't tell if the proposals or valid. Overall, I would lean slightly toward removing proposals by reference. And if we do that, it might even make sense to just define an ExternalCommit message that packages together the few things that are still allowed.
I'm not sure I understand how the receiver-orientation of the "all valid proposals must be included" rule matters to its application to this case. If we remove the requirement for external commits to include proposals by reference and don't soften that rule to exclude external commits, receivers will consider all external commits invalid in the presence of valid pending proposals by reference. In any case, I'm also in favor of keeping it simple and restrict the External Commit to a few, well defined components that do not include any proposals by reference.
Discussion on working call: "No proposals by reference" kind of follows from "all valid proposals", since the joiner can't necessarily determine that proposals are valid Maybe allow attempts? Might not get feedback to retry, though. Whole point of external commit is to allow async. Agreement to prohibit proposals by reference in external Commit

External Commits work like regular Commits, with a few differences: <ins> The Commit MUST NOT include any proposals by reference, since an external joiner cannot determine the validity of proposals sent within the group </ins> The proposals included by value in an External Commit MUST meet the following conditions:


efficiently encrypt messages to subsets of the group.  Each epoch has a distinct ratchet tree. <del> Each member of the group maintains a view of these two facets of the </del>  group's state.  MLS messages are used to initialize these views and keep them in sync as the group transitions between epochs. key schedule also defines a collection of secrets that are derived from the epoch secret.  For example: <del> An _encryption secret_ that is used to initialize a _secret tree_, which provides keys for encrypting handshake and application messages and providing forward secrecy for these messages within an epoch. </del>  A _confirmation key_ that is used to confirm that all members agree on the shared state of the group.

In the Protocol Overview, Figure 1 shows a ratchet tree, commitsecret, epochsecret, encryptionsecret, and a secret tree, while the text discusses the epochsecret and encryption_secret. Then the next section, Cryptographic State and Evolution states that "There are two types of cryptographic state at the core of MLS:" a key schedule, and a ratchet tree. The Secret Tree isn't mentioned there. Then we have a Key Schedule section which then includes a Secret Tree sub-section. Readers are left wondering what the relationship is between the the Key Schedule and Secret Tree.

efficiently encrypt messages to subsets of the group.  Each epoch has a distinct ratchet tree.  <ins> A _secret tree_ derived from the key schedule that represents shared secrets used by the members of the group to provide confidentiality and forward secrecy for MLS messages.  Each epoch has a distinct secret tree. Each member of the group maintains a view of these facets of the </ins> group's state.  MLS messages are used to initialize these views and keep them in sync as the group transitions between epochs. key schedule also defines a collection of secrets that are derived from the epoch secret.  For example:  <ins> An _encryption secret_ that is used to initialize the secret tree for the epoch. </ins> A _confirmation key_ that is used to confirm that all members agree on the shared state of the group.


4.1. <del> There are two types of cryptographic state at the core of MLS: A _key schedule_ that represents the shared secret state of the group and its evolution from one epoch to the next. </del>  A _ratchet tree_ that represents the membership of the group, providing group members a way to authenticate each other and efficiently encrypt messages to subsets of the group.  Each epoch <del> has a distinct ratchet tree. </del>  A _secret tree_ derived from the key schedule that represents shared secrets used by the members of the group to provide

Note that is currently under , so that should probably move or the top level section should be renamed. I ran out of imagination for today.

4.1.  <ins> The cryptographic state at the core of MLS is divided into three areas of responsibility: </ins> A _ratchet tree_ that represents the membership of the group, providing group members a way to authenticate each other and efficiently encrypt messages to subsets of the group.  Each epoch  <ins> has a distinct ratchet tree.  It seeds the _key schedule_. A _key schedule_ that describes the chain of key derivations used to progress from epoch to epoch (mainly using the _init_secret_ and _epoch_secret_); and to derive a variety of other secrets (see epoch-derived-secrets) used during the current epoch.  One of these (the _encryption_secret_) is the root of _secret_tree_. </ins> A _secret tree_ derived from the key schedule that represents shared secrets used by the members of the group to provide


provides FS with regard to any removed members and PCS for the committer and any updated members.  When creating or processing a Commit, three different ratchet trees and their associated GroupContexts are used: "epoch": The epoch that the group will be in after this Commit  A member of the group applies a Commit message by taking the following steps: The confirmation tag value confirms that the members of the group have arrived at the same state of the group: <del> 11.2.1. </del>  New members can join the group in two ways.  Either by being added by a group member, or by adding themselves through an external Commit. "signer".  The signature covers the following structure, comprising all the fields in the GroupInfo above "signature": <del> 11.2.1.1. </del>  External Commits are a mechanism for new members (external parties that want to become members of the group) to add themselves to a application will need to ensure that continuing, non-resync'ing members have the required PSK. <del> 11.2.1.2. </del>  The sender of a Commit message is responsible for sending a Welcome message to any new members added via Add proposals.  The Welcome

As suggested by NAME to improve the legibility of the document structure.
Sounds good. Queue it up for after the reorg :)
This issue accompanies the e-mail [1] pointing out editorial issues with the draft and suggesting improvements. TL;DR: I don't think the draft currently is what is needed to implement MLS. It needs an editorial overhaul with a better structure. [1] URL
If we want an implementation document, which is a legitimate ask, we should write an implementation document.
What's the purpose of the document if not for implementation? In order for this draft to become an RFC it needs implementations. I don't see a way this will happen with the draft as is.
Specifying a functional behavior and providing a guide for implementation are two very different things. Re: what you suggested in your email: Internal representations of the tree, the way you number the indices, how you implement them or the fact that you store some hashes in the nodes are completely irrelevant to this document. Also, there is nothing formal about this appendix. On the other hand the information you need from this document is how to perform computations : things such as providing the arguments to the nodeencap/nodedecap functions which are required to generate a correct payload and a standard way to serialize abstract structures... etc.
Right, the document is doing neither right now.
We have two separable issues here: We need better explanation of the overall structure of the protocol (as in Franziskus' email), and we could rearrange the text to be clearer. For the first, I have filed . That provides a pretty complete overview of the protocol, covering all of the syntax and high-level operations. For the second, we should do a second PR after the other outstanding PRs have landed. Looking at the table of contents, I would propose we refactor in the following way: For top-level sections: Move to after and For subsections: Move the tree-relevant subsections of into — including , , and Split into two: Put the GroupContext definition part in a new subsection under Put the transcript updating part in a new subsection under
I have an alternative organization of the first sections: Add the one sentence definition of before the last paragraph of Swap and This seems to all flow nicely with very few forward references. A more radical rewrite might Move forward before but I don't think that is necessary. I do like Richard's proposal to split
I'm considering this fixed after the series of reorg PRs that NAME NAME and I just finished.
This is a good start. I may propose some additional refactoring of the text before ### Creating a Commit in another PR. Hi all, While trying to implement the MLS protocol from scratch I ran into a couple issues and I think there’s a general problem regarding the structure of the draft. Note that these are all editorial issues. Right now I see no straightforward way to implement the spec as there’s no clear structure. To implement even the basic tree structure it’s necessary to jump through the entire document. There’s also a lot of prose and examples and very little implementable parts. Let me give some examples: Section 5 (Ratchet Trees) reads like a non-normative section (or paper). The really interesting bit related to trees is in Appendix A. I suggest moving the formal description of the tree from Appendix A to this section. It’s impossible to implement this from the hand full examples here. There’s no description in here on how the tree actually works. For example, adding a node to the tree is defined in 10.1.1 (Add). The ratchet tree nodes as described in Section 5 don't hold a KeyPackage. But Section 7 says “As the KeyPackage is a structure which is stored in the Ratchet Tree and updated depending on the evolution of this tree”. The tree nodes have a parent hash field, which is of course part of the ratchet tree but shouldn’t be necessary for the description of the tree structure. Also, the description of the tree hash (Section 7.5) follows the definition of the parenthash extension in Section 7.4 such that it is not entirely clear which parenthash is used there (extension or node value). Section 5 also talks about commit messages without linking to them or introducing them. Having to talk about commit messages in this section is also a little confusing. The term “handshake message” is first used in Section 7.8 (Key Schedule) but never introduced. This should probably happen in 10 (Group Evolution), which doesn’t talk about handshake messages at all until 10.3 (Ratchet Tree Extension). There are probably plenty more issues like this. But I don’t think addressing any of these issues individually makes sense. I therefore suggest having an overhaul of the document structure. The way I see MLS there are a set of high-level structures and components that should be clearly described and built upon each other. There’s a data structure (tree) that is used to represent a group. Then there is the MLS group that implements a specific semantic on the tree. Operations to modify the group are specified in the handshake protocol. And application messages define messages encrypted with keys derived in the handshake protocol with the key schedule. The tree is a basic left-balanced binary tree and defines functions to create a tree, and add and remove nodes. A group is the main part of MLS, a tree with nodes of a certain type. So this part should describe what nodes look like and how the tree is used to compute tree invariants and keys. The handshake protocol defines all messages needed to perform operations on the MLS group. This is certainly not exhaustive and would need further refinement. But it’s the high-level structure I’d expect from the document. I’m sure other structures would work as well. But what we currently have isn’t what is needed to implement MLS imho. Cheers, Franziskus

provides FS with regard to any removed members and PCS for the committer and any updated members. <ins> 11.2.1. </ins> When creating or processing a Commit, three different ratchet trees and their associated GroupContexts are used: "epoch": The epoch that the group will be in after this Commit <ins> 11.2.2. </ins> A member of the group applies a Commit message by taking the following steps: The confirmation tag value confirms that the members of the group have arrived at the same state of the group:  <ins> 11.2.3. </ins> New members can join the group in two ways.  Either by being added by a group member, or by adding themselves through an external Commit. "signer".  The signature covers the following structure, comprising all the fields in the GroupInfo above "signature":  <ins> 11.2.3.1. </ins> External Commits are a mechanism for new members (external parties that want to become members of the group) to add themselves to a application will need to ensure that continuing, non-resync'ing members have the required PSK.  <ins> 11.2.3.2. </ins> The sender of a Commit message is responsible for sending a Welcome message to any new members added via Add proposals.  The Welcome


Each outdated node key pair that was replaced by the update.  9. In order to facilitate the asynchronous addition of clients to a 9.5. <del> The "parent_hash" extension carries information to authenticate the structure of the tree, as described below. Consider a ratchet tree with a parent node P and children V and S. The parent hash of P changes whenever an "UpdatePath" object is applied to the ratchet tree along a path traversing node V (and hence also P).  The new "Parent Hash of P (with Co-Path Child S)" is obtained by hashing P's "ParentHashInput" struct using the resolution of S to populate the "original_child_resolution" field.  This way, P's Parent Hash fixes the new HPKE public keys of all nodes on the path from P to the root.  Furthermore, for each such key PK the hash also binds the set of HPKE public keys to which PK's secret key was encrypted in the Commit that contained the "UpdatePath" object. The Parent Hash of P with Co-Path Child S is the hash of a "ParentHashInput" object populated as follows.  The field "public_key" contains the HPKE public key of P.  If P is the root, then "parent_hash" is set to a zero-length octet string.  Otherwise "parent_hash" is the Parent Hash of P's parent with P's sibling as the co-path child. Finally, "original_child_resolution" is the array of "HPKEPublicKey" values of the nodes in the resolution of S but with the "unmerged_leaves" of P omitted.  For example, in the ratchet tree depicted in resolution-example the "ParentHashInput" of node Z with co-path child C would contain an empty "original_child_resolution" since C's resolution includes only itself but C is also an unmerged leaf of Z.  Meanwhile, the "ParentHashInput" of node Z with co-path child D has an array with one element in it: the HPKE public key of D. 9.5.1. The Parent Hash of P appears in three types of structs.  If V is itself a parent node then P's Parent Hash is stored in the "parent_hash" fields of both V's "ParentHashInput" struct and V's "ParentNode" struct.  (The "ParentNode" struct is used to encapsulate all public information about V that must be conveyed to a new member joining the group as well as to define the Tree Hash of node V.) If, on the other hand, V is a leaf and its KeyPackage contains the "parent_hash" extension then the Parent Hash of P (with V's sibling as co-path child) is stored in that field.  In particular, the extension MUST be present in the "leaf_key_package" field of an "UpdatePath" object.  (This way, the signature of such a KeyPackage also serves to attest to which keys the group member introduced into the ratchet tree and to whom the corresponding secret keys were sent. This helps prevent malicious insiders from constructing artificial ratchet trees with a node V whose HPKE secret key is known to the insider yet where the insider isn't assigned a leaf in the subtree rooted at V.  Indeed, such a ratchet tree would violate the tree invariant.) 9.5.2. To this end, when processing a Commit message clients MUST recompute the expected value of "parent_hash" for the committer's new leaf and verify that it matches the "parent_hash" value in the supplied "leaf_key_package".  Moreover, when joining a group, new members MUST authenticate each non-blank parent node P.  A parent node P is authenticated by performing the following check: Let L and R be the left and right children of P, respectively If L.parent_hash is equal to the Parent Hash of P with Co-Path Child R, the check passes If R is blank, replace R with its left child until R is either non-blank or a leaf node If R is a blank leaf node, the check fails If R.parent_hash is equal to the Parent Hash of P with Co-Path Child L, the check passes Otherwise, the check fails The left-child recursion under the right child of P is necessary because the expansion of the tree to the right due to Add proposals can cause blank nodes to be interposed between a parent node and its right child. 9.6. To allow group members to verify that they agree on the public cryptographic state of the group, this section defines a scheme for generating a hash value (called the "tree hash") that represents the contents of the group's ratchet tree and the members' KeyPackages. The tree hash of a tree is the tree hash of its root node, which we define recursively, starting with the leaves. As some nodes may be blank while others contain data we use the following struct to include data if present. The tree hash of a leaf node is the hash of leaf's "LeafNodeHashInput" object which might include a Key Package depending on whether or not it is blank. Now the tree hash of any non-leaf node is recursively defined to be the hash of its "ParentNodeHashInput".  This includes an optional "ParentNode" object depending on whether the node is blank or not. The "left_hash" and "right_hash" fields hold the tree hashes of the node's left and right children, respectively. 9.7. </del> Each member of the group maintains a GroupContext object that summarizes the state of the group: "interim_transcript_hash" field is set to the zero-length octet string. <del> 9.8. As described in commit, each MLS Commit message may optionally transmit a KeyPackage leaf and node values along its direct path. The path contains a public key and encrypted secret value for all intermediate nodes in the path above the leaf.  The path is ordered from the closest node to the leaf to the root; each node MUST be the parent of its predecessor. For each "UpdatePathNode", the resolution of the corresponding copath node MUST be filtered by removing all new leaf nodes added as part of this MLS Commit message.  The number of ciphertexts in the "encrypted_path_secret" vector MUST be equal to the length of the filtered resolution, with each ciphertext being the encryption to the respective resolution node. The HPKECiphertext values are computed as where "node_public_key" is the public key of the node that the path secret is being encrypted for, group_context is the current GroupContext object for the group, and the functions "SetupBaseS" and "Seal" are defined according to I-D.irtf-cfrg-hpke. Decryption is performed in the corresponding way, using the private key of the resolution node. </del> 10. Group keys are derived using the "Extract" and "Expand" functions

Depends on Some subsections of are more natural within the ratchet tree operations section. This PR pulls them up into the section.
Again, this diff looks better with
This issue accompanies the e-mail [1] pointing out editorial issues with the draft and suggesting improvements. TL;DR: I don't think the draft currently is what is needed to implement MLS. It needs an editorial overhaul with a better structure. [1] URL
If we want an implementation document, which is a legitimate ask, we should write an implementation document.
What's the purpose of the document if not for implementation? In order for this draft to become an RFC it needs implementations. I don't see a way this will happen with the draft as is.
Specifying a functional behavior and providing a guide for implementation are two very different things. Re: what you suggested in your email: Internal representations of the tree, the way you number the indices, how you implement them or the fact that you store some hashes in the nodes are completely irrelevant to this document. Also, there is nothing formal about this appendix. On the other hand the information you need from this document is how to perform computations : things such as providing the arguments to the nodeencap/nodedecap functions which are required to generate a correct payload and a standard way to serialize abstract structures... etc.
Right, the document is doing neither right now.
We have two separable issues here: We need better explanation of the overall structure of the protocol (as in Franziskus' email), and we could rearrange the text to be clearer. For the first, I have filed . That provides a pretty complete overview of the protocol, covering all of the syntax and high-level operations. For the second, we should do a second PR after the other outstanding PRs have landed. Looking at the table of contents, I would propose we refactor in the following way: For top-level sections: Move to after and For subsections: Move the tree-relevant subsections of into — including , , and Split into two: Put the GroupContext definition part in a new subsection under Put the transcript updating part in a new subsection under
I have an alternative organization of the first sections: Add the one sentence definition of before the last paragraph of Swap and This seems to all flow nicely with very few forward references. A more radical rewrite might Move forward before but I don't think that is necessary. I do like Richard's proposal to split
I'm considering this fixed after the series of reorg PRs that NAME NAME and I just finished. Hi all, While trying to implement the MLS protocol from scratch I ran into a couple issues and I think there’s a general problem regarding the structure of the draft. Note that these are all editorial issues. Right now I see no straightforward way to implement the spec as there’s no clear structure. To implement even the basic tree structure it’s necessary to jump through the entire document. There’s also a lot of prose and examples and very little implementable parts. Let me give some examples: Section 5 (Ratchet Trees) reads like a non-normative section (or paper). The really interesting bit related to trees is in Appendix A. I suggest moving the formal description of the tree from Appendix A to this section. It’s impossible to implement this from the hand full examples here. There’s no description in here on how the tree actually works. For example, adding a node to the tree is defined in 10.1.1 (Add). The ratchet tree nodes as described in Section 5 don't hold a KeyPackage. But Section 7 says “As the KeyPackage is a structure which is stored in the Ratchet Tree and updated depending on the evolution of this tree”. The tree nodes have a parent hash field, which is of course part of the ratchet tree but shouldn’t be necessary for the description of the tree structure. Also, the description of the tree hash (Section 7.5) follows the definition of the parenthash extension in Section 7.4 such that it is not entirely clear which parenthash is used there (extension or node value). Section 5 also talks about commit messages without linking to them or introducing them. Having to talk about commit messages in this section is also a little confusing. The term “handshake message” is first used in Section 7.8 (Key Schedule) but never introduced. This should probably happen in 10 (Group Evolution), which doesn’t talk about handshake messages at all until 10.3 (Ratchet Tree Extension). There are probably plenty more issues like this. But I don’t think addressing any of these issues individually makes sense. I therefore suggest having an overhaul of the document structure. The way I see MLS there are a set of high-level structures and components that should be clearly described and built upon each other. There’s a data structure (tree) that is used to represent a group. Then there is the MLS group that implements a specific semantic on the tree. Operations to modify the group are specified in the handshake protocol. And application messages define messages encrypted with keys derived in the handshake protocol with the key schedule. The tree is a basic left-balanced binary tree and defines functions to create a tree, and add and remove nodes. A group is the main part of MLS, a tree with nodes of a certain type. So this part should describe what nodes look like and how the tree is used to compute tree invariants and keys. The handshake protocol defines all messages needed to perform operations on the MLS group. This is certainly not exhaustive and would need further refinement. But it’s the high-level structure I’d expect from the document. I’m sure other structures would work as well. But what we currently have isn’t what is needed to implement MLS imho. Cheers, Franziskus

Each outdated node key pair that was replaced by the update. <ins> 8.4. To allow group members to verify that they agree on the public cryptographic state of the group, this section defines a scheme for generating a hash value (called the "tree hash") that represents the contents of the group's ratchet tree and the members' KeyPackages. The tree hash of a tree is the tree hash of its root node, which we define recursively, starting with the leaves. As some nodes may be blank while others contain data we use the following struct to include data if present. The tree hash of a leaf node is the hash of leaf's "LeafNodeHashInput" object which might include a Key Package depending on whether or not it is blank. Now the tree hash of any non-leaf node is recursively defined to be the hash of its "ParentNodeHashInput".  This includes an optional "ParentNode" object depending on whether the node is blank or not. The "left_hash" and "right_hash" fields hold the tree hashes of the node's left and right children, respectively. 8.5. The "parent_hash" extension carries information to authenticate the structure of the tree, as described below. Consider a ratchet tree with a parent node P and children V and S. The parent hash of P changes whenever an "UpdatePath" object is applied to the ratchet tree along a path traversing node V (and hence also P).  The new "Parent Hash of P (with Co-Path Child S)" is obtained by hashing P's "ParentHashInput" struct using the resolution of S to populate the "original_child_resolution" field.  This way, P's Parent Hash fixes the new HPKE public keys of all nodes on the path from P to the root.  Furthermore, for each such key PK the hash also binds the set of HPKE public keys to which PK's secret key was encrypted in the Commit that contained the "UpdatePath" object. The Parent Hash of P with Co-Path Child S is the hash of a "ParentHashInput" object populated as follows.  The field "public_key" contains the HPKE public key of P.  If P is the root, then "parent_hash" is set to a zero-length octet string.  Otherwise "parent_hash" is the Parent Hash of P's parent with P's sibling as the co-path child. Finally, "original_child_resolution" is the array of "HPKEPublicKey" values of the nodes in the resolution of S but with the "unmerged_leaves" of P omitted.  For example, in the ratchet tree depicted in resolution-example the "ParentHashInput" of node Z with co-path child C would contain an empty "original_child_resolution" since C's resolution includes only itself but C is also an unmerged leaf of Z.  Meanwhile, the "ParentHashInput" of node Z with co-path child D has an array with one element in it: the HPKE public key of D. 8.5.1. The Parent Hash of P appears in three types of structs.  If V is itself a parent node then P's Parent Hash is stored in the "parent_hash" fields of both V's "ParentHashInput" struct and V's "ParentNode" struct.  (The "ParentNode" struct is used to encapsulate all public information about V that must be conveyed to a new member joining the group as well as to define the Tree Hash of node V.) If, on the other hand, V is a leaf and its KeyPackage contains the "parent_hash" extension then the Parent Hash of P (with V's sibling as co-path child) is stored in that field.  In particular, the extension MUST be present in the "leaf_key_package" field of an "UpdatePath" object.  (This way, the signature of such a KeyPackage also serves to attest to which keys the group member introduced into the ratchet tree and to whom the corresponding secret keys were sent. This helps prevent malicious insiders from constructing artificial ratchet trees with a node V whose HPKE secret key is known to the insider yet where the insider isn't assigned a leaf in the subtree rooted at V.  Indeed, such a ratchet tree would violate the tree invariant.) 8.5.2. To this end, when processing a Commit message clients MUST recompute the expected value of "parent_hash" for the committer's new leaf and verify that it matches the "parent_hash" value in the supplied "leaf_key_package".  Moreover, when joining a group, new members MUST authenticate each non-blank parent node P.  A parent node P is authenticated by performing the following check: Let L and R be the left and right children of P, respectively If L.parent_hash is equal to the Parent Hash of P with Co-Path Child R, the check passes If R is blank, replace R with its left child until R is either non-blank or a leaf node If R is a blank leaf node, the check fails If R.parent_hash is equal to the Parent Hash of P with Co-Path Child L, the check passes Otherwise, the check fails The left-child recursion under the right child of P is necessary because the expansion of the tree to the right due to Add proposals can cause blank nodes to be interposed between a parent node and its right child. 8.6. As described in commit, each MLS Commit message may optionally transmit a KeyPackage leaf and node values along its direct path. The path contains a public key and encrypted secret value for all intermediate nodes in the path above the leaf.  The path is ordered from the closest node to the leaf to the root; each node MUST be the parent of its predecessor. For each "UpdatePathNode", the resolution of the corresponding copath node MUST be filtered by removing all new leaf nodes added as part of this MLS Commit message.  The number of ciphertexts in the "encrypted_path_secret" vector MUST be equal to the length of the filtered resolution, with each ciphertext being the encryption to the respective resolution node. The HPKECiphertext values are computed as where "node_public_key" is the public key of the node that the path secret is being encrypted for, group_context is the current GroupContext object for the group, and the functions "SetupBaseS" and "Seal" are defined according to I-D.irtf-cfrg-hpke. Decryption is performed in the corresponding way, using the private key of the resolution node. </ins> 9. In order to facilitate the asynchronous addition of clients to a 9.5.  Each member of the group maintains a GroupContext object that summarizes the state of the group: "interim_transcript_hash" field is set to the zero-length octet string.  10. Group keys are derived using the "Extract" and "Expand" functions