9. <del> In order to facilitate the asynchronous addition of clients to a group, key packages are pre-published that provide some public information about a user.  A KeyPackage object specifies: A protocol version and ciphersuite that the client supports, a public key that others can use for key agreement, a credential authenticating the client's application-layer identity, and an "endpoint_id" that uniquely identifies a client in a group. 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. 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 among the set of KeyPackages created by this client.  The whole structure is signed using the client's signature key.  A KeyPackage object with an invalid signature field MUST be considered malformed. The signature is computed by the function "SignWithLabel" with a label "KeyPackage" and a content comprising of all of the fields except for the signature field. KeyPackage objects MUST contain at least two extensions, one of type "capabilities", and one of type "lifetime".  The "capabilities" extension allows MLS session establishment to be safe from downgrade attacks on the parameters described (as discussed in group-creation), while still only advertising one version / ciphersuite per KeyPackage. As the "KeyPackage" is a structure which is stored in the Ratchet Tree and updated depending on the evolution of the tree, each modification of its content MUST be reflected by a change in its signature.  This allows other members to verify the validity of the KeyPackage at any time, particularly in the case of a newcomer joining the group. 9.1. The validity of a KeyPackage needs to be verified at a few stages: When a KeyPackage is downloaded by a group member, before it is used to add the client to the group When a KeyPackage is received by a group member in an Add, Update, or Commit message When a client joining a group receives KeyPackages for the other members of the group in the group's ratchet tree The client verifies the validity of a KeyPackage using the following steps: Verify that the credential in the KeyPackage is valid according to the authentication service and the client's local policy.  These actions MUST be the same regardless of at what point in the protocol the KeyPackage is being verified. Verify that the signature on the KeyPackage is valid using the public key in the KeyPackage's credential Verify that the KeyPackage is compatible with the group's parameters.  The ciphersuite and protocol version of the KeyPackage must match those in use in the group.  If the GroupContext has a "required_capabilities" extension, then the required extensions and proposals MUST be listed in the KeyPackage's "capabilities" extension. Verify that the following fields in the KeyPackage are unique among the members of the group (including any other members added in the same Commit): "credential.signature_key" "hpke_init_key" "endpoint_id" 9.2. The "capabilities" extension indicates what protocol versions, ciphersuites, protocol extensions, and non-default proposal types are supported by a client.  Proposal types defined in this document are considered "default" and thus need not be listed. This extension MUST be always present in a KeyPackage.  Extensions that appear in the "extensions" field of a KeyPackage MUST be included in the "extensions" field of the "capabilities" extension. 9.3. The "lifetime" extension represents the times between which clients will consider a KeyPackage valid.  This time is represented as an absolute time, measured in seconds since the Unix epoch (1970-01-01T00:00:00Z).  A client MUST NOT use the data in a KeyPackage for any processing before the "not_before" date, or after the "not_after" date. Applications MUST define a maximum total lifetime that is acceptable for a KeyPackage, and reject any KeyPackage where the total lifetime is longer than this duration. This extension MUST always be present in a KeyPackage. 9.4. Within MLS, a KeyPackage is identified by its hash (see, e.g., joining-via-welcome-message).  The "external_key_id" extension allows applications to add an explicit, application-defined identifier to a KeyPackage. 9.5. Each member of the group maintains a GroupContext object that summarizes the state of the group: The fields in this state have the following semantics: The "group_id" field is an application-defined identifier for the group. The "epoch" field represents the current version of the group key. The "tree_hash" field contains a commitment to the contents of the group's ratchet tree and the credentials for the members of the group, as described in tree-hashes. The "confirmed_transcript_hash" field contains a running hash over the messages that led to this state. The "extensions" field contains the details of any protocol extensions that apply to the group. When a new member is added to the group, an existing member of the group provides the new member with a Welcome message.  The Welcome message provides the information the new member needs to initialize its GroupContext. Different changes to the group will have different effects on the group state.  These effects are described in their respective subsections of proposals.  The following general rules apply: The "group_id" field is constant. The "epoch" field increments by one for each Commit message that is processed. The "tree_hash" is updated to represent the current tree and credentials. The "confirmed_transcript_hash" field is updated with the data for an MLSPlaintext message encoding a Commit message as described below. The "extensions" field changes when a GroupContextExtensions proposal is committed. The "confirmed_transcript_hash" is updated with an MLSPlaintext in two steps: Thus the "confirmed_transcript_hash" field in a GroupContext object represents a transcript over the whole history of MLSPlaintext Commit messages, up to the confirmation_tag field of the most recent Commit. The confirmation tag is then included in the transcript for the next epoch.  The interim transcript hash is computed by new members using the confirmation_tag of the GroupInfo struct, while existing members can compute it directly. As shown above, when a new group is created, the "interim_transcript_hash" field is set to the zero-length octet string. 10. </del> Group keys are derived using the "Extract" and "Expand" functions from the KDF for the group's ciphersuite, as well as the functions defined below: GroupInfo struct in order to allow non-members to join the group using an external commit. <del> 10.1. </del>  In addition to initializing a new epoch via KDF invocations as described above, an MLS group can also initialize a new epoch via an PublicGroupState for the previous epoch, encoded using the TLS serialization. <del> 10.2. </del>  Groups which already have an out-of-band mechanism to generate shared group secrets can inject those into the MLS key schedule to seed the Commit, then the resulting "psk_secret" is "psk_secret_[0]", the all- zero vector. <del> 10.3. </del>  For the generation of encryption keys and nonces, the key schedule begins with the "encryption_secret" at the root and derives a tree of nonces are derived, as described in encryption-keys.  The root of each ratchet is computed as: <del> 10.4. </del>  As described in message-framing, MLS encrypts three different types of information: Here, AEAD.Nn and AEAD.Nk denote the lengths in bytes of the nonce and key for the AEAD scheme defined by the ciphersuite. <del> 10.5. </del>  It is important to delete all security-sensitive values as soon as they are _consumed_. A sensitive value S is said to be _consumed_ if (not consume) the values K0 and N0 to allow for out-of-order delivery, and SHOULD retain AR2 for processing future messages. <del> 10.6. </del>  The main MLS key schedule provides an "exporter_secret" which can be used by an application as the basis to derive new secrets called It is RECOMMENDED for the application generating exported values to refresh those values after a Commit is processed. <del> 10.7. </del>  The main MLS key schedule provides a "resumption_secret" that is used as a PSK to inject entropy from one epoch into another.  This the number of past epochs for which the "resumption_secret" may be stored. <del> 10.8. </del>  The main MLS key schedule provides a per-epoch "authentication_secret".  If one of the parties is being actively authentication protocol to ensure that they share the same view of the group.  11. A group is always created with a single member, the "creator".  The

Depends on This swaps the "Key Packages" and "Key Schedule" sections, so that the former is just before "Group Creation".
Best reviewed with , which will show the diff against
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

