8.2. For the generation of encryption keys and nonces, the key schedule <del> begins with the "encryption_secret" and derives a tree of secrets with the same structure as the group's ratchet tree.  Each leaf in the Secret Tree is associated with the same group member as the corresponding leaf in the ratchet tree.  Nodes are also assigned an index according to their position in the array representation of the tree (described in tree-math).  If N is a node index in the Secret Tree then left(N) and right(N) denote the children of N (if they exist). </del>  The secret of any other node in the tree is derived from its parent's secret using a call to DeriveTreeSecret: Keys, nonces, and the secrets in ratchets are derived using DeriveTreeSecret.  The context in a given call consists of the index of the sender's leaf in the ratchet tree and the current position in <del> the ratchet.  In particular, the index of the sender's leaf in the ratchet tree is the same as the index of the leaf in the Secret Tree used to initialize the sender's ratchet. </del>  Here, AEAD.Nn and AEAD.Nk denote the lengths in bytes of the nonce and key for the AEAD scheme defined by the ciphersuite. 8.4. <del> It is important to delete all security sensitive values as soon as </del>  they are _consumed_. A sensitive value S is said to be _consumed_ if S was used to encrypt or (successfully) decrypt a message, or if For example, suppose a group member encrypts or (successfully) decrypts an application message using the j-th key and nonce in the <del> i-th ratchet.  Then, for that member, at least the following values have been consumed and MUST be deleted: </del>  <del> the "init_secret", "commit_secret", "epoch_secret", "encryption_secret" of that epoch, </del>  all node secrets in the Secret Tree on the path from the root to <del> the leaf with index i, </del>  <del> the first j secrets in the i-th application data ratchet and </del>  "application_ratchet_nonce_[N]_[j]" and <del> "application_ratchet_nonce_[N]_[j]". </del>  Concretely, suppose we have the following Secret Tree and ratchet for participant D: Then if a client uses key K1 and nonce N1 during epoch n then it must <del> consume (at least) values G, F, D, AR0, K1, N1 as well as the "commit_secret" and "init_secret" used to derive G (the "encryption_secret").  The client MAY retain (not consume) the values K0 and N0 to allow for out-of-order delivery, and SHOULD retain AR2 for processing future messages. </del>  8.5.

Some clarifications and typos from my attempt to understand secret trees and the deletion schedule. Please verify that I'm correct in that and also need to be consumed.
I'll hold off on merging until we resolve the discussion above, though.
We currently distinguish between leaf indices and node indices, where node indices are a straight-forward index in the array representing a ratcheting tree and leaf indices index what (I think) used be called the roster, an array just containing the members. However, the roster was removed and instead, we always use the ratcheting tree array when referring to both nodes and leaves. As a result, whenever we use a leaf index, we have to translate it to a node index when actually indexing the array. I don't see a reason for this added complexity and propose only use node indices. This was already discussed a bit in , where NAME pointed out that leaf indices make sure that a Remove only ever references a leaf. I think having a little check here that the index actually refers to a leaf is preferable over having to translate back and forth in many other places. Note, that we have to check anyway if the index in Remove is within the bounds of the tree, regardless of it being a leaf or a node index. I'm happy to provide a PR if this is something we want to do.
When I mentioned Remove in the context of , that was only an initial example. Here's a more fulsome list of where leaf indices are used: All of those are cases where a node index would be nonsensical. So while even with a leaf index, you still have to bounds-check, you're basically asking for us to allow more nonsensical cases to be expressible in the syntax. As far as comprehensibility / implementability: The difference between node indices and leaf indices is clearly laid out in the , and AFAICT, all of the references to indices are clear about which one they refer to. This distinction is a good one for implementors to have in mind, and not just to simplify parsing / input validation on messages as above. There are lots of internal methods that only make sense to refer to leaves as well. For example, a TreeKEM implementation internal to an MLS implementation will need to know the index of the caller of so that it can make the right UpdatePath. Here again, providing a node index is nonsensical. So implementations will likely end up needing to differentiate leaves from nodes anyway. Net of all that, I propose closing this with no action.
Fair enough. Closing this, then.
I suggest to re-open this in order to remove the concept of lead indices from the spec. We'll do so in OpenMLS in URL because it's a footgun causing a ton of issues. If you really want to keep the concept in the spec, it must be made clear when a leaf index and when a node index is used. Taking the text for remove proposals for example. This sounds like the index is a node index. This sounds like it could be a leaf index. But it's not defined either way. So your claim NAME that "all of the references to indices are clear about which one they refer to." is not true. I totally agree with this. But the current concept introduces more issues than it actually solves. So this definitely needs work, either by removing the concept (which I'd prefer) or actually making clear which index is used. In my opinion the tree should only be indexed by one type of indices to reduce ambiguity and the potential for bugs. This way we can reduce the places of conversion while still making it clear to implementors that there is a distinction between the two.
I'm happy to clean up references, but I would like to avoid explicitly allowing invalid requests. You shouldn't be able to ask to remove node 5, or say you signed with node 7. You risk bugs either way; in the "all indices are node indices" case you just have to catch them at runtime, as opposed to using the right type at build time. So I continue to believe this is better handled with type systems. Nonetheless, I'll do a review and try to clean up any ambiguous cases.

8.2. For the generation of encryption keys and nonces, the key schedule  <ins> begins with the "encryption_secret" at the root and derives a tree of secrets with the same structure as the group's ratchet tree.  Each leaf in the Secret Tree is associated with the same group member as the corresponding leaf in the ratchet tree.  Nodes are also assigned an index according to their position in the array representation of the tree (described in tree-math).  If N is a node index in the Secret Tree then left(N) and right(N) denote the children of N (if they exist). </ins> The secret of any other node in the tree is derived from its parent's secret using a call to DeriveTreeSecret: Keys, nonces, and the secrets in ratchets are derived using DeriveTreeSecret.  The context in a given call consists of the index of the sender's leaf in the ratchet tree and the current position in  <ins> the ratchet.  In particular, the node index of the sender's leaf in the ratchet tree is the same as the node index of the leaf in the Secret Tree used to initialize the sender's ratchet. </ins> Here, AEAD.Nn and AEAD.Nk denote the lengths in bytes of the nonce and key for the AEAD scheme defined by the ciphersuite. 8.4.  <ins> It is important to delete all security-sensitive values as soon as </ins> they are _consumed_. A sensitive value S is said to be _consumed_ if S was used to encrypt or (successfully) decrypt a message, or if For example, suppose a group member encrypts or (successfully) decrypts an application message using the j-th key and nonce in the  <ins> ratchet of node index N in some epoch n.  Then, for that member, at least the following values have been consumed and MUST be deleted: </ins>  <ins> the "commit_secret", "joiner_secret", "member_secret", "epoch_secret", "encryption_secret" of that epoch n as well as the "init_secret" of the previous epoch n-1, </ins> all node secrets in the Secret Tree on the path from the root to  <ins> the leaf with node index N, </ins>  <ins> the first j secrets in the application data ratchet of node index N and </ins> "application_ratchet_nonce_[N]_[j]" and  <ins> "application_ratchet_key_[N]_[j]". </ins> Concretely, suppose we have the following Secret Tree and ratchet for participant D: Then if a client uses key K1 and nonce N1 during epoch n then it must  <ins> consume (at least) values G, F, D, AR0, AR1, K1, N1 as well as the key schedule secrets used to derive G (the "encryption_secret"), namely "init_secret" of epoch n-1 and "commit_secret", "joiner_secret", "member_secret", "epoch_secret" of epoch n.  The client MAY retain (not consume) the values K0 and N0 to allow for out-of-order delivery, and SHOULD retain AR2 for processing future messages. </ins> 8.5.


Welcome message at the same time, by taking the following steps: Construct an initial Commit object with the "proposals" field <del> populated from Proposals received during the current epoch, and empty "key_package" and "path" fields. </del>  Generate a provisional GroupContext object by applying the proposals referenced in the initial Commit object, as described in above), then it MUST be populated.  Otherwise, the sender MAY omit the "path" field at its discretion. <del> If populating the "path" field: Create a UpdatePath using the new </del>  tree.  Any new member (from an add proposal) MUST be exluded from the resolution during the computation of the UpdatePath.  The GroupContext for this operation uses the "group_id", "epoch", "tree_hash", and "confirmed_transcript_hash" values in the initial <del> GroupContext object. </del>  Assign this UpdatePath to the "path" field in the Commit. Commit to the null optional.  Define "commit_secret" as the all- zero vector of the same length as a "path_secret" value would be. <del> Generate a new KeyPackage for the Committer's own leaf, with a "parent_hash" extension.  Store it in the ratchet tree and assign it to the "key_package" field in the Commit object. </del> If one or more PreSharedKey proposals are part of the commit, derive the "psk_secret" as specified in pre-shared-keys, where the order of PSKs in the derivation corresponds to the order of ratchet tree and generate the "commit_secret": Apply the UpdatePath to the tree, as described in <del> synchronizing-views-of-the-tree, and store "key_package" at the Committer's leaf. </del>  Verify that the KeyPackage has a "parent_hash" extension and that its value matches the new parent of the sender's leaf

It looks like in Commit was moved to in UpdatePath, but some docs didn't get updated.

Welcome message at the same time, by taking the following steps: Construct an initial Commit object with the "proposals" field  <ins> populated from Proposals received during the current epoch, and an empty "path" field. </ins> Generate a provisional GroupContext object by applying the proposals referenced in the initial Commit object, as described in above), then it MUST be populated.  Otherwise, the sender MAY omit the "path" field at its discretion.  <ins> If populating the "path" field: Create an UpdatePath using the new </ins> tree.  Any new member (from an add proposal) MUST be exluded from the resolution during the computation of the UpdatePath.  The GroupContext for this operation uses the "group_id", "epoch", "tree_hash", and "confirmed_transcript_hash" values in the initial  <ins> GroupContext object.  The "leaf_key_package" for this UpdatePath must have a "parent_hash" extension. </ins> Assign this UpdatePath to the "path" field in the Commit. Commit to the null optional.  Define "commit_secret" as the all- zero vector of the same length as a "path_secret" value would be.  If one or more PreSharedKey proposals are part of the commit, derive the "psk_secret" as specified in pre-shared-keys, where the order of PSKs in the derivation corresponds to the order of ratchet tree and generate the "commit_secret": Apply the UpdatePath to the tree, as described in  <ins> synchronizing-views-of-the-tree, and store "leaf_key_package" at the Committer's leaf. </ins> Verify that the KeyPackage has a "parent_hash" extension and that its value matches the new parent of the sender's leaf


11.1.7.  Add and Remove proposals can be constructed and sent to the group by a party that is outside the group.  For example, a Delivery Service might propose to remove a member of a group who has been inactive for

We had longstanding issue to allow the participants in an MLS group to detect when the DS drops messages. That issue was closed because it had been open for a long time without a concrete proposed solution. More recently, however, NAME and NAME suggested using a Proposal for this, and it made a lot of sense to me when I looked into the details. So this PR should be considere joint work between, NAME NAME and me. This PR adds an Proposal that any member of the group can send to advertise the last generation it sent, and acknowledge the receipt of application messages from other members. As a Proposal, it is included in the transcript via Commits, providing a pretty strong defence against message suppression -- the DS basically has to stop the group progressing at all if it wants to drop applications without the group being aware of it.
BTW, there are a bunch of ways to go about ). Most of these schemes are designed for 1:1 connections like TCP or QUIC, but adapt in a straightforward way to groups. Which one is most efficient depends on what you assume about losses. Some examples: List of received messages (the most naïve approach; efficient only if losses dominate) List of received message ranges (implemented here, more efficient for infrequent losses) Highest received generation, plus list of generations not received (more efficient for even more infrequent losses) FWIW, scheme (2) is effectively . TCP does something like (3), but because it is synchronous, doesn't include generations not received. The option for TCP is basically the same as option (2), reporting ranges of received packets.
Great idea! Two comments: To make sure that the DS doesn't drop any Commit that has an AppAck extension (to cover up their dropping of Application messages), either one has to encrypt Commit messages or AppAcks need to be mandatory for Commits by policy. Otherwise the DS can just drop any Commit that has an AppAck in it (without impeding the sending of messages in the group). A cheaper, although functionally less powerful version would be to simply send a hash of the SecretTree. The other group members would then know if they received the same messages as the sender (but not more than that).
NAME good point about addressing suppression of AppAck itself. I added a paragraph. Sending a hash of the SecretTree seems both less powerful and way more complicated, since we would have to define a hashing scheme. So I'm gonna pass :)
There's a few implications of simply allowing forks: FS issues - clients would need to use initsecret twice, which means keeping around initsecret for a potential fork, which breaks FS. One resolution here is to use something from the ASTree for initsecret (maybe the nth app secret, or a new ratchet derived from astreenode[n]secret). Does detection yield any sort of meaningful resolution technique? Are we better off than we are now in terms of user experience? (What we have now, if the DS fails to order, is two (or N) groups of clients operating on different versions of the tree, which effectively partitions the group) I wrote about this here: URL suggesting merges as a resolution technique. The main downside is clients would need to keep around the list of proposals for a while. This isn't bad imo, it reduces complexity to some extent. I'm somewhat in favour of a non-pseudorandom tiebreaker if there's no other arguments against it. How many bits of sequence number? Have we considered using varints? Similar to the CTLS proposal - URL Other forking considerations, I think there's more involved than just changing the epoch identifier: We'll need merges as well, since forks can lead to inconsistent group membership: Suppose in epoch 1 a group has A, B, and C A removes C in epoch 2A B adds D in epoch 2B We now have two group states 2A with AB and 2B with ABCD - and it's unclear which group to use to send with. We could mandate that any client MUST send a "merge" commit on it's view of history before sending any message. This commit could just be the list of epochs that are being merged (+a usual DirectPath update?), and processing it would mean replaying all proposals up to the common ancestor for those epochs on the common ancestor. Forward secrecy issues. By allowing forks, devices must now keep the previous epoch around to derive possible future forks - but this means we'll be re-using initsecret. A possible solution could be to use the nth app secret from the ASTree instead of initsecret as the start for the new epoch secret. This ties nicely with using a non-pseudorandom tiebreaker. Ordering implications It's unclear what a sensible ordering strategy is for the DS if we allow forking. (simply ensuring that each commit is based on the "current" epoch isn't well defined since there can be multiple "current" epochs). By allowing forks the DS need not enforce ordering at all imo. This means that apps need to keep around old epochs, but this is already required per (2). A different question is how many we need to keep around - perhaps a TTL or the past N epochs after a topological sort would make sense. Generally I think getting rid of server ordering is good idea: Helps Matrix and general federation use cases A common consistent mutated state for group operations can cause server side DB contention in my experience Forks is one way to remove ordering, and if we need to support it anyways it's a nice benefit. Richard Barnes wrote: >

11.1.7. <ins> An AppAck proposal is used to acknowledge receipt of application messages.  Though this information implies no change to the group, it is structured as a Proposal message so that it is included in the group's transcript by being included in Commit messages. An AppAck proposal represents a set of messages received by the sender in the current epoch.  Messages are represented by the "sender" and "generation" values in the MLSCiphertext for the message.  Each MessageRange represents receipt of a span of messages whose "generation" values form a continuous range from "first_generation" to "last_generation", inclusive. AppAck proposals are sent as a guard against the Delivery Service dropping application messages.  The sequential nature of the "generation" field provides a degree of loss detection, since gaps in the "generation" sequence indicate dropped messages.  AppAck completes this story by addressing the scenario where the Delivery Service drops all messages after a certain point, so that a later generation is never observed.  Obviously, there is a risk that AppAck messages could be suppressed as well, but their inclusion in the transcript means that if they are suppressed then the group cannot advance at all. The schedule on which sending AppAck proposals are sent is up to the application, and determines which cases of loss/suppression are detected.  For example: The application might have the committer include an AppAck proposal whenever a Commit is sent, so that other members could know when one of their messages did not reach the committer. The application could have a client send an AppAck whenever an application message is sent, covering all messages received since its last AppAck.  This would provide a complete view of any losses experienced by active members. The application could simply have clients send AppAck proposals on a timer, so that all participants' state would be known. An application using AppAck proposals to guard against loss/ suppression of application messages also needs to ensure that AppAck messages and the Commits that reference them are not dropped.  One way to do this is to always encrypt Proposal and Commit messages, to make it more difficult for the Delivery Service to recognize which messages conatain AppAcks.  The application can also have clients enforce an AppAck schedule, reporting loss if an AppAck is not received at the expected time. 11.1.8. </ins> Add and Remove proposals can be constructed and sent to the group by a party that is outside the group.  For example, a Delivery Service might propose to remove a member of a group who has been inactive for


ratchet of node index N in some epoch n.  Then, for that member, at least the following values have been consumed and MUST be deleted: <del> the "commit_secret", "joiner_secret", "member_secret", "epoch_secret", "encryption_secret" of that epoch n as well as the "init_secret" of the previous epoch n-1, </del>  all node secrets in the Secret Tree on the path from the root to the leaf with node index N, consume (at least) values G, F, D, AR0, AR1, K1, N1 as well as the key schedule secrets used to derive G (the "encryption_secret"), namely "init_secret" of epoch n-1 and "commit_secret", <del> "joiner_secret", "member_secret", "epoch_secret" of epoch n.  The client MAY retain (not consume) the values K0 and N0 to allow for out-of-order delivery, and SHOULD retain AR2 for processing future messages. </del>  8.6. From the "joiner_secret" in the decrypted GroupSecrets object and the PSKs specified in the "GroupSecrets", derive the <del> "member_secret" and using that the "welcome_secret", "welcome_key", and "welcome_nonce".  Use the key and nonce to decrypt the "encrypted_group_info" field. </del>  Verify the signature on the GroupInfo object.  The signature input comprises all of the fields in the GroupInfo object except the

This is a small suggestion to move the definition of the to the end of the first Extract-Expand (KDF) cycle of the [key schedule]. This makes more sense from a cryptographic perspective as we would not be using anything from the middle of a KDF cycle. A related suggestion is to remove the from the document. I removed it from the key schedule, removed all the mentions to forget it as soon as possible and updated the description of deriving the and from the [welcoming new members] section. [key schedule]: URL [welcoming new members]: URL
Thanks for the quick fix, NAME !

ratchet of node index N in some epoch n.  Then, for that member, at least the following values have been consumed and MUST be deleted:  <ins> the "commit_secret", "joiner_secret", "epoch_secret", "encryption_secret" of that epoch n as well as the "init_secret" of the previous epoch n-1, </ins> all node secrets in the Secret Tree on the path from the root to the leaf with node index N, consume (at least) values G, F, D, AR0, AR1, K1, N1 as well as the key schedule secrets used to derive G (the "encryption_secret"), namely "init_secret" of epoch n-1 and "commit_secret",  <ins> "joiner_secret", "epoch_secret" of epoch n.  The client MAY retain (not consume) the values K0 and N0 to allow for out-of-order delivery, and SHOULD retain AR2 for processing future messages. </ins> 8.6. From the "joiner_secret" in the decrypted GroupSecrets object and the PSKs specified in the "GroupSecrets", derive the  <ins> "welcome_secret" and using that the "welcome_key" and "welcome_nonce".  Use the key and nonce to decrypt the "encrypted_group_info" field. </ins> Verify the signature on the GroupInfo object.  The signature input comprises all of the fields in the GroupInfo object except the


In order to allow the same Welcome message to be sent to all new members, information describing the group is encrypted with a <del> symmetric key and nonce randomly chosen by the sender.  This key and nonce are then encrypted to each new member using HPKE.  In the same encrypted package, the committer transmits the path secret for the lowest node contained in the direct paths of both the committer and the new member.  This allows the new member to compute private keys for nodes in its direct path that are being reset by the corresponding Commit. </del>  If the sender of the Welcome message wants the receiving member to include a PSK in the derivation of the "epoch_secret", they can

use and instead of and , for consistency with the rest of the spec the key and nonce are now derived from the rather than randomly chosen

In order to allow the same Welcome message to be sent to all new members, information describing the group is encrypted with a  <ins> symmetric key and nonce derived from the "joiner_secret" for the new epoch.  The "joiner_secret" is then encrypted to each new member using HPKE.  In the same encrypted package, the committer transmits the path secret for the lowest node contained in the direct paths of both the committer and the new member.  This allows the new member to compute private keys for nodes in its direct path that are being reset by the corresponding Commit. </ins> If the sender of the Welcome message wants the receiving member to include a PSK in the derivation of the "epoch_secret", they can


that the chosen version and ciphersuite is the best option supported by all members. <del> Initialize a one-member group with the following initial values (where "0" represents an all-zero vector of size KDF.Nh): </del>  Ratchet tree: A tree with a single node, a leaf containing an HPKE public key and credential for the creator Tree hash: The root hash of the above ratchet tree <del> Confirmed transcript hash: 0 </del>  <del> Interim transcript hash: 0 </del>  Init secret: a fresh random value of size "KDF.Nh"

While reviewing , I noticed that in one place, we define the initial to be the all-zero vector, and in another, we define it to be the empty octet string. In keeping with the idea of starting chained hashes with the empty octet string, this PR normalizes on the empty octet string for the transcript hashes as well.

that the chosen version and ciphersuite is the best option supported by all members.  <ins> Initialize a one-member group with the following initial values: </ins> Ratchet tree: A tree with a single node, a leaf containing an HPKE public key and credential for the creator Tree hash: The root hash of the above ratchet tree  <ins> Confirmed transcript hash: the zero-length octet string </ins>  <ins> Interim transcript hash: the zero-length octet string </ins> Init secret: a fresh random value of size "KDF.Nh"


RFC EDITOR PLEASE DELETE THIS SECTION.  draft-10 Allow new members to join via an external Commit (*)

Immediately after I published draft-11, NAME noticed that I had failed to update the change log. This PR adds it to the editor's copy, so that it will be included in future versions.

RFC EDITOR PLEASE DELETE THIS SECTION. <ins> draft-11 Include subtree keys in parent hash (*) Pin HPKE to draft-07 (*) Move joiner secret to the end of the first key schedule epoch (*) Add an AppAck proposal Make initializations of transcript hashes consistent </ins> draft-10 Allow new members to join via an external Commit (*)


The second form of authentication is that group members can verify a message originated from a particular member of the group.  This is guaranteed by a digital signature on each message from the sender's <del> identity key. </del>  15.3.

I think this word was left from previous changes to the name of this particular key.. cc./ NAME NAME
Thanks Sofía !

The second form of authentication is that group members can verify a message originated from a particular member of the group.  This is guaranteed by a digital signature on each message from the sender's  <ins> signature key. </ins> 15.3.


has received its Commit message back from the server does it update its state to reflect the new member's addition. <del> Upon receiving the Welcome message and the corresponding Commit, the new member will be able to read and send new messages to the group. Messages received before the client has joined the group are ignored. </del>  Subsequent additions of group members proceed in the same way.  Any member of the group can download a KeyPackage for a new client and 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 <del> initialize its state. </del>  To enforce the forward secrecy and post-compromise security of messages, each member periodically updates their leaf secret.  Any

(see issue for details).
Currently section "4. Protocol Overview" states: I think this is a legacy from before the propose/commit scheme and got carried over. New members no longer need the Commit. The subsequent diagram also needs to be adapted.

has received its Commit message back from the server does it update its state to reflect the new member's addition.  <ins> Upon receiving the Welcome message, the new member will be able to read and send new messages to the group.  Messages received before the client has joined the group are ignored. </ins> Subsequent additions of group members proceed in the same way.  Any member of the group can download a KeyPackage for a new client and 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  <ins> initialize its state and join the group. </ins> To enforce the forward secrecy and post-compromise security of messages, each member periodically updates their leaf secret.  Any


managed in GitHub, but any substantive change should be discussed on the MLS mailing list. <del> Groups of agents who want to send each other encrypted messages need a way to derive shared symmetric encryption keys.  For two parties, this problem has been studied thoroughly, with the Double Ratchet emerging as a common solution doubleratchet signal.  Channels </del>  implementing the Double Ratchet enjoy fine-grained forward secrecy as well as post-compromise security, but are nonetheless efficient enough for heavy use over low-bandwidth networks. <del> For groups of size greater than two, a common strategy is to </del>  unilaterally broadcast symmetric "sender" keys over existing shared symmetric channels, and then for each agent to send messages to the group encrypted with their own sender key.  Unfortunately, while this

It first reads as if MLS allows groups to communicate with each other, which I don't think is the intent.
Thanks Mark ;)