9.  Group keys are derived using the "Extract" and "Expand" functions from the KDF for the group's ciphersuite, as well as the functions defined below: GroupInfo struct in order to allow non-members to join the group using an external commit.  <ins> 9.1. </ins> In addition to initializing a new epoch via KDF invocations as described above, an MLS group can also initialize a new epoch via an PublicGroupState for the previous epoch, encoded using the TLS serialization.  <ins> 9.2. </ins> Groups which already have an out-of-band mechanism to generate shared group secrets can inject those into the MLS key schedule to seed the Commit, then the resulting "psk_secret" is "psk_secret_[0]", the all- zero vector.  <ins> 9.3. </ins> For the generation of encryption keys and nonces, the key schedule begins with the "encryption_secret" at the root and derives a tree of nonces are derived, as described in encryption-keys.  The root of each ratchet is computed as:  <ins> 9.4. </ins> As described in message-framing, MLS encrypts three different types of information: Here, AEAD.Nn and AEAD.Nk denote the lengths in bytes of the nonce and key for the AEAD scheme defined by the ciphersuite.  <ins> 9.5. </ins> It is important to delete all security-sensitive values as soon as they are _consumed_. A sensitive value S is said to be _consumed_ if (not consume) the values K0 and N0 to allow for out-of-order delivery, and SHOULD retain AR2 for processing future messages.  <ins> 9.6. </ins> The main MLS key schedule provides an "exporter_secret" which can be used by an application as the basis to derive new secrets called It is RECOMMENDED for the application generating exported values to refresh those values after a Commit is processed.  <ins> 9.7. </ins> The main MLS key schedule provides a "resumption_secret" that is used as a PSK to inject entropy from one epoch into another.  This the number of past epochs for which the "resumption_secret" may be stored.  <ins> 9.8. </ins> The main MLS key schedule provides a per-epoch "authentication_secret".  If one of the parties is being actively authentication protocol to ensure that they share the same view of the group. <ins> 10. In order to facilitate the asynchronous addition of clients to a group, key packages are pre-published that provide some public information about a user.  A KeyPackage object specifies: A protocol version and ciphersuite that the client supports, a public key that others can use for key agreement, a credential authenticating the client's application-layer identity, and an "endpoint_id" that uniquely identifies a client in a group. 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. 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 among the set of KeyPackages created by this client.  The whole structure is signed using the client's signature key.  A KeyPackage object with an invalid signature field MUST be considered malformed. The signature is computed by the function "SignWithLabel" with a label "KeyPackage" and a content comprising of all of the fields except for the signature field. KeyPackage objects MUST contain at least two extensions, one of type "capabilities", and one of type "lifetime".  The "capabilities" extension allows MLS session establishment to be safe from downgrade attacks on the parameters described (as discussed in group-creation), while still only advertising one version / ciphersuite per KeyPackage. As the "KeyPackage" is a structure which is stored in the Ratchet Tree and updated depending on the evolution of the tree, each modification of its content MUST be reflected by a change in its signature.  This allows other members to verify the validity of the KeyPackage at any time, particularly in the case of a newcomer joining the group. 10.1. The validity of a KeyPackage needs to be verified at a few stages: When a KeyPackage is downloaded by a group member, before it is used to add the client to the group When a KeyPackage is received by a group member in an Add, Update, or Commit message When a client joining a group receives KeyPackages for the other members of the group in the group's ratchet tree The client verifies the validity of a KeyPackage using the following steps: Verify that the credential in the KeyPackage is valid according to the authentication service and the client's local policy.  These actions MUST be the same regardless of at what point in the protocol the KeyPackage is being verified. Verify that the signature on the KeyPackage is valid using the public key in the KeyPackage's credential Verify that the KeyPackage is compatible with the group's parameters.  The ciphersuite and protocol version of the KeyPackage must match those in use in the group.  If the GroupContext has a "required_capabilities" extension, then the required extensions and proposals MUST be listed in the KeyPackage's "capabilities" extension. Verify that the following fields in the KeyPackage are unique among the members of the group (including any other members added in the same Commit): "credential.signature_key" "hpke_init_key" "endpoint_id" 10.2. The "capabilities" extension indicates what protocol versions, ciphersuites, protocol extensions, and non-default proposal types are supported by a client.  Proposal types defined in this document are considered "default" and thus need not be listed. This extension MUST be always present in a KeyPackage.  Extensions that appear in the "extensions" field of a KeyPackage MUST be included in the "extensions" field of the "capabilities" extension. 10.3. The "lifetime" extension represents the times between which clients will consider a KeyPackage valid.  This time is represented as an absolute time, measured in seconds since the Unix epoch (1970-01-01T00:00:00Z).  A client MUST NOT use the data in a KeyPackage for any processing before the "not_before" date, or after the "not_after" date. Applications MUST define a maximum total lifetime that is acceptable for a KeyPackage, and reject any KeyPackage where the total lifetime is longer than this duration. This extension MUST always be present in a KeyPackage. 10.4. Within MLS, a KeyPackage is identified by its hash (see, e.g., joining-via-welcome-message).  The "external_key_id" extension allows applications to add an explicit, application-defined identifier to a KeyPackage. 10.5. Each member of the group maintains a GroupContext object that summarizes the state of the group: The fields in this state have the following semantics: The "group_id" field is an application-defined identifier for the group. The "epoch" field represents the current version of the group key. The "tree_hash" field contains a commitment to the contents of the group's ratchet tree and the credentials for the members of the group, as described in tree-hashes. The "confirmed_transcript_hash" field contains a running hash over the messages that led to this state. The "extensions" field contains the details of any protocol extensions that apply to the group. When a new member is added to the group, an existing member of the group provides the new member with a Welcome message.  The Welcome message provides the information the new member needs to initialize its GroupContext. Different changes to the group will have different effects on the group state.  These effects are described in their respective subsections of proposals.  The following general rules apply: The "group_id" field is constant. The "epoch" field increments by one for each Commit message that is processed. The "tree_hash" is updated to represent the current tree and credentials. The "confirmed_transcript_hash" field is updated with the data for an MLSPlaintext message encoding a Commit message as described below. The "extensions" field changes when a GroupContextExtensions proposal is committed. The "confirmed_transcript_hash" is updated with an MLSPlaintext in two steps: Thus the "confirmed_transcript_hash" field in a GroupContext object represents a transcript over the whole history of MLSPlaintext Commit messages, up to the confirmation_tag field of the most recent Commit. The confirmation tag is then included in the transcript for the next epoch.  The interim transcript hash is computed by new members using the confirmation_tag of the GroupInfo struct, while existing members can compute it directly. As shown above, when a new group is created, the "interim_transcript_hash" field is set to the zero-length octet string. </ins> 11. A group is always created with a single member, the "creator".  The


9.1.  In addition to initializing a new epoch via KDF invocations as described above, an MLS group can also initialize a new epoch via an asymmetric interaction using the external key pair for the previous PublicGroupState for the previous epoch, encoded using the TLS serialization. <del> 9.2. </del>  Groups which already have an out-of-band mechanism to generate shared group secrets can inject those into the MLS key schedule to seed the Commit, then the resulting "psk_secret" is "psk_secret_[0]", the all- zero vector. <del> 9.3. </del>  The main MLS key schedule provides an "exporter_secret" which can be used by an application as the basis to derive new secrets called It is RECOMMENDED for the application generating exported values to refresh those values after a Commit is processed. <del> 9.4. </del>  The main MLS key schedule provides a "resumption_secret" that is used as a PSK to inject entropy from one epoch into another.  This the number of past epochs for which the "resumption_secret" may be stored. <del> 9.5. </del>  The main MLS key schedule provides a per-epoch "authentication_secret".  If one of the parties is being actively applications to add an explicit, application-defined identifier to a KeyPackage. <del> 11.5. Each member of the group maintains a GroupContext object that summarizes the state of the group: The fields in this state have the following semantics: The "group_id" field is an application-defined identifier for the group. The "epoch" field represents the current version of the group key. The "tree_hash" field contains a commitment to the contents of the group's ratchet tree and the credentials for the members of the group, as described in tree-hashes. The "confirmed_transcript_hash" field contains a running hash over the messages that led to this state. The "extensions" field contains the details of any protocol extensions that apply to the group. When a new member is added to the group, an existing member of the group provides the new member with a Welcome message.  The Welcome message provides the information the new member needs to initialize its GroupContext. Different changes to the group will have different effects on the group state.  These effects are described in their respective subsections of proposals.  The following general rules apply: The "group_id" field is constant. The "epoch" field increments by one for each Commit message that is processed. The "tree_hash" is updated to represent the current tree and credentials. The "confirmed_transcript_hash" field is updated with the data for an MLSPlaintext message encoding a Commit message as described below. The "extensions" field changes when a GroupContextExtensions proposal is committed. The "confirmed_transcript_hash" is updated with an MLSPlaintext in two steps: Thus the "confirmed_transcript_hash" field in a GroupContext object represents a transcript over the whole history of MLSPlaintext Commit messages, up to the confirmation_tag field of the most recent Commit. The confirmation tag is then included in the transcript for the next epoch.  The interim transcript hash is computed by new members using the confirmation_tag of the GroupInfo struct, while existing members can compute it directly. As shown above, when a new group is created, the "interim_transcript_hash" field is set to the zero-length octet string. </del> 12. A group is always created with a single member, the "creator".  The