managed in GitHub, but any substantive change should be discussed on the MLS mailing list.  <ins> A group of agents who want to send each other encrypted messages needs a way to derive shared symmetric encryption keys.  For two parties, this problem has been studied thoroughly, with the Double Ratchet emerging as a common solution doubleratchet signal.  Channels </ins> implementing the Double Ratchet enjoy fine-grained forward secrecy as well as post-compromise security, but are nonetheless efficient enough for heavy use over low-bandwidth networks.  <ins> For a group of size greater than two, a common strategy is to </ins> unilaterally broadcast symmetric "sender" keys over existing shared symmetric channels, and then for each agent to send messages to the group encrypted with their own sender key.  Unfortunately, while this


If R is blank, replace R with its left child until R is either non-blank or a leaf node <del> If R is a leaf node, the check fails </del>  If R.parent_hash is equal to the Parent Hash of P with Co-Path Child L, the check passes

Add a missing word. Fail on blank leaf.
The is currently not very well described. Depending on the reading it might also be wrong. In particular, the condition , if implemented as described makes it so that valid parent hashes are rejected. Take for example a simple tree with two leafs where the right leaf (node 2) sends a commit. In this case the other leaf will reject the parent hash because it fails when going down to the right leaf. This might be a wrong interpretation of the described verification procedure but then the description needs updating. I suggest to add a more formal description of the parent hash generation and verification (e.g. pseudocode) to reduce ambiguity.
I think that condition should read "If R is a blank leaf node, the check fails".
Agree with NAME here; the matches this interpretation. So I think we just need to adjust that text.
I agree, this was fixed in

If R is blank, replace R with its left child until R is either non-blank or a leaf node  <ins> If R is a blank leaf node, the check fails </ins> If R.parent_hash is equal to the Parent Hash of P with Co-Path Child L, the check passes


Blank the intermediate nodes along the path from the removed leaf to the root  11.1.4. A PreSharedKey proposal can be used to request that a pre-shared key

This PR adds text to truncate the tree to perform garbage collection on removal. [ref: URL]
Over time we lost the rule that the ratchet tree should also be trimmed after members have been removed (where it really makes sense), not only when members are added.
NAME posted to address this issue
Fixed in - URL
Earlier, we had considered a "masking" approach for sender data, much like QUIC does for . This approach would save 28 bytes of overhead (nonce + tag), but was abandoned because of uncertainty around the security of the masking approach. Since then, Bellare et al. published , which includes what is effectively a proof of the security properties of the QUIC scheme. The QUIC scheme is essentially scheme HN1 of the paper, the main difference being that QUIC encrypt not the nonce directly, but a packet number which is used to compute the nonce. Our application would be similar, since we would encrypt metadata used to encrypt the key and the nonce. Given this new analysis, is it time to bring back masking?
Fixed by

Blank the intermediate nodes along the path from the removed leaf to the root <ins> Truncate the tree by reducing the size of tree until the rightmost non-blank leaf node </ins> 11.1.4. A PreSharedKey proposal can be used to request that a pre-shared key


a Commit message, who apply it to keep their local views of the tree in sync with the sender's.  More specifically, when a member commits a change to the tree (e.g., to add or remove a member), it transmits <del> an UpdatePath message containing a set of public and encrypted private values for intermediate nodes in the direct path of a leaf. The other members of the group use these values to update their view of the tree, aligning their copy of the tree to the sender's. </del>  <del> To transmit this update, the sender broadcasts to the group the following information for each node in the direct path of the leaf, including the root: </del>  The public key for the node The path secret value for a given node is encrypted for the subtree corresponding to the parent's non-updated child, that is, the child <del> on the copath of the leaf node.  There is one encrypted path secret for each public key in the resolution of the non-updated child. </del>  <del> The recipient of a path update processes it with the following steps: </del>  Compute the updated path secrets.

202105 Interim: To be reviewed by editors.
Ready to merge modulo one nit.

a Commit message, who apply it to keep their local views of the tree in sync with the sender's.  More specifically, when a member commits a change to the tree (e.g., to add or remove a member), it transmits  <ins> an UpdatePath containing a set of public keys and encrypted path secrets for intermediate nodes in the direct path of its leaf.  The other members of the group use these values to update their view of the tree, aligning their copy of the tree to the sender's. </ins>  <ins> An UpdatePath contains the following information for each node in the direct path of the sender's leaf, including the root: </ins> The public key for the node The path secret value for a given node is encrypted for the subtree corresponding to the parent's non-updated child, that is, the child  <ins> on the copath of the sender's leaf node.  There is one encryption of the path secret to each public key in the resolution of the non- updated child. </ins>  <ins> The recipient of an UpdatePath processes it with the following steps: </ins> Compute the updated path secrets.


7.4.  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

This PR fixes a few small issues I noticed while reviewing mlspp for compliance with the spec: The size of the vector needs to expand to support trees for large groups The content of the extension is specified is included in so that it goes into the transcript now carries an even though the value is always present, so that the transcript matches the data on the wire
Discussed at 20210526 Interim. Merge
URL In the current protocol draft: Unless I overlooked it, the current draft seems to be also missing a "simple" DoS case where a handshake message could omit / malform some (e.g. by having an empty ciphertext or invalid kem_output) -- this should be easily detectable (either by members themselves or if handshake messages are transmitted as MLSPlaintext, the delivery service could reject that). For that originally discussed case, I guess the sketched out solution with a "Nack" message containing a DLEQ proof could work. Besides the drawbacks described there, one will also need to extend HPKE definitions to allow for this "directly decrypt from the revealed shared secret" use case.
You're correct that there is a simpler DoS case where the sender could simply send a malformed . It shares with the DoS described that it is only detectable by affected nodes. And I think that those two together cover the DoS possibilities, since it covers the two things that can be chosen in Commit. As far as Nack/DLEQ -- I'm not sure what you have in mind there. It seems like you would want zero-knowledge proofs for two relationships between HPKE ciphertexts: (1) that two ciphertexts are encryptions of the same value under two different public keys, and (2) that a parent ciphertext is an encryption of the parent node secret, and the child ciphertext is an encryption of the child node secret. It's not clear that there are solutions for either of these. What we can do though, is make sure that the Security Considerations cover these risks. I would propose focusing on documenting the risk rather than fixing it.
For that simpler case, it can be detected by other nodes by doing extra checks (e.g. checking all cipher text lengths or public key formats match what one would expect in a particular ciphersuite). I was referring to the final comments here: URL So, the goal won't be to prevent malicious insiders, but allow affected nodes to report that they couldn't process Commit by revealing the corresponding ephemeral key. With HPKE / current draft, it may be: for malformed path secrets, affected nodes reveal + others check DLEQ; then using compute for and can go ahead to check the path secrets (either the ciphertext is malformed or the public keys derived from secrets don't match the received public keys in Commit)
Discussed on virtual interim 2020-10-20: Closing pending a more detailed proposal.
At a minimum we need to make handshake key chains. If we do Proposals (), then we will need to encrypt multiple handshake message per epoch, so we will need key chains instead of just a single handshake key. Even if we don't do , this would improve symmetry / code reuse. Specifically, we should repurpose the ASTree "per-user secret tree". Instead of the leaves of the tree being the starting point for an application key chain, there would be used to derive the starting points for the application key chain, handshake key chain, and possibly exported per-user secrets.
This ended up not being necessary. For handshake messages, we don't need forward secrecy within an epoch, so the full AStree machinery isn't necessary. Instead we use the simple TLS-like nonce updating described in

7.4. <ins> The "parent_hash" extension carries information to authenticate the structure of the tree, as described below. </ins> 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


the uncompressed Elliptic-Curve-Point-to-Octet-String conversion according to SECG.  Note that each new credential that has not already been validated by the application MUST be validated against the Authentication Service.

The spec doesn't say anything about the signature encoding yet. This PR adds a paragraph clarifying that signature encoding is done like in TLS.

the uncompressed Elliptic-Curve-Point-to-Octet-String conversion according to SECG. <ins> The signatures used throughout this document are encoded as specified in RFC8446.  In particular, ECDSA signatures are DER-encoded and EdDSA signatures are defined as the concatenation of "r" and "s" as specified in RFC8032. </ins> Note that each new credential that has not already been validated by the application MUST be validated against the Authentication Service.


value: Note that the "membership_tag" only needs to be computed for <del> MLSPlaintext messages that will be sent over the wire, and isn't needed for those that will be encrypted and transmitted as MLSCiphertext messages. </del>  9.2.

NAME pointed out to me that there's currently no way for a DS to distinguish between MLSPlaintext and MLSCiphertext messages. Obviously, an application could provide a flag on the outside of a message, but there are a few relatively minor benefits to including a flag in MLS: Signing the flag authenticates the sender's intent Including the flag in the transcript confirms that all group members got it in the same format Including the flag in the struct provides a clear signal as to whether the needs to be present/verified Since these benefits are pretty minor, I could kind of go either way on whether to make this change. But it seemed worth writing up a PR to see what the change would look like.
My original intention was not for the DS to distinguish the message types, but also for a receiving client. Even if there's a group policy that dictates whether to encrypt handshake messages, the client won't know how to parse a message, because the fields on plaintext and ciphertext are not aligned in the two structs. So I think fixing that would be the low-hanging fruit. That being said, I agree with NAME that it would be neat to have a flag that signals the encryption status, but also communicates the sender's intent in a cryptographically authenticated manner.
I agree with your reasoning and I think it's worthwhile doing. The only part that is not straight forward for me is this: If the sender is of type the also doesn't need to be present for an . This should either be clarified, or we could have a third wire format for external senders.
Re: -- there isn't an sender type, but there are and . You're correct that in both of those cases, the shouldn't be populated. The signal in this PR helps distinguish the two cases when .
Discussed at 20211004 interim, merge.
Here's the Weekly Digest for : - - Last week 18 issues were created. Of these, 3 issues have been closed and 15 issues are still open. :greenheart: , by :greenheart: , by :greenheart: , by :greenheart: , by :greenheart: , by :greenheart: , by :greenheart: , by :greenheart: , by :greenheart: , by :greenheart: , by :greenheart: , by :greenheart: , by :greenheart: , by :greenheart: , by :greenheart: , by :heart: , by :heart: , by :heart: , by :speaker: , by It received 5 comments. - - Last week, 10 pull requests were created, updated or merged. Last week, 1 pull request was opened. :greenheart: , by Last week, 7 pull requests were updated. :yellowheart: , by :yellowheart: , by :yellowheart: , by :yellowheart: , by :yellowheart: , by :yellowheart: , by :yellowheart: , by Last week, 2 pull requests were merged. :purpleheart: , by :purpleheart: , by - - Last week there were 4 commits. :hammerandwrench: by :hammerandwrench: by :hammerandwrench: by :hammerandwrench: by - - Last week there were 4 contributors. :bustinsilhouette: :bustinsilhouette: :bustinsilhouette: :bustin_silhouette: - - Last week there were no stargazers. - - Last week there were no releases. - - That's all for last week, please :eyes: Watch and :star: Star the repository to receive next weekly updates. :smiley: You can also .

value: Note that the "membership_tag" only needs to be computed for  <ins> MLSPlaintext messages that will be sent over the wire ("wire_format == mls_plaintext").  It isn't needed for messages that will be encrypted and transmitted as MLSCiphertext messages ("wire_format == mls_ciphertext"). </ins> 9.2.


represents a transcript over the whole history of MLSPlaintext Commit messages, up to the confirmation tag field in the current MLSPlaintext message.  The confirmation tag is then included in the <del> transcript for the next epoch.  The interim transcript hash is passed to new members in the GroupInfo struct, and enables existing members to incorporate a Commit message into the transcript without having to store the whole MLSPlaintextCommitAuthData structure. </del>  As shown above, when a new group is created, the "interim_transcript_hash" field is set to the zero-length octet 11.1.8.  Add and Remove proposals can be constructed and sent to the group by a party that is outside the group.  For example, a Delivery Service might propose to remove a member of a group who has been inactive for Construct a GroupInfo reflecting the new state: <del> Group ID, epoch, tree, confirmed transcript hash, and interim transcript hash from the new state </del>  The confirmation_tag from the MLSPlaintext object  Sign the GroupInfo using the member's private signing key Encrypt the GroupInfo using the key and nonce derived from the node in the "tree" array divided by two. Construct a new group state using the information in the GroupInfo <del> object.  The new member's position in the tree is "index", as defined above.  In particular, the confirmed transcript hash for the new state is the "prior_confirmed_transcript_hash" in the GroupInfo object. </del>  Update the leaf at index "index" with the private key corresponding to the public key in the node. their participation in the group (once in the ratchet tree) In the Welcome message, to tell new members of a group what <del> parameters are being used by the group </del>  In the GroupContext object, to ensure that all members of the group have the same view of the parameters in use <del> In other words, clients advertise their capabilities in KeyPackage extensions, the creator of the group expresses its choices for the group in Welcome extensions, and the GroupContext confirms that all members of the group have the same view of the group's extensions. </del>  This extension mechanism is designed to allow for secure and forward- compatible negotiation of extensions.  For this to work, KP: KeyPackage messages <del> GI: GroupInfo objects </del>  Recommended: Whether support for this extension is recommended by the IETF MLS WG.  Valid values are "Y" and "N".  The "Recommended"

NAME r?
Discussed at 20211004 interim, ready to merge after adding some additional detail about when to use one or the other.
After a thorough review of the current MLS extension system, there are a few questions and we also have a few suggestions for changes. The total set of extensions is fixed and cannot be changed during the lifetime of a group. The set of extensions that is associated with a group is decided by the group creator. All extension types from the set have to be supported by all clients and must therefore be advertised in their capabilities extension. The implementation needs to validate the support in 4 instances: When creating a new group, when joining a group, when creating an add proposal, when parsing someone else's add proposal as part of a Commit. Key package extensions are simple and well-understood, in that they never leave the KeyPackage and there is group consensus through the tree hash. The other kind of extension seems to be called a Group Info Extension (GIE). GIEs can come in two flavours: a) they must be included in the group context, b) they must not be included in the group context. The ratchet tree extensions is an example of type b). GIEs of type a) are essentially just passed from the Adder to the New Joiner, using Welcome messages as an E2EE pipe.The spec does not explicitly introduce the notion of a GIE, it is only mentioned in 16.2. Should the spec be more explicit here? Should the flavour of a GIE (as described above) be reflected in the struct in the spec? E.g. a message type that indicates whether a GIE should be included in the GroupContext (see proposal below). Can the content of a GIE be dynamic (i.e. it can change during the lifetime of a group)? The ratchet tree extension seems to validate that assumption, it is a bit unclear if this is true for all kinds of GIE. We should be more explicit in the spec. If the content of GIEs is indeed dynamic, should those GIEs always be excluded from the GroupContext? Or optionally? If they are to be included in the GroupContext, how do clients agree on the changes? (See proposal below) We need more guidance regarding extensions in order to keep implementations aligned and capable of interop. A first suggestion would be to allow for the creation of extensions that have dynamic content. This would allow them to use MLS to agree on arbitrary data. The requirement would be to make GIEs with dynamic content more robust by introducing a new proposal type to update the content of extensions: Another improvement to the current extension system would be to be more clear about the kinds of extensions by introducing a dedicated field for every extension: This would allow generic extensions, which in turn allow committers of Add proposals to communicate arbitrary data to new group members in a robust fashion.
Thanks for the analysis here. To your point about “dynamic GIEs” — My initial concept here was that the GIEs included in the GroupContext would be fixed for all time. The ratchet tree extension would not be included in the GroupContext; as you say, it just uses the Welcome for E2E tunneling. Boiling this down a bit, it seems like there are a few problems to solve here: Clarifying which extensions in the GroupInfo go into the GroupContext and which are just used for tunneling E2E Allowing for updates to the group’s extensions For the former, there's a pretty simple solution available: Just have separate fields in the GroupContext for permanent and ephemeral extensions. The joiner processing would then place the into the MLS state / GroupContext, and use/discard the . We could also then update the IANA considerations to reflect these additional categories for where an extension can appear. For the latter problem, I think your ExtensionUpdate is about the right approach. It might be simpler just to have the update proposal provide wholesale replacement to the extensions, so that we don't have to design a merge algorithm. I'm not totally sure this is worth doing right now, since it seems easy to do as an extension. But especially if we keep it simple, I'm not strongly opposed.
NAME NAME and I discussed real-time, propose to do the following: Split out the GroupContext extensions as above Define a new proposal type to update the group's extensions:

represents a transcript over the whole history of MLSPlaintext Commit messages, up to the confirmation tag field in the current MLSPlaintext message.  The confirmation tag is then included in the  <ins> transcript for the next epoch.  The interim transcript hash is computed by new members using the confirmation tag in the GroupInfo struct, and enables existing members to incorporate a Commit message into the transcript without having to store the whole MLSPlaintextCommitAuthData structure. </ins> As shown above, when a new group is created, the "interim_transcript_hash" field is set to the zero-length octet 11.1.8. <ins> A GroupContextExtensions proposal is used to update the list of extensions in the GroupContext for the group. "struct { Extension extensions<0..2^32-1>; } GroupContextExtensions; " A member of the group applies a GroupContextExtensions proposal by removing all of the existing extensions from the GroupContext object for the group and replacing them with the list of extensions in the proposal.  (This is a wholesale replacement, not a merge.  An extension is only carried over if the sender of the proposal includes it in the new list.)  Note that once the GroupContext is updated, its inclusion in the confirmation_tag by way of the key schedule will confirm that all members of the group agree on the extensions in use. 11.1.9. </ins> Add and Remove proposals can be constructed and sent to the group by a party that is outside the group.  For example, a Delivery Service might propose to remove a member of a group who has been inactive for Construct a GroupInfo reflecting the new state:  <ins> Group ID, epoch, tree, confirmed transcript hash, interim transcript hash, and group context extensions from the new state </ins> The confirmation_tag from the MLSPlaintext object <ins> Other extensions as defined by the application </ins> Sign the GroupInfo using the member's private signing key Encrypt the GroupInfo using the key and nonce derived from the node in the "tree" array divided by two. Construct a new group state using the information in the GroupInfo  <ins> object. The GroupContext contains the "group_id", "epoch", "tree_hash", "confirmed_transcript_hash", and "group_context_extensions" fields from the GroupInfo object. The new member's position in the tree is "index", as defined above. </ins> Update the leaf at index "index" with the private key corresponding to the public key in the node. their participation in the group (once in the ratchet tree) In the Welcome message, to tell new members of a group what  <ins> parameters are being used by the group, and to provide any additional details required to join the group </ins> In the GroupContext object, to ensure that all members of the group have the same view of the parameters in use  <ins> In other words, an application can use GroupContext extensions to ensure that all members of the group agree on a set of parameters. Clients indicate their support for parameters in KeyPackage extensions.  New members of a group are informed of the group's GroupContext extensions via the "group_context_extensions" field in the GroupInfo or PublicGroupState object.  The "other_extensions" field in a GroupInfo object can be used to provide additional parameters to new joiners that are used to join the group. </ins> This extension mechanism is designed to allow for secure and forward- compatible negotiation of extensions.  For this to work, KP: KeyPackage messages  <ins> GC: GroupContext objects (and the "group_context_extensions" field of GroupInfo objects) GI: The "other_extensions" field of GroupInfo objects </ins> Recommended: Whether support for this extension is recommended by the IETF MLS WG.  Valid values are "Y" and "N".  The "Recommended"


7.1. The "capabilities" extension indicates what protocol versions, <del> ciphersuites, and protocol extensions are supported by a client. </del>  This extension MUST be always present in a KeyPackage.  Extensions that appear in the "extensions" field of a KeyPackage MUST be received during the current epoch.  Invalid proposals include, for example, proposals with an invalid signature or proposals that are semantically invalid, such as an Add when the sender does not have <del> the application-level permission to add new users.  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 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 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 comitter 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. In the event that a valid proposal is omitted from the next Commit, MLS Extension Types (mls-extension-types)  MLS Credential Types (mls-credential-types) All of these registries should be under a heading of "Messaging Layer 16.3.  This registry lists identifiers for types of credentials that can be used for authentication in the MLS protocol.  The credential type field is two bytes wide, so valid credential type values are in the Initial contents: <del> 16.4. </del>  Specification Required RFC8126 registry requests are registered after a three-week review period on the MLS DEs' mailing list: mls-reg-

NAME pointed out to me some improvements that could be made in the extensibility of proposals. This PR makes a few changes to smooth out deployment of new proposals: Extends to two bytes, creates an IANA registry, and reserves some space for private use Extends the extension so that clients can indicate which proposal types they support Forbids committers from including proposals that some people don't support
Just for context: This PR addresses the second part of .
Agreed. Looks good.
Discussed at 20211004 interim, merge.
The "Sequencing of State Changes" and "Application Messages" sections date to very early in the document's life, and probably need some updates to reflect current thinking.
Closing for now, might reopen based on WGLC review.
After a thorough review of the current MLS extension system, there are a few questions and we also have a few suggestions for changes. The total set of extensions is fixed and cannot be changed during the lifetime of a group. The set of extensions that is associated with a group is decided by the group creator. All extension types from the set have to be supported by all clients and must therefore be advertised in their capabilities extension. The implementation needs to validate the support in 4 instances: When creating a new group, when joining a group, when creating an add proposal, when parsing someone else's add proposal as part of a Commit. Key package extensions are simple and well-understood, in that they never leave the KeyPackage and there is group consensus through the tree hash. The other kind of extension seems to be called a Group Info Extension (GIE). GIEs can come in two flavours: a) they must be included in the group context, b) they must not be included in the group context. The ratchet tree extensions is an example of type b). GIEs of type a) are essentially just passed from the Adder to the New Joiner, using Welcome messages as an E2EE pipe.The spec does not explicitly introduce the notion of a GIE, it is only mentioned in 16.2. Should the spec be more explicit here? Should the flavour of a GIE (as described above) be reflected in the struct in the spec? E.g. a message type that indicates whether a GIE should be included in the GroupContext (see proposal below). Can the content of a GIE be dynamic (i.e. it can change during the lifetime of a group)? The ratchet tree extension seems to validate that assumption, it is a bit unclear if this is true for all kinds of GIE. We should be more explicit in the spec. If the content of GIEs is indeed dynamic, should those GIEs always be excluded from the GroupContext? Or optionally? If they are to be included in the GroupContext, how do clients agree on the changes? (See proposal below) We need more guidance regarding extensions in order to keep implementations aligned and capable of interop. A first suggestion would be to allow for the creation of extensions that have dynamic content. This would allow them to use MLS to agree on arbitrary data. The requirement would be to make GIEs with dynamic content more robust by introducing a new proposal type to update the content of extensions: Another improvement to the current extension system would be to be more clear about the kinds of extensions by introducing a dedicated field for every extension: This would allow generic extensions, which in turn allow committers of Add proposals to communicate arbitrary data to new group members in a robust fashion.
Thanks for the analysis here. To your point about “dynamic GIEs” — My initial concept here was that the GIEs included in the GroupContext would be fixed for all time. The ratchet tree extension would not be included in the GroupContext; as you say, it just uses the Welcome for E2E tunneling. Boiling this down a bit, it seems like there are a few problems to solve here: Clarifying which extensions in the GroupInfo go into the GroupContext and which are just used for tunneling E2E Allowing for updates to the group’s extensions For the former, there's a pretty simple solution available: Just have separate fields in the GroupContext for permanent and ephemeral extensions. The joiner processing would then place the into the MLS state / GroupContext, and use/discard the . We could also then update the IANA considerations to reflect these additional categories for where an extension can appear. For the latter problem, I think your ExtensionUpdate is about the right approach. It might be simpler just to have the update proposal provide wholesale replacement to the extensions, so that we don't have to design a merge algorithm. I'm not totally sure this is worth doing right now, since it seems easy to do as an extension. But especially if we keep it simple, I'm not strongly opposed.
NAME NAME and I discussed real-time, propose to do the following: Split out the GroupContext extensions as above Define a new proposal type to update the group's extensions:
Looks good to me!

7.1. The "capabilities" extension indicates what protocol versions,  <ins> 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. </ins> This extension MUST be always present in a KeyPackage.  Extensions that appear in the "extensions" field of a KeyPackage MUST be received during the current epoch.  Invalid proposals include, for example, proposals with an invalid signature or proposals that are semantically invalid, such as an Add when the sender does not have  <ins> the application-level permission to add new users.  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 (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, 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 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 comitter 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. </ins> The Commit MUST NOT combine proposals sent within different epochs. In the event that a valid proposal is omitted from the next Commit, MLS Extension Types (mls-extension-types) <ins> MLS Proposal Types (mls-proposal-types) </ins> MLS Credential Types (mls-credential-types) All of these registries should be under a heading of "Messaging Layer 16.3. <ins> This registry lists identifiers for types of proposals that can be made for changes to an MLS group.  The extension type field is two bytes wide, so valid extension type values are in the range 0x0000 to 0xffff. Template: Value: The numeric value of the proposal type Name: The name of the proposal type Recommended: Whether support for this extension is recommended by the IETF MLS WG.  Valid values are "Y" and "N".  The "Recommended" column is assigned a value of "N" unless explicitly requested, and adding a value with a "Recommended" value of "Y" requires Standards Action RFC8126.  IESG Approval is REQUIRED for a Y->N transition. Reference: The document where this extension is defined Initial contents: 16.4. </ins> This registry lists identifiers for types of credentials that can be used for authentication in the MLS protocol.  The credential type field is two bytes wide, so valid credential type values are in the Initial contents:  <ins> 16.5. </ins> Specification Required RFC8126 registry requests are registered after a three-week review period on the MLS DEs' mailing list: mls-reg-


Note that, as a PSK may have a different lifetime than an update, it does not necessarily provide the same Forward Secrecy (FS) or Post- <del> Compromise Security (PCS) guarantees as a Commit message. </del>  Each PSK in MLS has a type that designates how it was provisioned. External PSKs are provided by the application, while recovery and re-

Most of these were stale; see comments for more specifics. I opened as a general issue arising from the final TODO.
Discussed at 20211004 interim, will merge after adding text in comment. NAME create new issue for multiple PSKs and PR for cascading HKDF.
The "Sequencing of State Changes" and "Application Messages" sections date to very early in the document's life, and probably need some updates to reflect current thinking.
Closing for now, might reopen based on WGLC review.

Note that, as a PSK may have a different lifetime than an update, it does not necessarily provide the same Forward Secrecy (FS) or Post-  <ins> Compromise Security (PCS) guarantees as a Commit message.  Unlike the key pairs populated in the tree by an Update or Commit, which always freshly generated, PSKs may be pre-distributed and stored.  This creates the risk that a PSK may be compromised in the process of distribution and storage.  The security that the group gets from injecting a PSK thus depends on both the entropy of the PSK and the risk of compromise.  These factors are outside of the scope of this document, but should be considered by application designers relying on PSKs. </ins> Each PSK in MLS has a type that designates how it was provisioned. External PSKs are provided by the application, while recovery and re-


in the Handshake Key Schedule to be ease chaining in case we switch design. (*)  draft-00 Initial adoption of draft-barnes-mls-protocol-01 as a WG item.

The math for left-balanced binary trees is very reliable once you have it right, but can be hard to get right from scratch. This PR provides an appendix that explains a theory and code for managing these trees.
I feel this is very useful to have in the draft for implementors. What should be the plan to merge it ? NAME
Thanks ! I expect that it's going to be very useful for implementers. I am trusting that it is correct Python syntax ;) Pointed out a potential typo in the comments.

in the Handshake Key Schedule to be ease chaining in case we switch design. (*) <ins> Added an appendix with example code for tree math </ins> draft-00 Initial adoption of draft-barnes-mls-protocol-01 as a WG item.


The proposals included by value in an External Commit MUST meet the following conditions: <del> There MUST be a single Add proposal that adds the new issuing new member to the group </del> There MUST be a single ExternalInit proposal <del> There MUST NOT be any Update proposals </del>  If a Remove proposal is present, then the "credential" and "endpoint_id" of the removed leaf MUST be the same as the There MUST NOT be any ExternalInit proposals External Commits MUST contain a "path" field (and is therefore a <del> "full" Commit) </del>  External Commits MUST be signed by the new member.  In particular, the signature on the enclosing MLSPlaintext MUST verify using the

In , we removed the requirement for an external Commit to have an Add proposal, under the theory that the required already has a leaf node for the new joiner. When I went to implement this, however, I noticed some ambiguities. First, the Commit processing section doesn't mention when the new joiner is added. The obvious approach is to do the add as part of the processing, but that processing presumes that the sender is already in the tree (for example, there is no provision for extending the tree). Second, the UpdatePath for an external commit needs to be done in a specific order in order to avoid a circular dependency. The TreeKEM encapsulation done by the joiner uses the GroupContext a context parameter, including the hash of the tree being used. But the LeafNode in the has to have a parent hash in it, which can only be computed after the encapsulation operation. This circularity can be resolved, but only by preparing the UpdatePath with the following specific steps: Compute the path secrets Compute the parent hash and the new leaf node Compute the new tree hash Encrypt the path secrets to the rest of the group That rules out some implementations that compute path secrets and encrypt them in one traversal of the direct path (this is what MLSpp does now). The options here are basically: Re-add the Add proposal, thus having a throw-away temporary LeafNode for the joiner Specify that the UpdatePath must be computed in the specific order above To me, (2) seems like a pretty clear winner.
Actually, it seems like there's a simpler option here: Extend the definition of an ExternalInit proposal to say that part of how it is applied is to ensure there is a blank leaf in the tree, extending the tree if necessary. (This implies that ExternalInit must be done after any Remove.) The leftmost blank leaf is the sender's leaf for purposes of UpdatePath processing. The provisional tree used in encrypting path secrets is still the provisional tree, in the sense that all proposals have been applied. This tree has a blank leaf for the UpdatePath sender. If we clarify these points, I think everything else works unmodified.
I agree with the first point. Even if the Add is not explicitly present, the external commit effectively emulates an Add anyway. To the second point: The solution is only simpler if that's how you want to implement it . I haven't tried it, but if you have chosen a different order of implementation, i.e. perform all changes to the provisional tree (including the update path and parent hash), then compute the tree hash and then encrypt the path, you now have to go and compute an additional tree hash before applying the update path, then apply the update path and keep the partial tree hash around for later encryption/decryption. This was not a part of MLS that I have looked at from a provable security perspective, but I'm wondering what the consequences are from a security perspective if we're including a parent hash for an incomplete provisional tree.
It seems like the main question here is what tree is provided to TreeKEM encryptions of path secrets. I'll call this the "context tree" below. It sounds like you have in mind that the tree provided to TreeKEM has the UpdatePath applied as well as the proposals, but that's not what the spec says right now: The starting point for this issue is that the "provisional" tree right now doesn't really faithfully capture the tree you're encrypting to, at least in the sense that it has a different number of leaves. That seemed wrong to me. It seems like we have the following options here: Do nothing. The context tree doesn't necessarily have a blank for the joiner. Maximal divergence between the new tree and the context tree (among these options) TreeKEM encryption operates on a different tree than the context tree Ensure the provisional tree has a blank leaf for the joiner (as up-thread). Context tree has a blank leaf for the joiner, but direct path doesn't match new tree TreeKEM encryption can operate on the context tree, because the structure is correct Use the "new" ratchet tree as the TreeKEM context. Context tree is exactly the new tree -- could probably eliminate provisional Requires separate steps for (1) updating tree nodes and (2) encrypting path secrets ... when these are currently described together as effectively one operation Personally, it seems like the answer is (2) or (3), and I probably lean toward (3). I don't think (1) is insecure, because the context tree does capture all of the nodes to which path secrets are encrypted. But the divergence is worrisome and it's cheap to do better. Between (2) and (3), there's a trade-off between fidelity and spec change size. The impact of (2) is more localized (just the ExternalInit section). Updating the spec to do (3) will require amending the description of update paths as well as the Commit creation / processing rules (to separate update/encrypt and remove provisional, respectively). But honestly, removing the provisional tree seems like a worthwhile simplification, so between that and the greater fidelity of the context tree, (3) seems worth the cost.
Actually, as I look into implementing this: We still need a provisional GroupContext, because the will differ. But we can at least have two trees instead of three.
When a new joiner sends an external Commit, within the current structure, they have to generate and send two KeyPackages: First in an Add proposal, and second in . Obviously, this is wasteful, since the second immediately overwrites the first. It also complicates receiver processing, since the receiver has to find where the appropriate Add happened so that it knows the sender's index in the new tree, so that it knows how to apply the update path in the Commit. We should remove the requirement for an Add commit in an external Commit, and instead assign the new member the leftmost free slot after all proposals have been applied, then apply the commit from there.
I agree that the key package in the Add proposal is redundant. I'm not so sure it will simplify implementations so much, because key packages from the update path of a Commit are usually treated like Update proposals (e.g. the constraint that the identity must be the same between the old and new credential). Tl;DR: I agree with the change, but I want to raise awareness about issues this introduces.
Approved, but see my comment in URL

The proposals included by value in an External Commit MUST meet the following conditions:  There MUST be a single ExternalInit proposal  <ins> There MUST NOT be any Add or Update proposals </ins> If a Remove proposal is present, then the "credential" and "endpoint_id" of the removed leaf MUST be the same as the There MUST NOT be any ExternalInit proposals External Commits MUST contain a "path" field (and is therefore a  <ins> "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> External Commits MUST be signed by the new member.  In particular, the signature on the enclosing MLSPlaintext MUST verify using the


A signature algorithm <del> MLS uses draft-08 of HPKE I-D.irtf-cfrg-hpke for public-key encryption.  The "DeriveKeyPair" function associated to the KEM for the ciphersuite maps octet strings to HPKE key pairs. </del>  Ciphersuites are represented with the CipherSuite type.  HPKE public keys are opaque values in a format defined by the underlying protocol

HPKE is in the RFC Editor queue, so there shouldn't be any further changes.
MLS draft-12 still references draft-08 of HPKE, which is now on draft-12. We should update to the final version of HPKE before finalizing MLS.

A signature algorithm  <ins> MLS uses HPKE for public-key encryption I-D.irtf-cfrg-hpke.  The "DeriveKeyPair" function associated to the KEM for the ciphersuite maps octet strings to HPKE key pairs. </ins> Ciphersuites are represented with the CipherSuite type.  HPKE public keys are opaque values in a format defined by the underlying protocol


5. The protocol uses "ratchet trees" for deriving shared secrets among a <del> group of clients. </del>  5.1. 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. </del>  The resolution of a non-blank node comprises the node itself, followed by its list of unmerged leaves, if any "merged" as they receive the private keys for nodes, as described in ratchet-tree-evolution.  5.4. A member of an MLS group advances the key schedule to provide forward secret is used to derive a new secret value for the corresponding node, from which the node's key pair is derived.  For example, suppose there is a group with four members, with C an unmerged leaf at node 5: MLS uses HPKE for public-key encryption I-D.irtf-cfrg-hpke.  The "DeriveKeyPair" function associated to the KEM for the ciphersuite <del> maps octet strings to HPKE key pairs. </del>  Ciphersuites are represented with the CipherSuite type.  HPKE public keys are opaque values in a format defined by the underlying protocol Blank the intermediate nodes along the path from "removed_index" to the root <del> Truncate the tree by reducing the size of tree until the rightmost non-blank leaf node </del>  11.1.4.

The terminology used in these sections is different, so that it's unclear if they're descriptions of the same operation with different levels of detail or entirely different operations performed at different times. I come in having read most of the original white paper, so that my intuition is that it's one operation performed against data stored in the tree structure. As such, I'm unclear how the -> relates to the via the "number of secrets [that] are derived from the epoch secret for different purposes". Also, The Group Creation section makes no reference to any of these secrets by name. I would kind of expect something called, , to be involved during group creation, but it doesn't seem like that's necessary. is defined anywhere? Is it application level data to be included as desired?
Can you add the definition of definition to ?
In , in the diagram copied below, the label, "secret", is passed to DeriveTreeSecret(), where in the preceding section, it was either "application" or "handshake". This is intentional?
Could you specifically call out where the authentication tag created by AES-256-GCM is stored? Some implementations will need to find it and pass it in as a param to .
The group creation and external init parts of the doc each have discussions in two parts. It'd be easier to follow if they were consolidated. I would suggest moving the group creation section up to the other relevant parts and maybe introduce the other concepts in the context of group creation... that's how implementations would start.
I'm not sure why there needs to be two ratchets per leaf node. It seems like they never get ratcheted at the same time, and they both get reset during the same event(s).
It was easier for me to understand the concept as just a kind of depth first search through the sibling's tree, favoring the left child, for a public or private key. If building an update path follows the addition of empty nodes, then for each step up the tree, the sibling's tree is not going to have filled any new key pairs yet, so that conducting a search at that moment is only going to resolve a public key from the previous epoch.
What does this mean? Is part of the sentence missing?
Leaving it to applications to determine how many resumption secrets to maintain creates a problem with ExternalInit's. The external init is predicated entirely on public group state, so that the new member(s) will not have any resumption secrets from prior epochs as determined by the application. If one of those resumption secrets is employed, the group fractures. Moreover, I'm not seeing how one resumptionsecret alone is enough for a recovery operation... if that's one of its intended uses, which seems to be implied. There's not much to go on in the doc, but it seems to suggest the resumption secret is included in the key derivation schedule as a PSK. But the schedule also requires an initsecret and a commit_secret as inputs, which a recovering node cannot rely on to be in sync with the group.
For members, A, C, and D of a 4 member group, where A performed the additions of B,C, and D and then broadcast a Welcome message to all the others, their respective trees would look like the following after each adds their own private keys locally: denotes that a public/private key pair resides at node x If node performs an another tree update, e.g. adds another member, when computing the updatePath for node 3, it must select a public key from the subtree rooted at node 5 (the underscore). No key pair has been computed for node 5 yet, so that it would select the public key for node C and proceed. This, however, prevents node D from decrypting the pathsecret for node 3 because D does not know the private key for C. If A is to encrypt the pathsecret multiple times---each with a different key---what's the protocol? How many copies are enough? Does the number of copies have to grow with the size of the tree?
Hi NAME thanks for raising all these issues. There's an effort under way to give the draft an overhaul from an editorial standpoint. That should help with some of the issues around definitions and structuring/sequence of sections. Regarding : is just a function that's used in multiple places and the label changes depending on what kind of secret we're deriving. Regarding storage of authentication tags: I'm not sure what you mean by "store" here? This sounds like an implementation specific question that the draft won't be able to answer. Regarding two ratchets per leaf node: I assume you mean that from a leaf secret we derive a node secret and a path secret. This is to ensure that keys are cryptographically separate, such that they can be used by different cryptographic primitives. Cryptographic assumptions on primitives such as KDFs, authenticated encryption, etc usually assume that the key is only used with that primitive. As a consequence, proving the security of the protocol would likely require non-standard assumptions if one were not to separate keys properly. Regarding truncation: The sentence is meant to express that, starting from the rightmost leaf, leaves are removed until the first non-blank leaf is reached. Regarding resumptions and ExternalInits: Good catch! That should be added to the section on ExternalInit. Recovery through resumption secrets: When creating a group, it can be linked to an existing group through the use of resumption secrets. Linking a new group to an existing group means that the creator follows the normal group creation process, i.e. creating a group and adding other members, sending Welcome messages, etc. However, when adding the new members, the group creator also injects a PSK derived from the resumption secret (from the existing group) into the key schedule. Thus, as per the "normal" group creation process, no other shared secrets are needed, excpet the PSK derived from the resumption secret for the step where the members are added. Regarding you most recent example: If I understand the scenario correctly, I believe you are not computing the resolution properly. The node sending the update would encrypt the path secret of node 3 to the resolution of the blank node. The resolution of a blank node is computed by taking the resolution of its two children, which in this case are both C and D. I hope that answered some of your questions. If you have questions regarding the draft in general, the mailing list is usually a better place to get answers. Also, if you're working on an implementation, there's a Wire group for discussions regarding implementation issues and interop between the individual implementations. See for the links. We're always happy to see new implementations!
By "two ratchets", I was referring to the secrets derived at the leaves of the secret tree... i.e. the final values computed by . There's one using a label of "application" and one using a label of "handshake". The former is ratcheted for application messages, and the latter for handshake messages. In the diagram I referenced above, the label switches to "secret"... probably from an earlier iteration of the doc. In any case, I don't see a reason to have two secrets here. It's more to maintain for little benefit. Simply shifting leaves and then truncating the right side of the tree is sufficient to properly remove any arbitrary leaf from the tree? I guess maybe that works with the resolution computation you described, but that could like unmerge half the leaves between the departing index and the end. For the AES-256-GCM authentication tag, for the library in URL, the tag is retrieved with a separate call after the cipher is finalized (), so that the ciphertext and the tag are two pieces of information that need to get formatted into a packet and transmitted. I understand that this is just one algorithm and others may only have the ciphertext. But for the ciphersuites that employ AES-256-GCM, two implementations might format the packets differently and be inoperable with each other. ..or there's already an agreed upon formatting that I don't know about. Moreover, the length of the tag is a configurable param for the algorithm, which should be specified somewhere. I get it now that recovery is just group creation using the same group ID with a resumption secret thrown in. I kind of like it being an External Init with the resumption secret thrown in. One message instead of N messages. Mailing lists are terrible at organizing information (not that I'm doing any better putting all my topics in one Issue).
Hi NAME , finally getting around to addressing these comments. Sorry it took a bit! Notes from a first read-through (before looking at the exchange with NAME Terminology between Ratchet Tree Evolution, Group Creation, Key Schedule - I think this has been sorted out by some more recent changes. Definition of - This doesn't go in {{ratchet-tree-evolution}}, it is defined in the commit generation / handling sections "secret" / "handshake" / "application" - Yes, this is intentional. The first derivation splits the application and handshake key chains, then both evolve via "secret" derivation GCM authentication tag - We assume the RFC 5116 AEAD interface, which has a unitary ciphertext, not a separate ciphertext+tag. Added a note to clarify. Group creation and external init parts each have discussions in two parts - Not immediately clear how to address this; will consider in a broader review. Two ratchets per leaf node - Assuming you're talking about the vs. distinction, this is to assure that each secret is only used with one algortihm. I have added a note to clarify. Easier to understand resolution as depth-first search - Added this to the description of resolution. "Truncate the tree..." - Yep, missing part of the sentence. Reworded. Resumption secret & ExternalInit - There's no connection between ExternalInit and resumption secrets (just ), and in any case, the only epoch for which an ExternalInit can be constructed is the current one. Otherwise, you're forking history. You're correct that the resumption secret is not sufficient to join a group, and it's not meant to be. It is only proof of prior involvement with the group. For members A, C, D, ... - Your representation of the secret knowledge here isn't quite right. C and D wouldn't know p[1], since it's not on their direct path. In your hypothetical, if A or B did an operation, they would encrypt to the resolution of node 5, that is to both C and D. This diagram seems useful for building intuition, though, so I've added something like it to the ratchet tree section. Notes on responses to NAME The separate application and handshake ratchet to ensure that application and handshake messages don't fate-share -- if the application keys are compromised, that doesn't compromise the handshake keys. While this might not matter much in practice, I believe it helps with the security analysis. Truncation is not how members are removed from the tree. That's done by blanking a member's leaf and parents. Truncation is just garbage collection. If you have a 1000-member group where leaf 1 is blank (starting at zero), and the member at leaf 2 removes members at leaves 3-1000, then you'll end up with a tree with three leaves (member A, blank, member B), and truncation will clear out the now-blank leaves 3-1000 and all their parents. Yeah, there's unfortunate divergence in AES-GCM interfaces. The good news is that even if an implementation produces a different layout, as long as you can find the tag, you can rearrange things to match the desired layout here (ciphertext tag).
The top of Sec. 5 has exactly one sentence before diving into tree terminology. Please explain at a high level what is the motivation for this structure and maybe how it fits into the rest of the protocol.
My 2 cents: Abstractly, a distributed cryptographic state (like the ratchet tree) consists of a set of secrets. Each user j knows some subset S(j) of those secrets. More generally a set of users U collectively knows secrets S(U). To get Post Compromise Security for the distributed state we need a way for each user j to re-sample all secrets in S(j) and distribute such that the following is true. Suppose an adversary knows secrets S(U) in the old crypto state for some set of users U. Then in the new state, after witnessing the distribution phase on the wire, the adversary only knows the secrets in S(U{i}). For example: Suppose I steal your secrets (i.e. you are user j and now i know S(j)). Now you re-sample & distribute. As a result I now know S(emptyset) = nothing at all. The session is secure again. Suppose I steal the keys of users 2, 4 and 7. So i know S({2,4,7}). Then after 7 re-samples & distributes I only know S({2,4}). Once 2 and 4 also re-sample and distribute I know nothing and the session is secure again. In a nutshell, the ratchet tree in MLS gives us a way to efficiently do this re-sampling & distribution for commitsecrets repeatedly, non-interactively and efficiently. That is by sending out a single, reasonably small packet. Of course, as the ratchet tree adds more secrets to MLS distributed state we then have to be able to re-sample & distribute those as well which the ratchet tree lets us do. But at the end of the day, its all about distributing new commitsecrets.
LGTM. Thanks!
NAME - I put my own spin on this in . Let me know if that looks good to you!
NAME Absolutely. Thank you!

5. The protocol uses "ratchet trees" for deriving shared secrets among a  <ins> group of clients.  A ratchet tree is an arrangement of secrets and key pairs among the members of a group in a way that allows for secrets to be efficiently updated to reflect changes in the group. The unique power of a ratchet tree is that it allows a group to efficiently remove a member, because it allows a group member to efficiently encrypt new entropy to a subset of the group.  A ratchet tree assigns shared keys to subgroups of the overall group, so that, for example, encrypting to all but one member of the group requires only log(N) encryptions, instead of the N-1 encryptions that would be needed to encrypt to each participant individually (where N is the number of members in the group). This remove operation allows MLS to efficiently achieve post- compromise security.  In an Update proposal or a full Commit message, an old, possibly compromised representation of a member is effeciently removed from the group and replaced with a freshly generated instance. </ins> 5.1. 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 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 "merged" as they receive the private keys for nodes, as described in ratchet-tree-evolution. <ins> For example, consider a four-member group (A, B, C, D) where the node above the right two members is blank.  (This is what it would look like if A created a group with B, C, and D.)  Then the public state of the tree and the views of the private keys of the tree held by each participant would be as follows, where "_" represents a blank node, "?" represents an unknown private key, and "pk(X)" represents the public key corresponding to the private key "X": Note how the tree invariant applies: Each member knows only their own leaf, and the private key AB is known only to A and B. </ins> 5.4. A member of an MLS group advances the key schedule to provide forward secret is used to derive a new secret value for the corresponding node, from which the node's key pair is derived. <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 as an input to DeriveSecret and the node secret is used as an input to DeriveKeyPair. </ins> For example, suppose there is a group with four members, with C an unmerged leaf at node 5: MLS uses HPKE for public-key encryption I-D.irtf-cfrg-hpke.  The "DeriveKeyPair" function associated to the KEM for the ciphersuite  <ins> maps octet strings to HPKE key pairs.  As in HPKE, MLS assumes that an AEAD algorithm produces a single ciphertext output from AEAD encryption (aligning with RFC5116), as opposed to a separate ciphertext and tag. </ins> Ciphersuites are represented with the CipherSuite type.  HPKE public keys are opaque values in a format defined by the underlying protocol Blank the intermediate nodes along the path from "removed_index" to the root  <ins> Truncate the tree by removing leaves from the right side of the tree until the rightmost leaf node is not blank. </ins> 11.1.4.


as follows I-D.irtf-cfrg-hpke RFC8446: The hash used for the MLS transcript hash is the one referenced in <del> the ciphersuite name.  In the ciphersuites defined above, "SHA256" and "SHA512" refer to the SHA-256 and SHA-512 functions defined in SHS. </del>  It is advisable to keep the number of ciphersuites low to increase the chances clients can interoperate in a federated environment, <del> therefore the ciphersuites only inlcude modern, yet well-established </del>  algorithms.  Depending on their requirements, clients can choose between two security levels (roughly 128-bit and 256-bit).  Within the security levels clients can choose between faster X25519/X448

I suggest that we consider including a ciphersuite that enables AES256 GCM with SHA384 and that for signatures ECDSA with P-384 (and SHA-384) is also permitted. The NSA restricts commercial applications to certain algorithms for applications that are used in or in coordination with the U.S. federal government and, from a general availability perspective, adding this could expand protocol use potential.
I presume you're thinking of here? I realize that the table specifies P-384 and SHA-384; is it your understanding that it forbids using bigger curves/hashes?
Both Suite B and alignment to the . The other options currently in the spec, size accounted for, do not align to requirements.
You didn't answer my question about forbidding the use of bigger curves/hashes. Would the cases you're interested in be covered by the AES-256 / SHA-512 / P-521 suite?
If this is needed for the reasons mentioned in the issue, I have no objections.

as follows I-D.irtf-cfrg-hpke RFC8446: The hash used for the MLS transcript hash is the one referenced in  <ins> the ciphersuite name.  In the ciphersuites defined above, "SHA256", "SHA384", and "SHA512" refer to the SHA-256, SHA-384, and SHA-512 functions defined in SHS. </ins> It is advisable to keep the number of ciphersuites low to increase the chances clients can interoperate in a federated environment,  <ins> therefore the ciphersuites only include modern, yet well-established </ins> algorithms.  Depending on their requirements, clients can choose between two security levels (roughly 128-bit and 256-bit).  Within the security levels clients can choose between faster X25519/X448