Depends on This PR moves the "Group State" subsection up into the "Key Schedule" section, where it fits more naturally.
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

9.1. <ins> Each member of the group maintains a GroupContext object that summarizes the state of the group: The fields in this state have the following semantics: The "group_id" field is an application-defined identifier for the group. The "epoch" field represents the current version of the group key. The "tree_hash" field contains a commitment to the contents of the group's ratchet tree and the credentials for the members of the group, as described in tree-hashes. The "confirmed_transcript_hash" field contains a running hash over the messages that led to this state. The "extensions" field contains the details of any protocol extensions that apply to the group. When a new member is added to the group, an existing member of the group provides the new member with a Welcome message.  The Welcome message provides the information the new member needs to initialize its GroupContext. Different changes to the group will have different effects on the group state.  These effects are described in their respective subsections of proposals.  The following general rules apply: The "group_id" field is constant. The "epoch" field increments by one for each Commit message that is processed. The "tree_hash" is updated to represent the current tree and credentials. The "confirmed_transcript_hash" field is updated with the data for an MLSPlaintext message encoding a Commit message as described below. The "extensions" field changes when a GroupContextExtensions proposal is committed. The "confirmed_transcript_hash" is updated with an MLSPlaintext in two steps: Thus the "confirmed_transcript_hash" field in a GroupContext object represents a transcript over the whole history of MLSPlaintext Commit messages, up to the confirmation_tag field of the most recent Commit. The confirmation tag is then included in the transcript for the next epoch.  The interim transcript hash is computed by new members using the confirmation_tag of the GroupInfo struct, while existing members can compute it directly. As shown above, when a new group is created, the "interim_transcript_hash" field is set to the zero-length octet string. 9.2. </ins> In addition to initializing a new epoch via KDF invocations as described above, an MLS group can also initialize a new epoch via an asymmetric interaction using the external key pair for the previous PublicGroupState for the previous epoch, encoded using the TLS serialization.  <ins> 9.3. </ins> Groups which already have an out-of-band mechanism to generate shared group secrets can inject those into the MLS key schedule to seed the Commit, then the resulting "psk_secret" is "psk_secret_[0]", the all- zero vector.  <ins> 9.4. </ins> The main MLS key schedule provides an "exporter_secret" which can be used by an application as the basis to derive new secrets called It is RECOMMENDED for the application generating exported values to refresh those values after a Commit is processed.  <ins> 9.5. </ins> The main MLS key schedule provides a "resumption_secret" that is used as a PSK to inject entropy from one epoch into another.  This the number of past epochs for which the "resumption_secret" may be stored.  <ins> 9.6. </ins> The main MLS key schedule provides a per-epoch "authentication_secret".  If one of the parties is being actively applications to add an explicit, application-defined identifier to a KeyPackage.  12. A group is always created with a single member, the "creator".  The


a credential authenticating the client's application-layer identity, and <del> an "endpoint_id" that uniquely identifies a client in a group. </del> 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 "hpke_init_key" <del> "endpoint_id" </del> 11.2. The "capabilities" extension indicates what protocol versions, 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 <del> KeyPackages with the same "endpoint_id", the committer again chooses one to include and considers the rest invalid.  Add proposals that contain KeyPackages with an "endpoint_id" tuple that matches that of an existing KeyPackage in the group MUST be considered invalid.  The committer MUST consider invalid any Add or Update proposal if the Credential in the contained KeyPackage shares the same signature key with a Credential in any leaf of the group, or indeed if the KeyPackage shares the same "hpke_init_key" with another KeyPackage in the group. </del>  The Commit MUST NOT combine proposals sent within different epochs. Due to the asynchronous nature of proposals, receivers of a Commit External Commits work like regular Commits, with a few differences:  The Commit MUST NOT include any proposals by reference, since an external joiner cannot determine the validity of proposals sent within the group There MUST NOT be any Update proposals <del> If a Remove proposal is present, then the "endpoint_id" of the Add KeyPackage MUST be the same as the one in the removed node, and the "credential" MUST present a set of identifiers that is acceptable to the application for the removed participant (as if this were an Update for that participant). The proposals included by reference in an External Commit MUST meet the following conditions: There MUST NOT be any ExternalInit proposals External Commits MUST contain a "path" field (and is therefore a "full" Commit).  The joiner is added at the leftmost free leaf node (just as if they were added with an Add proposal), and the path is calculated relative to that leaf node. </del>  External Commits MUST be signed by the new member.  In particular, the signature on the enclosing MLSPlaintext MUST verify using the

This involved a couple of changes other than just removing the field and references to it: Detecting duplicative Adds is now a little more ambiguous, since you can't just match on . However (a) this doesn't degrade the security of the protocol, just makes it a little less efficient (b) as discussed in , if applications want to sharpen it, they can add an -like identifier in an extension. The External Commit language around resync got generalized to say that the KeyPackage needs to meet the requirements of an Update for the removed node, which captures the successor notions we've discussed. I also noticed that when we forbade proposals by reference in , we didn't totally remove them. So I went ahead and fixed that here.
Comment by NAME What is the logic to make MLS128DHKEMX25519AES128GCMSHA256_Ed25519 the only MTI? It contains both FIPS and not FIPS items, pleasing no one.
I remember arguing that the FIPS suite should be MTI at one of the interims. The argument (iirc) was that people that need FIPS are going to implement FIPS anyways, so the MTI should be the one that's subjectively / widely-considered best. "But then wouldn't a FIPS-only client not conform to the RFC?" Yes, but the people that require FIPS will not care.
Well I know from IKEv2/IPsec, that we do take this into account and do ensure there is only FIPS MITM. I also know we are discussing this in openpgp and also for that reason are adding AES_GCM there. So if the only reasoning is "FIPS will force people to break RFCs anyway", that's kinda weak :P
You mean "MTI"? I don't think the MITM folks care about FIPS :) I think NAME summary captures the state of the art pretty well. Note as well that , so this will soon be moot anyway. (For some government-speed definition of "soon".) So yes, some FIPS folks might be sad in the short run, but things will even out. The working group had a robust discussion on this, and landed on this as a compromise that could get consensus. I don't think these FIPS concerns are worth upsetting that consensus.
Realistically, most folks using end-to-end encrypted instant messaging today are already using a variation of the DoubleRatchet protocol, using Ed25519. Many of our largest customers normally use FIPS, but have exceptions to use Ed25519 in this context and have no problem using Ed25519 for MLS.
(yes MTI :) That NIST doc is from 2017, so I"m not sure what you mean with "soon" :) I personally think it should be fixed but won't block it. Let's see what the IESG says though :)
We added to have a way to reference a member that is stable over the member's lifetime in the group. However, is never used in the protocol; the main need for such references is to give applications a way to tell an MLS stack what to do, e.g., which member to remove. isn't really required for this purpose, either: Leaf indices have the same stability as (since members don't change leaves), and no need for explicit checks on uniqueness. Applications can add their own unique identifiers in KeyPackage extensions Applications often want to refer to non-unique identifiers, e.g., asking to remove all devices belonging to an email address So it seems like at this point, can safely be removed.
NAME NAME NAME - tagging you all here since I seem to recall you had opinions on this topic.
With the changes in enforcing consistent verification methods and the fact that a prior key signs off on any new signature key, I think we have a fairly consistent chain for the identity and the endpoint_id can probably be removed. Leaving it in may strengthen security in some way, but I do not immediately see an issue.
What NAME said. I left a comment in regarding the details on the check, but otherwise, I think we're ok to remove . As you pointed out earlier NAME if an application decides it's necessary, they can always add it via an extension.
Thanks for taking care of the cleanup here after the discussion on . If the application has to provide a function that determines if a given KeyPackage represents a valid update, we could use the same function to determine if an Add is a duplicate. Although admittedly, in that case one would have to compare with all other KeyPackages in the group and the function might not be very efficient. Generally, I'm ok with this solution, though .

a credential authenticating the client's application-layer identity, and  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 "hpke_init_key"  11.2. The "capabilities" extension indicates what protocol versions, 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  <ins> KeyPackages that the committer considers to represent the same client or a client already in the group (for example, identical KeyPackages or KeyPackages sharing the same Credential), the committer again chooses one to include and considers the rest invalid.  The committer MUST consider invalid any Add or Update proposal if the Credential in the contained KeyPackage shares the same signature key with a Credential in any leaf of the group, or if the KeyPackage shares the same "hpke_init_key" with another KeyPackage in the group. </ins> The Commit MUST NOT combine proposals sent within different epochs. Due to the asynchronous nature of proposals, receivers of a Commit External Commits work like regular Commits, with a few differences: <ins> External Commits MUST contain a "path" field (and is therefore a "full" Commit).  The joiner is added at the leftmost free leaf node (just as if they were added with an Add proposal), and the path is calculated relative to that leaf node. </ins> The Commit MUST NOT include any proposals by reference, since an external joiner cannot determine the validity of proposals sent within the group There MUST NOT be any Update proposals  <ins> If a Remove proposal is present, then the leaf KeyPackage in the "path" field MUST meet the same criteria as the KeyPackage in an Update for the removed leaf (see update).  In particular, the "credential" in the KeyPackage MUST present a set of identifiers that is acceptable to the application for the removed participant. </ins> External Commits MUST be signed by the new member.  In particular, the signature on the enclosing MLSPlaintext MUST verify using the


process the Commit (i.e., not including any members being added or removed by 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, 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 that the committer considers to represent the same client or a client already in the group (for example, identical KeyPackages or KeyPackages sharing the same Credential), the committer again chooses one to include and considers the rest invalid.  The committer MUST consider invalid any Add or Update proposal if the Credential in the contained KeyPackage shares the same signature key with a Credential in any leaf of the group, or if the KeyPackage shares the same "hpke_init_key" with another KeyPackage in the group. </del>  The Commit MUST NOT combine proposals sent within different epochs. Due to the asynchronous nature of proposals, receivers of a Commit using the public key from the credential stored at the leaf in the tree indicated by the "sender" field. <del> Verify that all PSKs specified in any PreSharedKey proposals in the "proposals" vector are available. </del>  Generate the provisional ratchet tree and GroupContext by applying the proposals referenced in the initial Commit object, as

As discussed in , PreSharedKeyIDs shouldn't be repeated in the same commit
Should multiple PSK proposals within a commit be able to reference the same ? I feel like this should be covered in the same way we state multiple removals for the same should result in the committer choosing one. Wasn't sure if this was an oversight or on purpose. I can submit a PR to clarify if this is something that should be addressed
I agree, duplicates are an error. Committer MUST NOT include duplicates; receiver MUST reject Commit with duplicates. NAME - Please send a PR!

process the Commit (i.e., not including any members being added or removed by the Commit).  <ins> If there are multiple proposals that apply to the same leaf, or multiple PreSharedKey proposals that reference the same PreSharedKeyID, the committer MUST choose one and include only that one in the Commit, 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 that the committer considers to represent the same client or a client already in the group (for example, identical KeyPackages or KeyPackages sharing the same Credential), the committer again chooses one to include and considers the rest invalid.  The committer MUST consider invalid any Add or Update proposal if the Credential in the contained KeyPackage shares the same signature key with a Credential in any leaf of the group, or if the KeyPackage shares the same "hpke_init_key" with another KeyPackage in the group. </ins> The Commit MUST NOT combine proposals sent within different epochs. Due to the asynchronous nature of proposals, receivers of a Commit using the public key from the credential stored at the leaf in the tree indicated by the "sender" field.  <ins> Verify that all PreSharedKey proposals in the "proposals" vector have unique PreSharedKeyIDs and are available. </ins> Generate the provisional ratchet tree and GroupContext by applying the proposals referenced in the initial Commit object, as


Verify that the credential in the KeyPackage is valid according to the authentication service and the client's local policy.  These actions MUST be the same regardless of at what point in the <del> protocol the KeyPackage is being verified. </del>  Verify that the signature on the KeyPackage is valid using the public key in the KeyPackage's credential Verify that the "hpke_init_key" value is different from the corresponding field in the KeyPackage being replaced. <del> Verify that the set of identities attested by the credential is acceptable to the application for the participant being updated. </del> Replace the sender's leaf KeyPackage with the one contained in the Update proposal

This PR moves the remark that there has to be validation of the attested identities upon a credential update to the KeyPackage validation subsection. It also changes the responsibility for the check to be that of the AS rather than that of the application. Note, that the boundaries are somewhat arbitrary at this point, since the application running on the local device may well implement parts of the AS. The reason for this change is to ensure any validation checks that pertain to authentication are part of the AS.
NAME Please make any required changes to the architecture document as well. Since I think that's where the AS is defined.
Right now it says in the Update proposal section: "Verify that the set of identities attested by the credential is acceptable to the application for the participant being updated." Firstly, I think this is a matter of authentication and thus the AS' job and secondly, it's a validation step that should probably go into the section.
Discussion on virtual interim: Depends on what the boundary of the AS is: Checking credential is valid Also checking (credential -> credential) transitions Moving this check to KP validation section would make it clearer NAME to make a PR
Fixed by

Verify that the credential in the KeyPackage is valid according to the authentication service and the client's local policy.  These actions MUST be the same regardless of at what point in the  <ins> protocol the KeyPackage is being verified with the following exception: If the KeyPackage is an update to another KeyPackage, the authentication service MUST additionally validate that the set of identities attested by the credential in the new KeyPackage is acceptable relative to the identities attested by the old credential. </ins> Verify that the signature on the KeyPackage is valid using the public key in the KeyPackage's credential Verify that the "hpke_init_key" value is different from the corresponding field in the KeyPackage being replaced.  Replace the sender's leaf KeyPackage with the one contained in the Update proposal


8. The ratchet tree for an epoch describes the membership of a group in <del> that epoch, providing public-key encryption keys that can be used to encrypt to subsets of the group as well as information to </del>  authenticate the members.  In order to reflect changes to the membership of the group from one epoch to the next, corresponding changes are made to the ratchet tree.  In this section, we describe 8.2. <del> A member of an MLS group advances the key schedule to provide forward secrecy and post-compromise security by providing the group with fresh key material to be added into the group's shared secret.  To do so, one member of the group generates fresh key material, applies it to their local tree state, and then sends this key material to other members in the group via an UpdatePath message (see update-paths) . All other group members then apply the key material in the UpdatePath to their own local tree state to derive the group's now-updated shared secret. To begin, the generator of the UpdatePath updates its leaf node and its direct path to the root with new secret values.  The HPKE leaf public key within the KeyPackage MUST be derived from a freshly generated HPKE secret key to provide post-compromise security. The generator of the UpdatePath starts by sampling a fresh random value called "leaf_secret", and uses the leaf_secret to generate their leaf HPKE key pair (see key-packages) and to seed a sequence of "path secrets", one for each ancestor of its leaf.  In this setting, path_secret[0] refers to the leaf's parent, path_secret[1] to the parent's parent, and so on.  At each step, the path secret is used to derive a new secret value for the corresponding node, from which the node's key pair is derived. </del>  The node secret is derived as a temporary intermediate secret so that each secret is only used with one algorithm: The path secret is used structure, where "lp" and "np[i]" represent the leaf_priv and node_priv values generated as described above: <del> After performing these operations, the generator of the UpdatePath MUST delete the leaf_secret. </del> 8.3. In addition to the path-based updates to the tree described above, it

Changes (apart from editorial): Remove the text about the key schedule. Reason: I found the text confusing, because updating paths provides FS and PCS for the HPKE keys in the ratchet tree, and not directly for the key schedule. Generate the leaf key pair simply as a fresh HPKE key pair, and not from the leafsecret. I couldn't see any benefit of using the leafsecret. I wonder if there is a good reason?
I am concerned that we are asking for a LOT of fresh entropy if (in addition to nonces) we need a crypto-random value every epoch for every group (a Wire client can easily a member of thousands of groups at a time). Do you have a concern about deriving it?
Discussion on the virtual interim: To NAME concern: This allows the base path secret to be derived, e.g., using a KDF, so this doesn't necessarily increase the amount of entropy needed Clear to merge

8. The ratchet tree for an epoch describes the membership of a group in  <ins> that epoch, providing public-key encryption (HPKE) keys that can be used to encrypt to subsets of the group as well as information to </ins> authenticate the members.  In order to reflect changes to the membership of the group from one epoch to the next, corresponding changes are made to the ratchet tree.  In this section, we describe 8.2.  <ins> In order to provide forward secrecy and post-compromise security, whenever a member initiates an epoch change (i.e., commits; see commit), they refresh all HPKE key pairs in the ratchet tree for which they know the secret keys.  More precisely, they refresh the key pairs of their leaf and of all nodes on their leaf's direct path. The member initiating the epoch change generates the fresh key pairs using the following procedure.  The procedure is designed in a way that allows group members to efficiently communicate the fresh secret keys to other group members, as described in update-paths. Generate a fresh HPKE key pair for the leaf. Generate a sequence of path secrets, one for each node on the leaf's direct path as follows.  In this context, "path_secret[0]" is generated for the leaf's parent, "path_secret[1]" is generated for the leaf's grandparent, etc. Compute the sequence of HPKE key pairs "(node_priv,node_pub)", one for each node on the leaf's direct path, as follows. </ins> The node secret is derived as a temporary intermediate secret so that each secret is only used with one algorithm: The path secret is used structure, where "lp" and "np[i]" represent the leaf_priv and node_priv values generated as described above:  8.3. In addition to the path-based updates to the tree described above, it


applications can choose to make it available to new members in order to allow External Commits. <del> External Commits work like regular Commits, with a few differences: </del>  External Commits MUST contain a "path" field (and is therefore a "full" Commit).  The joiner is added at the leftmost free leaf The proposals included by value in an External Commit MUST meet the following conditions: <del> There MUST be a single ExternalInit proposal </del>  <del> There MUST NOT be any Update proposals </del>  <del> If a Remove proposal is present, then the LeafNode in the "path" field MUST meet the same criteria as the LeafNode in an Update for the removed leaf (see update).  In particular, the "credential" in the LeafNode MUST present a set of identifiers that is acceptable to the application for the removed participant. </del>  External Commits MUST be signed by the new member.  In particular, the signature on the enclosing MLSPlaintext MUST verify using the

The current description of external commits still leaves room for the committer to include various other proposals such as arbitrary proposals that are not Update or Remove proposals (e.g., Adds, RequiredCapability, PSK, etc.). I think we should narrow that down to a specific set of proposals (i.e. an allow list style) to keep the implementation and general protocol complexity low for external commits, both for creation and processing. The way I see it, the new joiner can always issue a follow-up commit for other proposals, but I might be missing important use-cases, so I'd be grateful for feedback on this.
I agree with the intent here. Looking through the list of proposal types, the only other one that seems like it might be useful is PSK -- I could see it being useful to prove that you have a PSK when joining from the outside. If we can add that as an option (probably in parallel to Remove), this seems good to go to me.
Would an External Commit adding yourself contain an Add or an Update Proposal?
It used to contain an proposal, but since that's pretty much redundant with the the commiter has to include, it got optimized away. So an external commit should be handled as if it contained an Add proposal, where the KeyPackage in the Add is instead in the .
Comment by NAME Why RECOMMENDED and not MUST ?

applications can choose to make it available to new members in order to allow External Commits.  <ins> In principle, External Commits work like regular Commits.  However, their content has to meet a specific set of requirements: </ins> External Commits MUST contain a "path" field (and is therefore a "full" Commit).  The joiner is added at the leftmost free leaf The proposals included by value in an External Commit MUST meet the following conditions:  <ins> There MUST be a single ExternalInit proposal. </ins>  <ins> There MAY be a single Remove proposal, where the LeafNode in the "path" field MUST meet the same criteria as the LeafNode in an Update for the removed leaf (see update).  In particular, the "credential" in the LeafNode MUST present a set of identifiers that is acceptable to the application for the removed participant. </ins>  <ins> There MAY be one or more PreSharedKey proposals. There MUST NOT be any other proposals. </ins> External Commits MUST be signed by the new member.  In particular, the signature on the enclosing MLSPlaintext MUST verify using the


label "KeyPackage" and a content comprising of all of the fields except for the signature field. <del> Note that the "capabilties" field in the "leaf_node" allows MLS session establishment to be safe from downgrade attacks on the parameters described (as discussed in group-creation), while still only advertising one version / ciphersuite per KeyPackage. </del>  The "leaf_node_source" field of the LeafNode in a KeyPackage MUST be set to "key_package".

Note that this is a little bit duplicative: There's already a field in KeyPackage. But removing that would require something like sending MLSMessage in Add, which seems unpleasant. We should probably either require that , or just not use MLSMessage for KeyPackage. Likewise, the version field in GroupInfo can probably be removed, since it will be on the outside MLSMessage regardless of whether the GroupInfo is sent bare or in Welcome.
First, we need to add ProtocolVersion to the beginning of MLSMessageContentTBS. Regarding KeyPackage and GroupInfo, the semantics here are subtle. Example: My MLS 1.1 client could send an MLSMessage with MLS version 1.1 to the DS/Directory, which contains a KeyPackage which only advertises MLS version 1.0. Another MLS client which only supports MLS 1.0 could read it with no problem. (I could publish both 1.0 and 1.1 KeyPackages).
The latest commits here address the initial comments about KeyPackage and GroupInfo, and fixes the problem NAME notes about MLSMessageContentTBS. For GroupInfo: I have just removed the field. I don't think there's any ambiguity created by moving it out to the MLSMessage wrapper. For KeyPackage: I have left the field and required it to be the same as the MLSMessage field. The thinking here is that these are saying the same thing to different audiences (the adder and everything else), so they should be consistent. You could allow them to differ if you said that is only to indicate the format/semantics of the inner KeyPackage; then would indicate the actual version in use. So to NAME point, if MLS 1.1 used the same KeyPackage format/semantics as MLS 1.0 (but presumably differed some other way), then you could have . But this seems harder to reason about than just saying "If I don't support the outer version, I can't use this KeyPackage".
I'm confused about what we're trying to do here. The way I understand it is that MLS messages are always sent in the context of a group. That group has a version associated with it via the group context. I see how it would make sense to add the version field to MLS messages, but I don't see how that version has anything to do with a potential KeyPackage that might or might not be a part of that message. I think the spec already has enough requirements about versions: If the version indicated by an MLSMessage differs from that of the group indicated by the group id, it's invalid. Similarly, if the version of the KeyPackage contained in a given Add proposal doesn't match that of the group in which it's meant to be used, it's invalid. Where exactly do we run into trouble if we don't explicitly relate the version in the framing and that of it's contents? The versions should already be the same, since they have to be the same as that of the group. Regarding the example with the DS: I don't quite understand the setting. As far as I'm aware, uploading or downloading of KeyPackages is not specified in the spec, so even if it happens within an MLS group (maybe the DS and the client are in a group) the KeyPackage would be part of an application message and thus the version of the KeyPackage would be irrelevant as far as the spec is concerned. Please correct me if I'm wrong or if I misunderstand the problem at hand.
Hi Konrad, Not all MLS objects are sent in the context of a group. KeyPackage objects are not. The version number applies to the protocol which specifies the objects, some of which are messages. When some lower level protocol sends an MLSCiphertext or MLSPlaintext message, the MLS client and/or DS could support multiple versions of MLS. The DS can't tell which group is being referenced until it can parse the message. It can't do that until it knows the protocol version, so the protocol and version are typically the first fields. I agree that for MLSCiphertext and MLSPlaintext objects if the version of the message doesn't correspond to the version of the group, that sounds like an invalid message to me. (I don't see when you would put a KeyPackage inside an MLS application message.) Even though uploading and downloading KeyPackage objects is not included in the spec, we need to allow another protocol to do so and give that protocol a reasonable migration path. If my directory supports KeyPackages in v1.0 and v1.1 format, we should be able to publish both versions until all the v1.0 potential peers have migrated. The format of the KeyPackage objects may be different. Being able to distinguish these so they can be parsed without external help is a basic data-hygiene function.

So to NAME point, if MLS 1.1 used the same KeyPackage format/semantics as MLS 1.0 (but presumably differed some other way), then you could have . But this seems harder to reason about than just saying "If I don't support the outer version, I can't use this KeyPackage". MLSMessage{version=1.1, KeyPackage{version=1.0,...}} is the case I am envisioning. My client supports both MLS version 1.1 and 1.0. My DS supports v1.1 so I always communicate with v1.1, but I publish keypackages in both 1.0 and 1.1 formats.
Thanks for the explanation, that makes the intent a bit clearer to me. However, I still don't see the scenario where something like is a valid message in the context of the MLS spec. I agree that you should be able to publish KeyPackages of multiple versions and that version agility is important. Do I understand correctly, that in the scenario you describe () you're uploading a KeyPackage to the DS? I guess I don't understand why that would be an MLSMessage and not some message in the proprietary protocol you use to upload KeyPackages.
NAME - You don't need to allow for uploading both 1.0 and 1.1 key packages -- you just have the client upload both and . Since there doesn't seem to be a compelling case here for allowing the versions to differ, I'm going to go ahead and merge with the requirement that they be the same.
To enable version agility, we need MLSMessages to indicate what version they are intended for. Since version is included in signature labels, we need to deconflict these two indicators.

label "KeyPackage" and a content comprising of all of the fields except for the signature field.  <ins> If a client receives a KeyPackage carried within an MLSMessage object, then it MUST verify that the "version" field of the KeyPackage has the same value as the "version" field of the MLSMessage.  The "version" field in the KeyPackage provides an explicit signal of the intended version to the other members of group when they receive the KeyPackage in an Add proposal. The "capabilities" field in the "leaf_node" allows MLS session establishment to be safe from downgrade attacks on the parameters described (as discussed in group-creation), while still only advertising one version / ciphersuite per KeyPackage. </ins> The "leaf_node_source" field of the LeafNode in a KeyPackage MUST be set to "key_package".


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 PublicGroupState 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 PublicGroupState for the previous epoch, encoded using the TLS serialization. </del>  9.4.

PublicGroupState was removed as an independent struct in . This PR fixes some dangling references to it.
cc NAME
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.
Also, update WireFormat: s/mlspublicgroupstate(4),/mlsgroup_info(4),/

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 GroupInfo 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 GroupInfo for the previous epoch, encoded using the TLS serialization. </ins> 9.4.


min and max values, the vector description simply includes a "V". For example: <del> Such a vector can represent values with length from 0 bytes to 2^62 </del>  bytes.  The variable-length integer encoding reserves the two most significant bits of the first byte to encode the base 2 logarithm of the integer encoding length in bytes.  The integer value is encoded When decoding, values using more bits than necessary MUST be treated as malformed. <del> This means that integers are encoded on 1, 2, 4, or 8 bytes and can encode 6-, 14-, 30-, or 62-bit values respectively. </del>  <del> For example, the eight-byte sequence c2 19 7c 5e ff 14 e8 8c (in hexadecimal) decodes to the decimal value 151288809941952652; the four byte sequence 9d 7f 3e 7d decodes to 494878333; the two byte sequence 7b bd decodes to 15293; and the single byte 25 decodes to 37 (as does the two byte sequence 40 25). </del>  The following figure adapts the pseudocode provided in RFC9000 to add a check for minimum-length encoding: The use of variable-size integers for vector lengths allows vectors <del> to grow very large, up to 2^62 bytes.  Implementations should take </del>  care not to allow vectors to overflow available storage.  To facilitate debugging of potential interoperatbility problems, implementations should provide a clear error when such an overflow

The recently introduced variable size encoding allows for very large message sizes that are not practical in any realistic scenario. Since decoders will most likely not allow such large messages anyway, this PR limits the maximum size to 30-bit.
cc NAME who had some thoughts about limits on the last virtual interim.
My general points are: Code defensively Fail gracefully Use the lower level protocol carrying MLS Messages to send a clear and relevant error message if something is too big. Ex: HTTP/2.0 413 Commit.Proposals exceeds 2^16 bytes ex: HTTP/2.0 413 Too many proposals referenced in Commit (>9999)

min and max values, the vector description simply includes a "V". For example:  <ins> Such a vector can represent values with length from 0 bytes to 2^30 </ins> bytes.  The variable-length integer encoding reserves the two most significant bits of the first byte to encode the base 2 logarithm of the integer encoding length in bytes.  The integer value is encoded When decoding, values using more bits than necessary MUST be treated as malformed.  <ins> This means that integers are encoded on 1, 2, or 4 bytes and can encode 6-, 14-, or 30-bit values respectively. </ins>  <ins> Vectors that start with "11" prefix are invalid and MUST be rejected. For example, the four byte sequence 0x9d7f3e7d decodes to 494878333; the two byte sequence 0x7bbd decodes to 15293; and the single byte 0x25 decodes to 37. </ins> The following figure adapts the pseudocode provided in RFC9000 to add a check for minimum-length encoding: The use of variable-size integers for vector lengths allows vectors  <ins> to grow very large, up to 2^30 bytes.  Implementations should take </ins> care not to allow vectors to overflow available storage.  To facilitate debugging of potential interoperatbility problems, implementations should provide a clear error when such an overflow


8.6.2. <del> 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_node".  Moreover, when joining a group, new members MUST authenticate each non-blank parent node P.  A parent node P is authenticated by checking that there exists a child V of P and a node U in the resolution of V such that V."parent_hash" is equal to the Parent Hash of P with V's sibling as the co-path child. </del>  8.7.

This PR revises the parent hash verification section to reflect more recent discussion. Right now, the definition of validity according to the parent hash ties it to the "top-down" validation algorithm, when what really matters is that each parent node has a path to a leaf. This PR changes the definition to that more general version, and mentions the "top-down" and "bottom-up" approaches to checking this property.
cc NAME NAME
Great proposal! It achieves what we want much more directly. One comment: in the description of the "top down" approach, the part "there is exactly one descendant" isn't exactly consistent with being "parent hash valid", because the latter requires that P can be chained to at least one leaf. The bottom up approach is consistent with being parent hash valid. As a result, a malicious insider may be able to craft a tree that is accepted by the bottom up approach but not by the top down approach [1] which may hurt interoperability. I'd suggest either to change "exactly one" to "at least one" in the top down approach or to change "can be chained back to a leaf" to "can be chained back to exactly one leaf" in the definition of parent hash valid (and modify the bottom up approach accordingly). I don't have a strong opinion which is better. [1] An example such tree: the tree has 3 nodes: the root P and its children A and B. Assuming both A and B are malicious, A signs the parent hash of P with copath child B and B signs the parent hash of P with copath child A. (This is possible with the current resolution-hash for parent-hash.)
NAME - I am keen on the "exactly one" framing precisely because you shouldn't have these collisions in a real tree. At least I haven't been able to think up a case; maybe you have a non-malicious case? Given that, I'm inclined to tighten the "bottom-up" so that it fails if there are multiple valid paths to a node.
Just to close the loop here: NAME - thanks for the suggestion, added a sentence NAME - Updated the "bottom up" description to say that each node should be covered by exactly one chain, which is the parallel requirement to exactly one child.
Note that this doesn't completely address the "unease" feeling mentioned in , but I can live it. Other than that, I think it makes the parent hash construct a lot more clear and this PR has a lot of pedagogical value. The section on parent hash in the current document is really difficult to grasp for a newcomer.

8.6.2.  <ins> Parent hashes are verified at two points in the protocol: When joining a group and when processing a Commit. The parent hash in a node U is valid with respect to a parent node P if the following criteria hold: U is a descendant of P in the tree The nodes between U and P in the tree are all blank The "parent_hash" field of U is equal to the parent hash of P with copath child S, where S is the child of P that is not on the path from U to P. A parent node P is "parent-hash valid" if it can be chained back to a leaf node in this way.  That is, if there is leaf node L and a sequence of parent nodes P_1, ..., P_N such that P_N = P and each step in the chain is authenticated by a parent hash: L's parent hash is valid with respect to P_1, P_1's parent hash is valid with respect to P_2, and so on. When joining a group, the new member MUST authenticate that each non- blank parent node P is parent-hash valid.  This can be done "bottom up" by building chains up from leaves and verifying that all non- blank parent nodes are covered by exactly one such chain, or "top down" by verifying that there is exactly one descendant of each non- blank parent node for which the parent node is parent-hash valid. When processing a Commit message that includes an UpdatePath, 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_node".  After being merged into the tree, the nodes in the UpdatePath form a parent-hash chain from the committer's leaf to the root. </ins> 8.7.


The parent hash of P changes whenever an "UpdatePath" object is applied to the ratchet tree along a path from a leaf U traversing <del> 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. </del>  The field "public_key" contains the HPKE public key of P.  If P is the root, then the "parent_hash" field is set to a zero-length octet If, on the other hand, V is the leaf U and its LeafNode has "leaf_node_source" set to "commit", then the Parent Hash of P (with <del> V's sibling as co-path child) is stored in the "parent_hash" field. </del>  This is true in particular of the LeafNode object sent in the "leaf_node" field of an UpdatePath.  The signature of such a LeafNode thus also attests to which keys the group member introduced into the

The co-path is written both as 'co-path' and 'copath'. As a formality check, this should be unified.

The parent hash of P changes whenever an "UpdatePath" object is applied to the ratchet tree along a path from a leaf U traversing  <ins> node V (and hence also P).  The new "Parent hash of P (with copath child S)" is obtained by hashing P's "ParentHashInput" struct. </ins> The field "public_key" contains the HPKE public key of P.  If P is the root, then the "parent_hash" field is set to a zero-length octet If, on the other hand, V is the leaf U and its LeafNode has "leaf_node_source" set to "commit", then the Parent Hash of P (with  <ins> V's sibling as copath child) is stored in the "parent_hash" field. </ins> This is true in particular of the LeafNode object sent in the "leaf_node" field of an UpdatePath.  The signature of such a LeafNode thus also attests to which keys the group member introduced into the


client's appearance in the group, signed by that client: The "public_key" field conains an HPKE public key whose private key <del> is held only by the member occupying this leaf.  The "credential" contains authentication information for this member, as described in credentials. </del>  The "capabilities" field indicates what protocol versions, ciphersuites, protocol extensions, and non-default proposal types are the "extensions" field of the "capabilities" field. The "leaf_node_source" field indicates how this LeafNode came to be <del> added to the tree. </del>  In the case where the leaf was added to the tree based on a pre- published KeyPackage, the "lifetime" field represents the times The client verifies the validity of a LeafNode using the following steps: <del> Verify that the credential in the KeyPackage is valid according to </del>  the authentication service and the client's local policy.  These actions MUST be the same regardless of at what point in the <del> protocol the KeyPackage is being verified with the following exception: If the KeyPackage is an update to another KeyPackage, the authentication service MUST additionally validate that the set of identities attested by the credential in the new KeyPackage is </del>  acceptable relative to the identities attested by the old credential. Verify the "lifetime" field: <del> When validating a downloaded KeyPackage, the current time MUST be within the "lifetime" range.  A KeyPackage that is expired or not yet valid MUST NOT be sent in an Add proposal. </del>  When receiving an Add or validating a tree, checking the "lifetime" is RECOMMENDED, if it is feasible in a given for the context in which the LeafNode is being validated (as defined in ratchet-tree-node-contents). <del> Verify that the following fields in the KeyPackage are unique among the members of the group (including any other members added in the same Commit): </del>  "public_key" "credential.signature_key"  8.2. In order to provide forward secrecy and post-compromise security, A protocol version and ciphersuite that the client supports, a public key that others can use to encrypt a Welcome message to <del> this client, and </del>  the content of the leaf node that should be added to the tree to represent this client. multiple ciphersuites. The value for "init_key" MUST be a public key for the asymmetric <del> encryption scheme defined by cipher_suite, and it MUST be unique </del>  among the set of KeyPackages created by this client.  Likewise, the "leaf_node" field MUST be valid for the ciphersuite, including both <del> the "hpke_key" and "credential" fields.  The whole structure is </del>  signed using the client's signature key.  A KeyPackage object with an invalid signature field MUST be considered malformed. explicit signal of the intended version to the other members of group when they receive the KeyPackage in an Add proposal. <del> The "capabilities" field in the "leaf_node" allows MLS session establishment to be safe from downgrade attacks on the parameters described (as discussed in group-creation), while still only advertising one version / ciphersuite per KeyPackage. </del>  <del> The "leaf_node_source" field of the LeafNode in a KeyPackage MUST be set to "key_package". </del>  11.1. The client verifies the validity of a KeyPackage using the following steps: <del> Verify the LeafNode in the KeyPackage according to the process defined in leaf-node-validation. Verify that the signature on the KeyPackage is valid using the public key in the LeafNode's credential Verify that the ciphersuite and protocol version of the LeafNode </del>  match those in use in the group. <del> 11.2. </del>  <del> The "capabilities" extension indicates what protocol versions, ciphersuites, protocol extensions, and non-default proposal types are supported by a client.  Proposal types defined in this document are considered "default" and thus need not be listed. This extension MUST be always present in a KeyPackage.  Extensions that appear in the "extensions" field of a KeyPackage MUST be included in the "extensions" field of the "capabilities" extension. 11.3. The "lifetime" extension represents the times between which clients will consider a KeyPackage valid.  This time is represented as an absolute time, measured in seconds since the Unix epoch (1970-01-01T00:00:00Z).  A client MUST NOT use the data in a KeyPackage for any processing before the "not_before" date, or after the "not_after" date. Applications MUST define a maximum total lifetime that is acceptable for a KeyPackage, and reject any KeyPackage where the total lifetime is longer than this duration. This extension MUST always be present in a KeyPackage. </del>  <del> 11.4. </del>  Within MLS, a KeyPackage is identified by its hash (see, e.g., joining-via-welcome-message).  The "external_key_id" extension allows

This PR picks up changes from clarifying and cleaning up a few issues around LeafNode.
A big improvement in readability and correctness.

client's appearance in the group, signed by that client: The "public_key" field conains an HPKE public key whose private key  <ins> is held only by the member occupying this leaf (or in the case of a LeafNode in a KeyPackage object, the issuer of the KeyPackage).  The "credential" contains authentication information for this member, as described in credentials. </ins> The "capabilities" field indicates what protocol versions, ciphersuites, protocol extensions, and non-default proposal types are the "extensions" field of the "capabilities" field. The "leaf_node_source" field indicates how this LeafNode came to be  <ins> added to the tree.  This signal tells other members of the group whether the leaf node is required to have a "lifetime" or "parent_hash", and whether the "group_id" is added as context to the signature.  Whether these fields can be computed by the client represented by the LeafNode depends on when the LeafNode was created. For example, a KeyPackage is created before the client knows which group it will be used with, so its signature can't bind to a "group_id". </ins> In the case where the leaf was added to the tree based on a pre- published KeyPackage, the "lifetime" field represents the times The client verifies the validity of a LeafNode using the following steps:  <ins> Verify that the credential in the LeafNode is valid according to </ins> the authentication service and the client's local policy.  These actions MUST be the same regardless of at what point in the  <ins> protocol the LeafNode is being verified with the following exception: If the LeafNode is an update to another LeafNode, the authentication service MUST additionally validate that the set of identities attested by the credential in the new LeafNode is </ins> acceptable relative to the identities attested by the old credential. Verify the "lifetime" field:  <ins> When validating a LeafNode in a KeyPackage before sending an Add proposal, the current time MUST be within the "lifetime" range.  A KeyPackage containing a LeafNode that is expired or not yet valid MUST NOT be sent in an Add proposal. </ins> When receiving an Add or validating a tree, checking the "lifetime" is RECOMMENDED, if it is feasible in a given for the context in which the LeafNode is being validated (as defined in ratchet-tree-node-contents).  <ins> Verify that the following fields in the LeafNode are unique among the members of the group (including any other members added in the same Commit): </ins> "public_key" "credential.signature_key" <ins> Verify that the extensions in the leaf node are supported.  The ID for each extension in the "extensions" field MUST be listed in the field "capabilities.extensions" of the LeafNode. </ins> 8.2. In order to provide forward secrecy and post-compromise security, A protocol version and ciphersuite that the client supports, a public key that others can use to encrypt a Welcome message to  <ins> this client, (an "init key") and </ins> the content of the leaf node that should be added to the tree to represent this client. multiple ciphersuites. The value for "init_key" MUST be a public key for the asymmetric  <ins> encryption scheme defined by "cipher_suite", and it MUST be unique </ins> among the set of KeyPackages created by this client.  Likewise, the "leaf_node" field MUST be valid for the ciphersuite, including both  <ins> the "public_key" and "credential" fields.  The whole structure is </ins> signed using the client's signature key.  A KeyPackage object with an invalid signature field MUST be considered malformed. explicit signal of the intended version to the other members of group when they receive the KeyPackage in an Add proposal.  <ins> The field "leaf_node.capabilities" indicates what protocol versions, ciphersuites, protocol extensions, and non-default proposal types are supported by the client.  (Proposal types defined in this document are considered "default" and not listed.)  This information allows MLS session establishment to be safe from downgrade attacks on the parameters described (as discussed in group-creation), while still only advertising one version / ciphersuite per KeyPackage. The field "leaf_node.leaf_node_source" of the LeafNode in a KeyPackage MUST be set to "key_package". </ins>  <ins> Extension included in the "extensions" or "leaf_node.extensions" fields MUST be included in the "leaf_node.capabilities" field. </ins> 11.1. The client verifies the validity of a KeyPackage using the following steps:  <ins> Verify that the ciphersuite and protocol version of the KeyPackage </ins> match those in use in the group.  <ins> Verify the "leaf_node" field of the KeyPackage according to the process defined in leaf-node-validation. </ins>  <ins> Verify that the signature on the KeyPackage is valid using the public key in "leaf_node.credential". </ins>  <ins> 11.2. </ins> Within MLS, a KeyPackage is identified by its hash (see, e.g., joining-via-welcome-message).  The "external_key_id" extension allows


concatenating the resolution of its left child with the resolution of its right child, in that order <del> For example, consider the following tree, where the "_" character </del>  represents a blank node and unmerged leaves are indicated in square brackets: The resolution of leaf 1 is the empty list [] <del> The resolution of root node is the list [A, Z, C] </del>  Every node, regardless of whether the node is blank or populated, has a corresponding _hash_ that summarizes the contents of the subtree to DeriveKeyPair. For example, suppose there is a group with four members, with C an <del> unmerged leaf at node 5: </del>  If member B subsequently generates an UpdatePath based on a secret "leaf_secret", then it would generate the following sequence of path Compute the updated path secrets. <del> Identify a node in the filtered direct path for which the local member is in the subtree of the non-updated child. </del>  Identify a node in the resolution of the copath node for which <del> this node has a private key. </del>  Decrypt the path secret for the parent of the copath node using the private key from the resolution node.

This PR adds two clarifications suggested by NAME Clarifying some antecedents in UpdatePath processing Explaining how the example trees can come about in practice

concatenating the resolution of its left child with the resolution of its right child, in that order  <ins> For example, consider the following subtree, where the "_" character </ins> represents a blank node and unmerged leaves are indicated in square brackets: The resolution of leaf 1 is the empty list []  <ins> The resolution of top node is the list [A, Z, C] </ins> Every node, regardless of whether the node is blank or populated, has a corresponding _hash_ that summarizes the contents of the subtree to DeriveKeyPair. For example, suppose there is a group with four members, with C an  <ins> unmerged leaf at Z: </ins> If member B subsequently generates an UpdatePath based on a secret "leaf_secret", then it would generate the following sequence of path Compute the updated path secrets.  <ins> Identify a node in the filtered direct path for which the recipient is in the subtree of the non-updated child. </ins> Identify a node in the resolution of the copath node for which  <ins> the recipient has a private key. </ins> Decrypt the path secret for the parent of the copath node using the private key from the resolution node.