5.2. <del> A particular instance of a ratchet tree is based on the following cryptographic primitives, defined by the ciphersuite in use: </del>  <del> An HPKE ciphersuite, which specifies a Key Encapsulation Mechanism (KEM), an AEAD encryption scheme, and a hash function </del>  <del> A Derive-Key-Pair function that produces an asymmetric key pair for the specified KEM from a symmetric secret </del>  Each node in a ratchet tree contains up to five values: Each MLS session uses a single ciphersuite that specifies the following primitives to be used in group key computations: <del> A hash function </del>  <del> A key encapsulation mechanism (KEM) </del>  <del> An AEAD encryption algorithm </del>  A signature algorithm <del> The ciphersuite's KEM used to instantiate an HPKE I-D.irtf-cfrg-hpke instance for the purpose of public-key encryption.  Each ciphersuite has a "Derive-Key-Pair" function that maps octet strings of length "Nsk" (a KEM-specific constant defined by HPKE) to HPKE key pairs. This function is defined to be HPKE's "DeriveKeyPair" function. </del>  Ciphersuites are represented with the CipherSuite type.  HPKE public keys are opaque values in a format defined by the underlying protocol 7.8. <del> Group keys are derived using the HKDF-Extract and HKDF-Expand functions as defined in RFC5869, as well as the functions defined below: </del>  <del> The Hash function used by HKDF is the ciphersuite hash algorithm. Hash.length is its output length in bytes.  In the below diagram: </del>  <del> HKDF-Extract takes its salt argument from the top and its IKM </del>  argument from the left <del> Derive-Secret takes its Secret argument from the incoming arrow </del>  When processing a handshake message, a client combines the following information to derive new epoch secrets: each key/nonce pair MUST NOT be used to encrypt more than one message. <del> Keys, nonces and secrets of ratchets are derived using Derive-App- Secret.  The context in a given call consists of the index of the sender's leaf in the ratchet tree and the current position in the ratchet.  In particular, the index of the sender's leaf in the </del>  ratchet tree is the same as the index of the leaf in the AS Tree used to initialize the sender's ratchet. The context used for the derivation of the "exported_value" MAY be empty while each application SHOULD provide a unique label as an <del> input of the HKDF-Expand-Label for each use case.  This is to prevent </del>  two exported outputs from being generated with the same values and used for different functionalities. the definition of application_secret.) The secret of any other node in the tree is derived from its parent's <del> secret using a call to Derive-App-Secret. </del>  If N is a node index in the AS Tree then the secrets of the children of N are defined to be: S was used to encrypt or (successfully) decrypt a message, or if a key, nonce, or secret derived from S has been consumed.  (This <del> goes for values derived via Derive-Secret as well as HKDF-Expand- Label.) </del>  Here, S may be the "init_secret", "commit_secret", "epoch_secret", "application_secret" as well as any secret in the AS Tree or one of These ciphersuites map to HPKE primitives and TLS signature schemes as follows I-D.irtf-cfrg-hpke RFC8446: <del> The hash used for HKDF computations in MLS is the same hash used in the HPKE KDF for the ciphersuite. </del> It is advisable to keep the number of ciphersuites low to increase the chances clients can interoperate in a federated environment, therefore the ciphersuites only inlcude modern, yet well-established

In , we changed from using HKDF to using the KDF from HPKE, which I thought removed the need for the ciphersuite to specify a hash function. As a result, ciphersuites no longer needed to specify a hash. However, the transcript computations still have a raw invocation, not a KDF invocation. So we need to either: Change the transcript hash computations to use the KDF, or Clarify that ciphersuites still specify a hash function (as before)
What about the call in the and the "hashes" for ?
Our HPKE dependency provides a perfectly usable KDF. We should just use that instead of nailing a dependency to HKDF. This will mainly be a matter of changing and calls to the corresponding calls on the HPKE KDF.
I can't wait to see the implementations.
Thanks for the enthusiasm, but I don't think this is a spec issue :)

5.2.  <ins> A particular instance of a ratchet tree is defined by the same parameters that define an instance of HPKE, namely: </ins>  <ins> A Key Encapsulation Mechanism (KEM), including a "DeriveKeyPair" function that creates a key pair for the KEM from a symmetric secret </ins>  <ins> A Key Derivation Function (KDF), including "Extract" and "Expand" functions An AEAD encryption scheme </ins> Each node in a ratchet tree contains up to five values: Each MLS session uses a single ciphersuite that specifies the following primitives to be used in group key computations:  <ins> HPKE parameters: A Key Encapsulation Mechanism (KEM) </ins>  <ins> A Key Derivation Fucntion (KDF) </ins>  <ins> An AEAD encryption algorithm </ins> A signature algorithm  <ins> The HPKE parameters are used to instantiate HPKE I-D.irtf-cfrg-hpke for the purpose of public-key encryption.  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 7.8.  <ins> Group keys are derived using the "Extract" and "Expand" functions from the KDF for the group's ciphersuite, as well as the functions defined below: </ins>  <ins> The value "KDF.Nh" is the size of an output from "KDF.Extract", in bytes.  In the below diagram: </ins>  <ins> KDF.Extract takes its salt argument from the top and its IKM </ins> argument from the left  <ins> DeriveSecret takes its Secret argument from the incoming arrow </ins> When processing a handshake message, a client combines the following information to derive new epoch secrets: each key/nonce pair MUST NOT be used to encrypt more than one message.  <ins> Keys, nonces and secrets of ratchets are derived using DeriveAppSecret.  The context in a given call consists of the index of the sender's leaf in the ratchet tree and the current position in the ratchet.  In particular, the index of the sender's leaf in the </ins> ratchet tree is the same as the index of the leaf in the AS Tree used to initialize the sender's ratchet. The context used for the derivation of the "exported_value" MAY be empty while each application SHOULD provide a unique label as an  <ins> input of the ExpandWithLabel for each use case.  This is to prevent </ins> two exported outputs from being generated with the same values and used for different functionalities. the definition of application_secret.) The secret of any other node in the tree is derived from its parent's  <ins> secret using a call to DeriveAppSecret. </ins> If N is a node index in the AS Tree then the secrets of the children of N are defined to be: S was used to encrypt or (successfully) decrypt a message, or if a key, nonce, or secret derived from S has been consumed.  (This  <ins> goes for values derived via DeriveSecret as well as ExpandWithLabel.) </ins> Here, S may be the "init_secret", "commit_secret", "epoch_secret", "application_secret" as well as any secret in the AS Tree or one of These ciphersuites map to HPKE primitives and TLS signature schemes as follows I-D.irtf-cfrg-hpke RFC8446:  It is advisable to keep the number of ciphersuites low to increase the chances clients can interoperate in a federated environment, therefore the ciphersuites only inlcude modern, yet well-established


the signature computation comprises all of the fields except for the signature field. <del> KeyPackage objects MUST contain at least three extensions, one of type "supported_versions", one of type "supported_ciphersuites", and one of type "lifetime".  The "supported_versions" and "supported_ciphersuites" extensions allow MLS session establishment to be safe from downgrade attacks on these two parameters (as discussed in group-creation), while still only advertising one version / ciphersuite per KeyPackage. </del>  As the "KeyPackage" is a structure which is stored in the Ratchet Tree and updated depending on the evolution of this tree, each 7.1. <del> The "supported_versions" extension contains a list of MLS versions that are supported by the client.  The "supported_ciphersuites" extension contains a list of MLS ciphersuites that are supported by the client. </del>  <del> These extensions MUST be always present in a KeyPackage. </del>  7.2. Fetch KeyPackages for the members to be added, and selects a version and ciphersuite according to the capabilities of the members.  To protect against downgrade attacks, the creator MUST <del> use the "supported_versions" and "supported_ciphersuites" fields in these KeyPackages to verify 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 Hash.length): KP: KeyPackage messages <del> W: Welcome messages </del>  Recommended: Whether support for this extension is recommended by the IETF MLS WG.  Valid values are "Y" and "N".  The "Recommended"

This PR makes a few minor fixes to extensions: Coalesces and into a single extension Adds a list of supported extensions in parallel with the versions and ciphersuites Adds a missing value to the IANA table for the extension

the signature computation comprises all of the fields except for the signature field.  <ins> KeyPackage objects MUST contain at least two extensions, one of type "capabilities", and one of type "lifetime".  The "capabilities" extension allow MLS session establishment to be safe from downgrade attacks on the parameters described (as discussed in group-creation), while still only advertising one version / ciphersuite per KeyPackage. </ins> As the "KeyPackage" is a structure which is stored in the Ratchet Tree and updated depending on the evolution of this tree, each 7.1.  <ins> The "capabilities" extension indicates what protocol versions, ciphersuites, and protocol extensions are supported by a client. </ins>  <ins> This extension MUST be always present in a KeyPackage.  Extensions that appear in the "extensions" field of a KeyPackage MUST be included in the "extensions" field of the "capabilities" extension. </ins> 7.2. Fetch KeyPackages for the members to be added, and selects a version and ciphersuite according to the capabilities of the members.  To protect against downgrade attacks, the creator MUST  <ins> use the "capabilities" extensions in these KeyPackages to verify 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 (where "0" represents an all-zero vector of size Hash.length): KP: KeyPackage messages  <ins> GI: GroupInfo objects </ins> Recommended: Whether support for this extension is recommended by the IETF MLS WG.  Valid values are "Y" and "N".  The "Recommended"


5.4. <del> When performing a Commit, the leaf KeyPackage of the committer and its direct path to the root are updated with new secret values.  The HPKE leaf public key within the KeyPackage MUST be a freshly generated value to provide post-compromise security. The generator of the Commit starts by using the HPKE secret key "leaf_hpke_secret" associated with the new leaf KeyPackage (see key- packages) to compute "path_secret[0]" and generate a sequence of "path secrets", one for each ancestor of its leaf.  That is, path_secret[0] is used for the node directly above the leaf, </del>  path_secret[1] for its parent, and so on.  At each step, the path secret is used to derive a new secret value for the corresponding node, from which the node's key pair is derived. For example, suppose there is a group with four members: If member B subsequently generates a Commit based on a secret <del> "leaf_hpke_secret", then it would generate the following sequence of path secrets: </del>  After the Commit, the tree will have the following structure, where "np[i]" represents the node_priv values generated as described above:  5.5. The members of the group need to keep their views of the tree in sync

This PR tries to clarify these paragraphs by the following: Differentiating between HPKE public/private keys when describing how these keys are rotated for a commit Clarifying who does the actions associated with a commit Adding consistency fixes between the two paragraphs Let me know if these fixes are not quite right, as this PR tries to clarify where I had questions when reading.

5.4.  <ins> When performing a Commit, the generator of the Commit updates its leaf KeyPackage and its direct path to the root with new secret values.  The HPKE leaf public key within the KeyPackage MUST be derived from a freshly generated HPKE secret key to provide post- compromise security. The generator of the Commit starts by sampling a fresh random value called "leaf_secret", and uses the leaf_secret to generate their leaf HPKE key pair (see key-packages) and to seed a sequence of "path secrets", one for each ancestor of its leaf.  In this setting, path_secret[0] refers to the node directly above the leaf, </ins> path_secret[1] for its parent, and so on.  At each step, the path secret is used to derive a new secret value for the corresponding node, from which the node's key pair is derived. For example, suppose there is a group with four members: If member B subsequently generates a Commit based on a secret  <ins> "leaf_secret", then it would generate the following sequence of path secrets: </ins> After the Commit, the tree will have the following structure, where "np[i]" represents the node_priv values generated as described above: <ins> After performing these operations, the generator of the Commit MUST delete the leaf_secret. </ins> 5.5. The members of the group need to keep their views of the tree in sync


5.5. <del> The members of the group need to keep their views of the tree in sync and up to date.  When a client commits a change to the tree (e.g., to add or remove a member), it transmits a handshake message containing a set of public values for intermediate nodes in the direct path of a leaf.  The other members of the group can use these public values to update their view of the tree, aligning their copy of the tree to the sender's. </del>  To perform an update for a path (a Commit), the sender broadcasts to the group the following information for each node in the direct path For example, in order to communicate the example update described in the previous section, the sender would transmit the following values: <del> In this table, the value pk(X) represents the public key derived from the node secret X.  The value E(K, S) represents the public-key encryption of the path secret S to the public key K. </del>  6.

This PR does the following: Adds a lead-in sentence describing how this section builds on the prior. Differentiates between public keys for nodes and public keys for leaves (users) Clarifies when secret key material needs to be deleted. I also wanted to make a couple other changes, but wanted feedback first. This section describes the private value corresponding to a node's public key as 'ns[0]' (or node secret), whereas the prior section referred to it as 'np[0]' (or node_priv). It would be better to have these terms be consistent. On line 773, it says that the client "...transmits a handshake message containing a set of public values" and on line 776 it says that "other members of the group can use these public values to update". However, this values seems to be contradictory with the value listed on line 785. The current terminology is unclear regarding 1) a Commit to the tree, 2) an update to the tree, and and 3) a handshake message. I think these are very similar but using the same language consistently would be better (such as defining a Commit as what updates the tree, and then describing how the handshake message is different from a Commit message, which is described earlier in the spec).
Thanks for looking through this, NAME There's a lot of historical baggage here, so it's good to get it cleaned up. I'll review the diff in a moment, but to respond to your points: Just to confirm terminology here, there are three values in play, (1) a symmetric secret, (2) a KEM private key derived from the secret, and (3) the corresponding public key. It should be the case that you only need to store the latter two in the tree (priv+pub), since the secrets are used transiently: For deriving the tree key pairs, for deriving the commit secret, and for initializing new members added in an epoch. stores the node secrets to simplify these operations, but it feels like it should be possible to arrange things so that this isn't necessary. So I think the right answer here is to just refer to node private keys. Line 785 is correct, the Commit contains a public key and encrypted copies of the path secret for each node. So the text should refer to public and encrypted private values as necessary. The right taxonomy here is: "Handshake message" encompasses and . Processing the Commit is the thing that causes an update to the tree; more specifically, applying a DirectPath. So we should only talk about handshake messages when we want to cover both Proposals and Commits, and most instances of "update" should probably refer to either Commit or DirectPath.

5.5.  <ins> After generating a Commit as described in the prior section, the generator of the Commit must broadcast this update to other members of the group, who apply it to keep their local views of the tree in sync with the sender's.  When a client commits a change to the tree (e.g., to add or remove a member), it transmits a handshake message containing a set of public values for intermediate nodes in the direct path of a leaf.  The other members of the group can use these public values to update their view of the tree, aligning their copy of the tree to the sender's. </ins> To perform an update for a path (a Commit), the sender broadcasts to the group the following information for each node in the direct path For example, in order to communicate the example update described in the previous section, the sender would transmit the following values:  <ins> In this table, the value pk(ns[X]) represents the public key derived from the node secret X, whereas pk(X) represents the public leaf key for user X.  The value E(K, S) represents the public-key encryption of the path secret S to the public key K. After processing the update, each recipient MUST delete outdated key material, specifically: The path secrets used to derive each updated node key pair. Each outdated node key pair that was replaced by the update. </ins> 6.


of the group, who apply it to keep their local views of the tree in sync with the sender's.  When a client commits a change to the tree (e.g., to add or remove a member), it transmits a handshake message <del> containing a set of public values for intermediate nodes in the direct path of a leaf.  The other members of the group can use these public values to update their view of the tree, aligning their copy of the tree to the sender's. </del>  To perform an update for a path (a Commit), the sender broadcasts to the group the following information for each node in the direct path

This PR adds a fix mentioned in but was not included in the PR that was eventually merged. Specifically, both public and encrypted private values are sent in a message containing key material to ratchet the tree forward.

of the group, who apply it to keep their local views of the tree in sync with the sender's.  When a client commits a change to the tree (e.g., to add or remove a member), it transmits a handshake message  <ins> 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 can use these values to update their view of the tree, aligning their copy of the tree to the sender's. </ins> To perform an update for a path (a Commit), the sender broadcasts to the group the following information for each node in the direct path


Interim transcript hash: 0 <del> Init secret: 0 </del>  For each member, construct an Add proposal from the KeyPackage for that member (see add)

This suggestion is based on a discussion in the MLS mailing list . It prevents issues that currently arrise when the very first commit of a group is an "add-only" commit because both the and would be 0. Let me know if the wording or anything else should be changed. /cc NAME URL
I believe this change invalidates the last paragraph of the , as it is impossible to verify the group creation process for the joiner without knowing the random that the group creator used.
maybe one alternative is to keep the zero , but make it compulsory to populate the path for this group creation welcome/commit (so that isn't zero)?
That would also work NAME (I also suggested that as an alternative in my original report in the mailing list), to me it is a little bit less elegant though. Personally, I also wonder why it is necessary to verify that group creation just happened. (Moreover, this way of recognizing group creation would prevent any group from which all but one person left from continuing, which my be confusing to end-users)
lgtm with one minor thing fixed Hi Eric, Thanks for the analysis, and the detailed, clear writeup. I agree that there are some real issues here, and with the solution approach. Just to align on a couple of details: is clearly a problem. Good catch. is less clear to me. You lose me when you say ""add-only" commits allow new group members (ONLY) to learn the value at the root of the TreeKEM tree". How do you think this happens? As I understand the spec, this is not the case. New members are added to the "unmergedleaves" array at the root, but they are not told the root secret or root private key. In any case, I agree that we should change the initial initsecret from being zero to being random. I think I had set it to zero out of a general caution against requiring fresh entropy, and likely under the theory that the first commit would change it (before was optional). But it is clearly necessary now. If you would like to send a PR, I would be happy to review. Thanks, --Richard >

Interim transcript hash: 0  <ins> Init secret: a fresh random value of size "KDF.Nh" </ins> For each member, construct an Add proposal from the KeyPackage for that member (see add)


members to incorporate a Commit message into the transcript without having to store the whole MLSPlaintextCommitAuthData structure. <del> When a new group is created, the "interim_transcript_hash" field is set to the zero-length octet string. </del>  7.7. These ciphersuites map to HPKE primitives and TLS signature schemes as follows I-D.irtf-cfrg-hpke RFC8446:  It is advisable to keep the number of ciphersuites low to increase the chances clients can interoperate in a federated environment, therefore the ciphersuites only inlcude modern, yet well-established

Addresses ambiguity pointed out on the mailing list by NAME
In , we changed from using HKDF to using the KDF from HPKE, which I thought removed the need for the ciphersuite to specify a hash function. As a result, ciphersuites no longer needed to specify a hash. However, the transcript computations still have a raw invocation, not a KDF invocation. So we need to either: Change the transcript hash computations to use the KDF, or Clarify that ciphersuites still specify a hash function (as before)
What about the call in the and the "hashes" for ?

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 string. </ins> 7.7. These ciphersuites map to HPKE primitives and TLS signature schemes as follows I-D.irtf-cfrg-hpke RFC8446: <ins> The hash used for the MLS transcript hash is the one referenced in the ciphersuite name.  In the ciphersuites defined above, "SHA256" and "SHA512" refer to the SHA-256 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, therefore the ciphersuites only inlcude modern, yet well-established


Commit message. An Add is applied after being included in a Commit message.  The <del> position of the Add in the list of adds determines the leaf index "index" where the new member will be added.  For the first Add in the Commit, "index" is the leftmost empty leaf in the tree, for the second Add, the next empty leaf to the right, etc. </del>  If necessary, extend the tree to the right until it has at least index + 1 leaves the sender of the proposal SHOULD retransmit it in the new epoch. A member of the group MAY send a Commit that references no proposals <del> at all, which would thus have empty vectors for "updates", "removes", and "adds".  Such a Commit resets the sender's leaf and the nodes along its direct path, and provides forward secrecy and post- compromise security with regard to the sender of the Commit.  An Update proposal can be regarded as a "lazy" version of this operation, where only the leaf changes and intermediate nodes are blanked out. </del>  The "path" field of a Commit message MUST be populated if the Commit <del> covers at least one Update or Remove proposal, i.e., if the length of the "updates" or "removes" vectors is greater than zero.  The "path" field MUST also be populated if the Commit covers no proposals at all (i.e., if all three proposal vectors are empty).  The "path" field MAY be omitted if the Commit covers only Add proposals.  In pseudocode, the logic for whether the "path" field is required is as follows: </del>  To summarize, a Commit can have three different configurations, with different uses: A member of the group creates a Commit message and the corresponding Welcome message at the same time, by taking the following steps: <del> Construct an initial Commit object with "updates", "removes", and "adds" fields populated from Proposals received during the current epoch, and empty "key_package" and "path" fields. </del>  Generate a provisional GroupContext object by applying the <del> proposals referenced in the initial Commit object in the order provided, as described in proposals.  First the list of update proposals, then the list of remove proposals, and last the list of add proposals.  Add proposals are applied left to right: Each Add proposal is applied at the leftmost unoccupied leaf, or appended to the right edge of the tree if all leaves are occupied. </del>  Decide whether to populate the "path" field: If the "path" field is required based on the proposals that are in the commit (see tree indicated by the "sender" field. Generate a provisional GroupContext object by applying the <del> proposals referenced in the commit object in the order provided, as described in proposals.  First the list of update proposals, then the list of remove proposals, and last the list of add proposals.  Add proposals are applied left to right: Each Add proposal is applied at the leftmost unoccupied leaf, or appended to the right edge of the tree if all leaves are occupied. Verify that the "path" value is populated if either of the "updates" or "removes" vectors has length greater than zero, or if all of the "updates", "removes", and "adds" vectors are empty. </del>  Otherwise, the "path" value MAY be omitted. If the "path" value is populated: Process the "path" value using

In principle, the set of ProposalType values is extensible. But with the current Commit syntax, there is no way for a Commit to reference a Proposal with a type other than Add/Update/Remove. We could enable this by adding a field to the commit that would carry such references: However, if other proposal types had sequencing requirements, they would not get the benefit of explicit, separate grouping and ordering as is done for the current proposal types. If we don't extend the Commit in this way, we should add some explanatory text to clarify that new proposal types require an extension that changes the format of the Commit message.
Personally, I lean toward the "do nothing" option here. If we take the view that proposals should describe modifications to the tree (other actions being signaled in ), then Add, Update, and Remove are pretty much the available atomic operations. You could envision some minor variations (an Add with a specified location, say) or compositions (Resync = Remove+Add), but nothing is coming to my mind that would be important enough to merit the extra complexity.
Fixed by
[This issue is part of a feature breakdown based on ] Applications might want to signal to group members processing a commit that they should take certain actions in addition to ratcheting the group forward. The most obvious example is folding in a PSK for the epoch, but you can also imagine addressing things like transcript consistency or updatable PKE through this channel. This should be straightforward syntactically, but will also require updating the extensibility section to make clear that Commit extensions can also in principle affect the GroupContext extensions. In other words: Each group member associates a list of extensions with the group, which are confirmed via the GroupContext New joiners get configured with these via extensions in the GroupInfo Existing members can find out about changes to the group's extensions via extensions in the Commit
I think making commit messages extensible is a great idea. However, I'd like to understand better what we want to use the extensions first. I thought that was what Proposals are for. I think for mechanisms that are part of the base protocol spec, there is no reason not to extend the list of proposals. Regarding the nature of extensions: Do I understand correctly, that an extension is a property of the group first and foremost? And extensions in the commit are meant to alert other group members to changes in those properties? Or are extensions in the commit messages a mechanism to allow for the inclusion of additional data in the commits? Or both? I feel like the meaning of the word "extension" is a little stretched here. Since we're discussing this also in the context of PSK injections: Do you think PSK injections should be something that can be switched on and off for any given group? If so, why? I would consider it part of the base protocol and do not see what benefits we get from disallowing it in a group.
It's a fair point, that we have basically two extension points here -- proposal types and extensions. I think the theory I had in mind was something like: Proposals describe modifications you make to the tree before you use it for TreeKEM (to get the path) (if you do) The DirectPath describes the outcome of TreeKEM.Encap Extensions tell you anything else you need to do If that philosophy makes sense, I can update the PR to make that clearer. Note that proposal extensibility is currently broken because we don't actually have a way for the Commit to include any Proposals with new proposal types. I've filed for that.
On the nature of extensions: My thinking here is that an "extension" is purely syntactic; its semantics are defined entirely by its ExtensionType. One thing that extensions do (in the context of GroupContext) is express what attributes/parameters the group has agreed to. But they can also do other things.
On PSK injections: My thinking here is that PSKs are a per-epoch decision, in that you can inject a different set of PSKs at each epoch. Since the Commit is the thing that starts a new epoch, it signals what PSKs to use for that epoch. A few illustrative cases: A "meeting password" might get injected on every commit ... or you might only inject it in commits where you have an Add, to verify that the new guy has it In a scenario where you have some OOB PSK distribution (say to add more PQ safety), you would inject a PSK every epoch, but a different one in each epoch When re-starting a group or branching a subgroup, you would incorporate the resumption secret from the previous group, but then might not need a PSK ever again.
I see, thanks for the clarification. My understanding of extensions was that their advantage is that they're dynamic and can be defined and used after MLS is defined, i.e. to keep the standard extensible. I know that in the TLS context, extensions are used for all sorts of basic protocol behaviour, but my understanding was that was more something that has evolved over time rather than it being "by-design". Is there any reason for Proposals to be limited to TreeKEM operations (other than that being their historic purpose)? Because otherwise it seems a little odd to me not to use them for core MLS operations such as PSK injection.
Otherwise, I agree with your take on PSK injections. They should definitely be part of the commit and it should be possible to inject them ad-hoc. Although for the re-starting or branching use-case, I think they should be part of the Welcome message, but I think we're having that discussion on the mailing list already.
This was discussed at the interim on 2020-08-25. The feeling of the group was that Proposals should be a sufficient extension mechanism for Commits, so we don't need this if we And since is about to do that, closing this issue.
Two small things, otherwise lgtm.

Commit message. An Add is applied after being included in a Commit message.  The  <ins> position of the Add in the list of proposals determines the leaf index "index" where the new member will be added.  For the first Add in the Commit, "index" is the leftmost empty leaf in the tree, for the second Add, the next empty leaf to the right, etc. </ins> If necessary, extend the tree to the right until it has at least index + 1 leaves the sender of the proposal SHOULD retransmit it in the new epoch. A member of the group MAY send a Commit that references no proposals  <ins> at all, which would thus have an empty "proposals" vector.  Such a Commit resets the sender's leaf and the nodes along its direct path, and provides forward secrecy and post-compromise security with regard to the sender of the Commit.  An Update proposal can be regarded as a "lazy" version of this operation, where only the leaf changes and intermediate nodes are blanked out. </ins> The "path" field of a Commit message MUST be populated if the Commit  <ins> covers at least one Update or Remove proposal.  The "path" field MUST also be populated if the Commit covers no proposals at all (i.e., if the proposals vector is empty).  The "path" field MAY be omitted if the Commit covers only Add proposals.  In pseudocode, the logic for validating a Commit is as follows: </ins> To summarize, a Commit can have three different configurations, with different uses: A member of the group creates a Commit message and the corresponding Welcome message at the same time, by taking the following steps:  <ins> Construct an initial Commit object with the "proposals" field populated from Proposals received during the current epoch, and empty "key_package" and "path" fields. </ins> Generate a provisional GroupContext object by applying the  <ins> proposals referenced in the initial Commit object, as described in proposals.  Update proposals are applied first, followed by Remove proposals, and then finally Add proposals.  Add proposals are applied in the order listed in the "proposals" vector, and always to the leftmost unoccupied leaf in the tree, or the right edge of the tree if all leaves are occupied. Note that the order in which different types of proposals are applied should be updated by the implementation to include any new proposals added by negotiated group extensions. </ins> Decide whether to populate the "path" field: If the "path" field is required based on the proposals that are in the commit (see tree indicated by the "sender" field. Generate a provisional GroupContext object by applying the  <ins> proposals referenced in the initial Commit object, as described in proposals.  Update proposals are applied first, followed by Remove proposals, and then finally Add proposals.  Add proposals are applied in the order listed in the "proposals" vector, and always to the leftmost unoccupied leaf in the tree, or the right edge of the tree if all leaves are occupied. Note that the order in which different types of proposals are applied should be updated by the implementation to include any new proposals added by negotiated group extensions. Verify that the "path" value is populated if the "proposals" vector contains any Update or Remove proposals, or if it's empty. </ins> Otherwise, the "path" value MAY be omitted. If the "path" value is populated: Process the "path" value using


Thus the "confirmed_transcript_hash" field in a GroupContext object represents a transcript over the whole history of MLSPlaintext Commit <del> messages, up to the confirmation field in the current MLSPlaintext message.  The confirmation and signature fields are then included in the 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 Construct an MLSPlaintext object containing the Commit object. Use the "commit_secret" to advance the key schedule and compute <del> the "confirmation" value in the MLSPlaintext.  Sign the </del>  MLSPlaintext using the current epoch's GroupContext as context. Update the tree in the provisional state by applying the direct Group ID, epoch, tree, confirmed transcript hash, and interim transcript hash from the new state <del> The confirmation from the MLSPlaintext object </del>  Sign the GroupInfo using the member's private signing key and derived secrets for the new epoch. Use the "confirmation_key" for the new epoch to compute the <del> confirmation MAC for this message, as described below, and verify that it is the same as the "confirmation" field in the </del>  MLSPlaintext object. If the above checks are successful, consider the updated GroupContext object as the current state of the group. <del> The confirmation value confirms that the members of the group have arrived at the same state of the group: HMAC RFC2104 uses the Hash algorithm for the ciphersuite in use. </del>  10.2.1. Set the confirmed transcript hash in the new state to the value of the "confirmed_transcript_hash" in the GroupInfo. <del> Verify the confirmation MAC in the GroupInfo using the derived </del>  confirmation key and the "confirmed_transcript_hash" from the GroupInfo.

This PR gets rid of the explicit HMAC reference and uses KDF.Extract instead (which internally uses HMAC). Also is renamed to .
I don't love the extra , but I can live with it.

Thus the "confirmed_transcript_hash" field in a GroupContext object represents a transcript over the whole history of MLSPlaintext Commit  <ins> messages, up to the confirmation tag field in the current MLSPlaintext message.  The confirmation tag and signature fields are then included in the 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. </ins> As shown above, when a new group is created, the "interim_transcript_hash" field is set to the zero-length octet Construct an MLSPlaintext object containing the Commit object. Use the "commit_secret" to advance the key schedule and compute  <ins> the "confirmation_tag" value in the MLSPlaintext.  Sign the </ins> MLSPlaintext using the current epoch's GroupContext as context. Update the tree in the provisional state by applying the direct Group ID, epoch, tree, confirmed transcript hash, and interim transcript hash from the new state  <ins> The confirmation_tag from the MLSPlaintext object </ins> Sign the GroupInfo using the member's private signing key and derived secrets for the new epoch. Use the "confirmation_key" for the new epoch to compute the  <ins> confirmation tag for this message, as described below, and verify that it is the same as the "confirmation_tag" field in the </ins> MLSPlaintext object. If the above checks are successful, consider the updated GroupContext object as the current state of the group.  <ins> The confirmation tag value confirms that the members of the group have arrived at the same state of the group: </ins> 10.2.1. Set the confirmed transcript hash in the new state to the value of the "confirmed_transcript_hash" in the GroupInfo.  <ins> Verify the confirmation tag in the GroupInfo using the derived </ins> confirmation key and the "confirmed_transcript_hash" from the GroupInfo.


KeyPackage, the leaf secret from which the Commit is built, and the intermediate key pairs along the direct path to the root. <del> A new member receiving a Welcome message can recognize group creation if the number of entries in the "members" array is equal to the number of leaves in the tree minus one.  A client receiving a Welcome message SHOULD verify whether it is a newly created group, and if so, SHOULD verify that the above process was followed by reconstructing the Add and Commit messages and verifying that the resulting transcript hashes and epoch secret match those found in the Welcome message. </del> 10. Over the lifetime of a group, its membership can change, and existing

As discussed on this removes the paragraph in the Group Creation section specifying that joiners SHOULD verify group creation if it is detected. A recent change to group creation - namely a random initial - makes it impossible to verify group creation.
URL Hi Eric, I agree with (2). I think the stuff that's in the spec right now about verifying group creation is mainly just a legacy of the fact that we used to have separate Init and Add operations. When those got coalesced into Welcome, it must have seemed appealing to enable recipients to continue to distinguish the cases. But as you point out, that's not really tenable. So please feel free to send a PR! Thanks, --Richard Cornelissen Eric wrote:

KeyPackage, the leaf secret from which the Commit is built, and the intermediate key pairs along the direct path to the root.  10. Over the lifetime of a group, its membership can change, and existing


9.1. <del> The "signature" field in an MLSPlaintext object is computed using the signing private key corresponding to the credential at the leaf in the tree indicated by the sender field.  The signature covers the plaintext metadata and message content, which is all of MLSPlaintext except for the "signature" field.  The signature also covers the GroupContext for the current epoch, so that signatures are specific to a given group and epoch. </del>  9.2. The "ciphertext" field of the MLSCiphertext object is produced by supplying the inputs described below to the AEAD function specified <del> by the ciphersuite in use.  The plaintext input contains content and signature of the MLSPlaintext, plus optional padding.  These values are encoded in the following form: </del>  The key and nonce used for the encryption of the message depend on the content type of the message.  The sender chooses the handshake Initial contents: <del> These ciphersuites map to HPKE primitives and TLS signature schemes </del>  as follows I-D.irtf-cfrg-hpke RFC8446: The hash used for the MLS transcript hash is the one referenced in

that MLSPlaintext does not currently authenticate the sender's membership on the group, and as a result, some important attacks arise. Discussion on the list indicated that adding a MAC with a key derived from the key schedule would provide this authentication. This PR implements that suggestion.
Lgtm. Rebase and I'll properly approve.
Generally not a fan of rebasing, but here you go.
From NAME ... This would be a useful terminology to describe a client which shares its cryptographic material across multiple device. To be discussed ?
Done in draft-06 Yeah, I like Option 2 here. I like that it avoids growing packet size. One caveat though: I'd go for the MAC(...) version rather than the confirmationkey. For starters thing including confirmationkey doesnt authenticate the contents of the packet. But even if it did, signatures aren't meant to hide the contents of what was signed. (Amend you favorite sig scheme to tack on the message at the end a signature and you've still got a secure signature scheme. But clearly not a message hiding one.) That means that AFAIK neither ECDSA nor EdDSA etc. were designed or analyzed for such a property. So this would amount to a very non-standard use of a signature schemes by MLS. Not saying it doesn't work for the particular sig schemes in our ciphersuite. But its def. not how signatures are "meant" to be used. But that leaves Option 2 with, say, including tag = MAC(confkey, conftranshash MLSPlaintext.content) into whats being signed which I like and think gets the job done. Both conf* values are taken from the current epoch. By MLSPlaintext.content I mean whats now called MLSPlaintextTBS. NAME For Proopsals I think this works. Is that about what you had in mind for commits too? Joël On 18/08/2020 17:26, Richard Barnes wrote: >

9.1.  <ins> The "signature" field in an MLSPlaintext object authenticates the sender of the MLSPlaintext message.  The input to the signature is an MLSPlaintextTBS structure, which encodes the content and metadata of the message as well a context for the signature that associates to the group messages sent within the group. In cases where the sender is a member of the group, the context contains the GroupContext for the current epoch and a "membership tag" that authenticates that the sender is a member of the group.  As a result, for messages sent within a group, the signature authenticates the member's individual identity, and the membersip tag authenticates their membership in the group. The "membership_tag" field in the MLSPlaintext object authenticates the sender's membership in the group.  For an MLSPlaintext with a sender type other than "member", this field MUST be omitted.  For messages sent by members, it MUST be present and set to the following value: When the sender type is "member", the "membership_tag" field MUST contain a full-size MAC output, and MUST be verified before verifying the signature or processing the message content.  For other sender types, the "membership_tag" field MUST be set to the zero-length octet string. </ins> 9.2. The "ciphertext" field of the MLSCiphertext object is produced by supplying the inputs described below to the AEAD function specified  <ins> by the ciphersuite in use.  The plaintext input contains the content and signature of the MLSPlaintext, plus optional padding.  (The membership token is not included in the encrypted content, because the AEAD operation already authenticates the sender's membership in the group.)  These values are encoded in the following form: Note that the "membership_tag" is not carried forward from MLSPlaintext to MLSCiphertext.  In the context of an MLSCiphertext, the AEAD authentication provides the function that the "membership_tag" provides for MLSPlaintext. </ins> The key and nonce used for the encryption of the message depend on the content type of the message.  The sender chooses the handshake Initial contents:  <ins> All of these ciphersuites use HMAC RFC2104 as their MAC function, with different hashes per ciphersuite.  The mapping of ciphersuites to HPKE primitives, HMAC hash functions, and TLS signature schemes is </ins> as follows I-D.irtf-cfrg-hpke RFC8446: The hash used for the MLS transcript hash is the one referenced in


5.4. <del> When performing a Commit, the generator of the Commit updates its leaf KeyPackage and its direct path to the root with new secret values.  The HPKE leaf public key within the KeyPackage MUST be derived from a freshly generated HPKE secret key to provide post- compromise security. The generator of the Commit starts by sampling a fresh random value called "leaf_secret", and uses the leaf_secret to generate their leaf HPKE key pair (see key-packages) and to seed a sequence of "path secrets", one for each ancestor of its leaf.  In this setting, </del>  path_secret[0] refers to the node directly above the leaf, path_secret[1] for its parent, and so on.  At each step, the path secret is used to derive a new secret value for the corresponding For example, suppose there is a group with four members: <del> If member B subsequently generates a Commit based on a secret </del>  "leaf_secret", then it would generate the following sequence of path secrets: <del> After the Commit, the tree will have the following structure, where "np[i]" represents the node_priv values generated as described above: </del>  <del> After performing these operations, the generator of the Commit MUST delete the leaf_secret. </del>  5.5. <del> After generating a Commit as described in the prior section, the generator of the Commit must broadcast this update to other members of the group, who apply it to keep their local views of the tree in sync with the sender's.  When a client commits a change to the tree (e.g., to add or remove a member), it transmits a handshake 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 can use these values to update their view of the tree, aligning their copy of the tree to the sender's. To perform an update for a path (a Commit), 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 7.7. <del> As described in commit, each MLS Commit message needs to transmit a KeyPackage leaf and node values along its direct path.  The path contains a public key and encrypted secret value for all intermediate nodes in the path above the leaf.  The path is ordered from the closest node to the leaf to the root; each node MUST be the parent of its predecessor. </del>  The number of ciphertexts in the "encrypted_path_secret" vector MUST be equal to the length of the resolution of the corresponding copath

This PR updates two sections to reference generating and sending an message, as not all Commits transmit fresh key material (as discussed in ). This PR also includes light editorial edits for clarity and adds detail around the steps to perform an update. Section 7.7 currently says that "each MLS Commit message needs to transmit a KeyPackage leaf and node values along its direct path", but section 10.2 says that an UpdatePath is optional. I can update 7.7 as well, if 10.2 is correct.
I went ahead and changed Section 7.7 to specify that MLS commits may optionally transmit updates for consistency with Section 10.2

5.4.  <ins> A member of an MLS group advances the key schedule to provide forward secrecy and post-compromise security by providing the group with fresh key material to be added into the group's shared secret.  To do so, one member of the group generates fresh key material, applies it to their local tree state, and then sends this key material to other members in the group via an UpdatePath message (see update-paths) . All other group members then apply the key material in the UpdatePath to their own local tree state to derive the group's now-updated shared secret. To begin, the generator of the UpdatePath updates its leaf KeyPackage and its direct path to the root with new secret values.  The HPKE leaf public key within the KeyPackage MUST be derived from a freshly generated HPKE secret key to provide post-compromise security. The generator of the UpdatePath starts by sampling a fresh random value called "leaf_secret", and uses the leaf_secret to generate their leaf HPKE key pair (see key-packages) and to seed a sequence of "path secrets", one for each ancestor of its leaf.  In this setting, </ins> path_secret[0] refers to the node directly above the leaf, path_secret[1] for its parent, and so on.  At each step, the path secret is used to derive a new secret value for the corresponding For example, suppose there is a group with four members:  <ins> If member B subsequently generates an UpdatePath based on a secret </ins> "leaf_secret", then it would generate the following sequence of path secrets:  <ins> After applying the UpdatePath, the tree will have the following structure, where "np[i]" represents the node_priv values generated as described above: </ins>  <ins> After performing these operations, the generator of the UpdatePath MUST delete the leaf_secret. </ins> 5.5.  <ins> After generating fresh key material and applying it to ratchet forward their local tree state as described in the prior section, the generator must broadcast this update to other members of the group in 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 a 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. 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: </ins> The public key for the node 7.7.  <ins> As described in commit, each MLS Commit message may optionally transmit a KeyPackage leaf and node values along its direct path. The path contains a public key and encrypted secret value for all intermediate nodes in the path above the leaf.  The path is ordered from the closest node to the leaf to the root; each node MUST be the parent of its predecessor. </ins> The number of ciphertexts in the "encrypted_path_secret" vector MUST be equal to the length of the resolution of the corresponding copath


NAME raised a few points today, which I'm trying to capture in this issue with some thoughts on proposed solutions. In , we removed the usage of to send Add proposals, in order to allow for the signature on an external commit to cover the GroupContext (which is not necessarily possible for an external Add sender). In the discussion, the point was made that an Add proposal only really differs from a KeyPackage in that the KeyPackage doesn't have or fields. Part of the resolution in was a decision that folks didn't think was a significant loss. And I think there's pretty broad agreement that is actively undesirable, since it means that the new joiner has to be sufficiently in sync with the group to know the epoch hasn't changed before sending its proposal. After talking with NAME there are two other issues to highlight: Losing is a bigger deal than it might first seem in a system that has both "prepublished KeyPackages" and "KeyPackage as request to be added". Someone could go grab a bunch of prepublished KeyPackages that they don't own, and send them in as requests to be added. This isn't an immediate security risk, since the attacker doesn't necessarily have the private keys that go with the KeyPackages (if they do, they have other attack avenues). But it would mean, for example, that a client would have an even less certain idea of all the groups it was a member of. When "requests to be added" were expressed as Proposals, the "request" semantic followed from being a Proposal, which tied into the recommendations for what should be included in a Commit. Expressing these requests in a different way, means that the "request" semantic rests outside of the core MLS logic. A few possible avenues come to mind for addressing these issues: Don't change the protocol, but document the operational considerations. For example: The "unintended join" issue with abuse of prepublished KPs could be mitigated by a DS that supports proactive joins enforcing that the sender owns the signature key (e.g., checking that authenticates the same identity that the DS has for the client). The request semantic could be captured in the architecture document / protocol overview as a thing that applications could implement if they choose. Re-add joiner-sent Add proposals, using a different SenderType this time to avoid the conflict with external commits. Say and This would still have the epoch problem. Add a field to KeyPackage indicating whether the KeyPackage is general or directed to a specific group Something like Require that if the field is set in an Add proposal, then it MUST match the group's ID. Specify that only directed KPs can be used to request to be added Add parallel text to proposals that recommend the the committer commit Add proposals for any directed KPs it has received. Define a new top level message (an entry in the MLSMessage enum) that expresses precisely the semantic we want, something like:
Thanks NAME for writing this up. My problems with using KeyPackages as a "verb" are more extensive than what Richard described. The rules for deciding which combinations of Proposals are valid are already pretty complicated. Relying on a new implicit mechanism that interacts with this explicit Proposal logic makes me very uncomfortable. While it might require some retries to get the correct epoch, I am also concerned about copy paste attacks. I think approaches 1, 3, and 4 are all vulnerable some form of copy paste attack. I propose re-adding new joiner-sent Add Proposals with a new SenderType. While catching the correct epoch can be a nuisance, I think it is a separable problem. Thanks, -rohan
Personally, I'm less concerned about the flow concerns here and more concerned about epoch mismatches. In real-time scenarios, epoch mismatches are a huge pain. You have potentially hundreds of people joining at the beginning of a meeting and retrying a join adds a lot of latency. So the ability for the committer to choose which requests to join go in which Commits is really important for things to work smoothly. I would not be able to use (2) in Webex, but would gladly use (3). As for being more implicit: I actually have the opposite concern, that allowing joiner-signed Adds is riskier from a policy POV. In general, before someone is added to the group, some application logic at a member needs to check that the new member is acceptable. If an Add proposal has to be made by a member, you get two opportunities to check, when the proposal is made and when it's committed. If the new joiner makes the proposal, you lose the first. (I also have a more minor aesthetic concern about re-using the general framing for this specific niche use case. We have this general framing that is actually general for members; they can send any content type. External proposals and external commits are constrained to one content type, but still pretty general. New member add proposals would be limited to one content type and one proposal type, which is getting quite narrow.) By contrast, the semantic shift for extending KeyPackage is really pretty minor. The semantic of KeyPackage is already "Please make an Add proposal with this object to add me to a group". That's not far away from "Please make an Add proposal to add me to ${THISSPECIFICGROUP}." That seems like a smaller semantic shift than the shift from "Someone authorized to make changes to this group proposed a change" to "Some rando on the Internet proposed a change". Net of all that, I'm pretty strongly in favor of (3).
NAME I specifically mentioned copy paste attacks. I would be happy for us to have a reasonable general solution to epoch mismatches within a narrow time window, but not at the expense of an unbounded timeframe for a copy paste attack. That said, if you want to implement some implicit KeyPackage behavior to add using authorization rules that work for your implementation and your DS is in a position to prevent those attacks, you can still do what you proposed. I'm just asking not to take away the tool we are using. In terms of scope, external add proposals have been in the protocol document since -08 (2019). We use them extensively and implemented them in good faith. Removing them now and proposing we use an implicit approach with a know security concern that happens to solve a problem that was previously out of scope seems a bit arbitrary. Proposal 2 (new SenderType) does not harm anyone else and preserves functionality in the draft. It does not prevent you from solving the epoch problem your way or in any other future way.
I think the points raised by NAME are quite valid, in particular since external proposals can still be used with preconfigured senders (and there is a better symmetry with external remove proposals). I don't see why that wouldn't be good enough in a scenario where the DS is quite involved beyond the handshake message ordering anyway. That being said, from all the options above (3) would also be my favorite. I like the idea of purpose-bound KeyPackages.
I don't understand this concern since adding someone to a group has never required their consent or knowledge. The change here is just that you can perhaps get someone else to do the adding, though I would say that's better in the sense that a non-malicious client doing the Add would actually send the Welcome. If the malicious user is doing the Add, they might not send a Welcome. NAME Would you mind expanding more on what you mean by copy-paste attack? This isn't a downside, this is a core reason that it external Adds were removed. We observed that applications seem to prefer doing this themselves (at least those that spoke on the ticket / interim did).
The concern with implying Add semantics using a bare (undirected) keypackage is we don't have any record of anyone's intent to add the MLS client to the group. [I don't understand what is supposedly "better" about not sending an external proposal. Either way someone sends a Proposal and whoever sends a valid commit containing that Proposal is supposed to send the Welcome.] Alice (in domain A) decides to join the Bananas group in domain B. Alice sends a directed KeyPackage (option 3). Variation 1: Two members of the Bananas group, Cathy and Doug, copy this request and replay it repeatedly to the group, possibly after Alice leaves the group. [Currently we only will forward a Welcome to an MLS client if the KeyPackageRef was seen when consuming a KeyPackage from the Directory; or from sending an External Proposal or External Commit in that epoch. So before this change we could scope the state needed to forward a Welcome to a specific sender, or a particular epoch. Either way this was easy to clean up. With the current proposal this needs to be sent across all the domains in the group, and is no longer time bounded.] Variation 2: An attacker Bob on B returns an error and copies (stored) the add request. Later at a time of Bob's choosing, he sends (pastes) the request to the Bananas group, causing Alice to be added to Bananas at a different time than Alice intended. Well, we are an implementer and are definitely using them and like them, so it is a downside for us, and it sounds like for everyone else it was a no-op, not an upside (they can do it their way whether or not External Proposals for Adds are possible or not). For background, we are using External Proposal Adds only to add a new client device representing a user who is already in the group. We never use them to add a different user or for a server to add a user. Our clients are not necessarily online at the same time, so there may not be an MLS member online when the request is sent. This is fine for External Proposal because each client can process and verify MlsMessage linearly. In addition, all our use cases are federated, so the user who ends up sending the Commit which finally adds the new client often has no direct relationship with the backend/directory through which the new client normally forwards MlsCiphertext/MlsPlaintext messages or in which is stores its KeyPackages.
On the copy/paste risk: It occurs to me that we actually have a solution for this already: The field in LeafNode. KeyPackages are already designed to expire, and that mechanism provides flexibility that matches the use case much better than .
Our use case for external add proposals is for an ordinary client adding itself (never a preconfigured sender), whereas for external removes this is always a backend taking action (preconfigured sender). >I don't see why that wouldn't be good enough in a scenario where the DS is quite involved beyond the handshake message ordering anyway. I'm not sure I follow you. Coordinating enough DS involvement across federated domains looks hard to me. Could you elaborate please?
Good point, although running a bunch of timers on servers is definitely more work than checking the epoch.
So in that instance you say it is fine to use preconfigured senders, why wouldn't it be fine when adding members? Distributing keys is indeed a hard problem in a federated environment. But I don't see how that's getting better with "new member" external Add proposals. In both cases, there is a signature to be verified and the key material needs to be available to the verifier.
One more note on the preconfigured senders: They are synchronized as a group extension (something that was added recently and I forgot about it). That makes the distribution much easier and less error-prone than the external Add with sender type "new member".
Interim 2022-05-26: A few comparisons: Add vs. Add ~ Add includes client's intent to join Add vs. directed KeyPackage ~ main difference is epoch vs. lifetime NAME to make a PR implementing the "new sender type" approach NAME to make a PR implementing the "directed KeyPackage" approach Aim to decide on next call

3. <ins> The Asynchronous Ratcheting Tree is the building block around which this protocol is built.  It was originally specified by Cohn-Gordon et al with a formal proof of its security properties (https://eprint.iacr.org/2017/666). ART uses a Diffie-Hellman Key Tree to derive a shared secret for a group, allowing it to be efficiently updated (ratcheted) by any group member (leaf node) to provide the properties of Forward Secrecy and Post-Compromise Security.  Alongside ratcheting, receiving messages, and changing the group membership can also be efficiently achieved. </ins> 3.1. <ins> ART uses a recursive structure, in which every node is a Diffie- Hellman public key or key pair.  The value of a given parent node's private key is computed as follows: (TODO: define our KDF). The ART root secret is the private Diffie-Hellman key of the root of the tree.  This should not be used directly within the protocol, but rather computed into a Stage Key (TODO: below). </ins> 3.2. 3.3.


Provider (SP) as described in I-D.ietf-mls-architecture.  In particular, we assume the SP provides the following services: <del> A long-term identity key provider which allows clients to </del>  authenticate protocol messages in a group. A broadcast channel, for each group, which will relay a message to A KeyPackage object specifies a ciphersuite that the client supports, as well as providing a public key that others can use for key <del> agreement.  The client's identity key can be updated throughout the </del>  lifetime of the group by sending a new KeyPackage with a new <del> identity; the new identity MUST be validated by the authentication service. </del>  When used as InitKeys, KeyPackages are intended to be used only once and SHOULD NOT be reused except in case of last resort.  (See The value for hpke_init_key MUST be a public key for the asymmetric encryption scheme defined by cipher_suite.  The whole structure is <del> signed using the client's identity key.  A KeyPackage object with an </del>  invalid signature field MUST be considered malformed.  The input to the signature computation comprises all of the fields except for the signature field. creator, because they are derived from an authenticated key exchange protocol.  Subsequent leaf keys are known only by their owner. <del> Note that the long-term identity keys used by the protocol MUST be </del>  distributed by an "honest" authentication service for clients to authenticate their legitimate peers. The second form considers authentication with respect to the sender, meaning the group members can verify that a message originated from a particular member of the group.  This property is provided by digital <del> signatures on the messages under identity keys. </del>  14.3.

The public key in a credential is introduced without a special name and the field in the BasicCredential is currently called public_key, which is not very expressive. A few paragraphs below, it is referenced as "Identity Key" or simply "Identity", which is a bit misleading, as Credentials are supposed to contain an additional "identity" which is not the key. I propose we clear up the terminology a bit here and refer to the public key contained in a Credential as "Signature Key".

Provider (SP) as described in I-D.ietf-mls-architecture.  In particular, we assume the SP provides the following services:  <ins> A long-term signature key provider which allows clients to </ins> authenticate protocol messages in a group. A broadcast channel, for each group, which will relay a message to A KeyPackage object specifies a ciphersuite that the client supports, as well as providing a public key that others can use for key  <ins> agreement.  The client's signature key can be updated throughout the </ins> lifetime of the group by sending a new KeyPackage with a new  <ins> signature key; the new signature key MUST be validated by the authentication service. </ins> When used as InitKeys, KeyPackages are intended to be used only once and SHOULD NOT be reused except in case of last resort.  (See The value for hpke_init_key MUST be a public key for the asymmetric encryption scheme defined by cipher_suite.  The whole structure is  <ins> signed using the client's signature key.  A KeyPackage object with an </ins> invalid signature field MUST be considered malformed.  The input to the signature computation comprises all of the fields except for the signature field. creator, because they are derived from an authenticated key exchange protocol.  Subsequent leaf keys are known only by their owner.  <ins> Note that the long-term signature keys used by the protocol MUST be </ins> distributed by an "honest" authentication service for clients to authenticate their legitimate peers. The second form considers authentication with respect to the sender, meaning the group members can verify that a message originated from a particular member of the group.  This property is provided by digital  <ins> signatures on the messages under signature keys. </ins> 14.3.


A proposal to make the change is broadcast to the group in a Proposal message <del> A member of the group broadcasts a Commit message that causes one or more proposed changes to enter into effect </del>  The group thus evolves from one cryptographic state to another each time a Commit message is sent and processed.  These states are 11.1.6.  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 11.2.1.  The sender of a Commit message is responsible for sending a Welcome message to any new members added via Add proposals.  The Welcome message provides the new members with the current state of the group,

Sofía noted "It is also unclear at some points the notation used for the Diffie-Hellman or Elliptic Curve parameters. Related to this, is unsure if this protocol takes into account the cofactor issues."
What precisely is unclear? X25519 specifies the scalar-clamping that's used to remove the 8-torsion part of the given curve point. And P-256 is a prime curve, so it has no cofactor.
Well, it only says: It does not refer that you are referring to section 'The X25519 and X448 Functions' of the RFC (if so). And actually, I think it is referred to the subsection in that section where is says "To implement the X25519(k, u) and X448(k, u)". It also says: which does not recommend taking into account that "For X25519, in order to decode 32 random bytes as an integer scalar, set the three least significant bits of the first byte and the most significant bit of the last to zero, set the second most significant bit of the last byte to 1 and, finally, decode as little-endian. " (the clamping mechanism). It is a little bit unclear from the text how the generation of the shared secret happens, as it does not point to an specific part of the RFC, nor specify if the encoding/decoding functions are needed. But this is just my read ;) Precisely. But this section is a little bit more defined. Compare: :)
And around unclear: The definition of used to derive the private key, is defined as "The ciphersuite must also specify an algorithm Derive-Key-Pair that maps octet strings with the same length as the output of the hash function to key pairs for the asymmetric encryption scheme.". It is unclear how this algorithm looks in practice. It seems to imply that this function takes any amount of same length octet strings as input (generated as an output of a hash function) and map to create a private key and a public key somehow.It says "Given an octet string X, the private key produced by the Derive-Key-Pair operation is SHA-256(X). (Recall that any 32-octet string is a valid Curve25519 private key.) The corresponding public key is X25519(SHA-256(X), 9)." If you don't read the RFC, it is unclear that the "9 is the u-coordinate of the base point and is encoded as a byte with value 9, followed by 31 zero bytes". It also does not specify if the private key should have been generated from randomness.When saying "Encryption keys are derived from shared secrets by taking the first 16 bytes of H(Z), where Z is the shared secret and H is SHA-256.", it seems like 'H(input)' could be defined somewhere else previously. Maybe it should be in place to have a definition section for the different functions, like on the RFC7748 in "3. Notation"?When it says "Generate an ephemeral DH key pair (x, x G) in the DH group specified by the ciphersuite in use", does this refer only to the usage of elliptic curves (as it generates the public key as 'xG'? Maybe if it is DH, it should be noted as 'g^x'? This is just some things that were unclear for me when reading :) Perhaps they are discussed somewhere else. It also depends on how self-contained that document wants to be. Hope this helps :)
Ah, ok. These seem like fair points. I had thought that X25519 specifies that clamping be done on every call to , but I see now that it's done once in the decoding step and then never again. Also that P-256 excerpt was very informative. Thanks!

A proposal to make the change is broadcast to the group in a Proposal message  <ins> A member of the group or a new member broadcasts a Commit message that causes one or more proposed changes to enter into effect </ins> The group thus evolves from one cryptographic state to another each time a Commit message is sent and processed.  These states are 11.1.6. <ins> An ExternalInitSecret proposal is used by new members that want to join a group by using an external commit.  This propsal can only be used in that context. The field "kem_output" contains the encrypted "external_seed" as described in computing-the-external-init-secret. 11.1.7. </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 11.2.1. <ins> External Commits are a mechanism for new members (external parties that want to become members of the group) to add themselves to a group, without requiring that an existing member has to come online to issue a Commit that references an Add Proposal. Whether existing members of the group will accept or reject an External Commit follows the same rules that are applied to other handshake messages. New members can create and issue an External Commit if they have access to the following information: group ID current epoch ID ciphersuite public tree hash confirmed transcript hash group extensions Public group key This information is aggregated in "ExternalCommitInfo" as follows: "struct { CipherSuite cipher_suite; CipherSuite cipher_suite; opaque group_id<0..255>; uint64 epoch; opaque tree_hash<0..255>; opaque confirmed_transcript_hash<0..255>; Extension extensions<0..2^32-1>; HPKEPublicKey group_public_key; } ExternalCommitInfo; " Note that the "tree_hash" field is used the same way as in the ratchet-tree-extension. The information above are not deemed public data in general, but applications can choose to make them available to new members in order to allow External Commits. External Commits work like regular Commits, with a few differences: External Commits MUST reference the Add Proposal that adds the issuing new member to the group External Commits MUST be signed by the new member External Commits MUST contain a "path" field (and is therefore a "full" Commit) When processing a Commit, both existing and new members MUST use the external init secret as described in The sender type for the MLSPlaintext encpsulating the External Commit MUST be "new_member" If the Add Proposal is also issued by the new member, its member SenderType MUST be "new_member" 11.2.1.1. The external init secret is calculated using the following steps: In each epoch, existing members use the "external_secret" from the key schedule to derive an HPKE keypair: "group_private_key, group_public_key = KEM.DeriveKeyPair(external_secret)" The group public key is published as part of the "ExternalCommitInfo". The new joiner generates a random "external_seed_secret" of length "KDF.Nh". The new joiner sends the seed to the group by computing: "kem_output, context = SetupBaseS(group_public_key, public_group_context) " where "public_group_context" is the ExternalCommitInfo serialized in TLS syntax. The new joiner sends "kem_output" to the group by encapsulating it in an ExternalInitSecret proposal: "struct { opaque kem_output<0..2^16-1>; } ExternalInitSecret; " Existing members of the group process the ExternalInit proposal and can in turn compute: "context = SetupBaseR(kem_output, group_private_key, public_group_context) " Both sides can now compute: "external_init_secret = ctx.export("external init secret", KDF.Nh) " - Both sides use "external_init_secret" as the iit secret when processing the commit. 11.2.2. External Commits are a mechanism for new members (external parties that want to become members of the group) to add themselves to a group, without requiring that an existing member has to come online to issue a Commit that references an Add Proposal. Whether existing members of the group will accept or reject an External Commit follows the same rules that are applied to other handshake messages. New members can create and issue an External Commit if they have access to the following information: group ID current epoch ID ciphersuite public tree confirmed transcript hash group extensions This information is aggregated in "ExternalCommitInfo" as follows: "struct { CipherSuite cipher_suite; GroupContext group_context; optional<Node> ratchet_tree<1..2^32-1>; } ExternalCommitInfo; " Note that the "ratchet_tree" field is used the same way as in the ratchet-tree-extension. The information above are not deemed public data in general, but applications can choose to make them available to new members in order to allow External Commits. External Commits work like regular Commits, with a few differences: External Commits MUST reference the Add Proposal that adds the issuing new member to the group External Commits MUST be signed by the new member External Commits MUST contain a "path" field (and is therefore a "full" Commit) When processing a Commit, both existing and new members MUST use an empty init secret (all-zero vector of size Hash.length) If the Add Proposal is also issued by the new member, its member SenderType MUST be "new_member" 11.2.3. </ins> The sender of a Commit message is responsible for sending a Welcome message to any new members added via Add proposals.  The Welcome message provides the new members with the current state of the group,


credential in a ratchet tree, the KeyPackage ciphersuite for a credential in a KeyPackage object.  For ciphersuites using Ed25519 or Ed448 signature schemes, the public key is in the format specified RFC8032.  For ciphersuites using ECDSA with the NIST curves P-256 or P-521, the public key is the output of

This PR adds clarification around X509 cert structure, the chain and its encoding X509.
NAME thanks for the suggestions. updated PR to reflect the changes . pls let me know ..
NAME i have pushed another commit .. can you please check. thanks

credential in a ratchet tree, the KeyPackage ciphersuite for a credential in a KeyPackage object. <ins> For X509Credential, each entry in the chain represents a single DER- encoded X509 certificate.  The chain is ordered such that the first entry (chain[0]) is the end-entity certificate and each subsequent certificate in the chain MUST be the issuer of the previous certificate.  The algorithm for the "public_key" in the end-entity certificate MUST match the relevant ciphersuite. </ins> For ciphersuites using Ed25519 or Ed448 signature schemes, the public key is in the format specified RFC8032.  For ciphersuites using ECDSA with the NIST curves P-256 or P-521, the public key is the output of


The public key of a signature key pair matching the SignatureScheme specified by the CipherSuite of the group <del> The identity of the holder of the private keys </del>  Credentials MAY also include information that allows a relying party to verify the identity / signing key binding.

Thanks NAME pointing out the imprecision. Is this what you were looking for? Thanks NAME for the pointer to the correct registry.
This doesn't make sense because we don't actually use this registry. The signature scheme is defined by the ciphersuite, and the ciphersuite registry doesn't map to this unrelated registry
The is not authoritative in the group context and will always correspond with the one in the group (as is required by the generic description of the functionalities that Credentials have to provide). The SignatureScheme is there for when the Credential is handled in a context where there is no surrounding group. For example, when it's handled by the AS. There is a mapping from ciphersuit to SignatureScheme in Table 5, the entries of which correspond to the entries in the referenced registry. Is that what you meant?

The public key of a signature key pair matching the SignatureScheme specified by the CipherSuite of the group  <ins> The identity of the holder of the private key </ins> Credentials MAY also include information that allows a relying party to verify the identity / signing key binding.


Thus the "confirmed_transcript_hash" field in a GroupContext object represents a transcript over the whole history of MLSPlaintext Commit messages, up to the confirmation tag field in the current <del> MLSPlaintext message.  The confirmation tag and signature fields are then included in the 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 objects in cases where it is necessary for the Delivery Service to examine such messages.  External sender types are sent as MLSPlaintext, see external- proposals for their use. signing private key corresponding to the credential at the leaf of the tree indicated by the sender field.  The signature covers the plaintext metadata and message content, which is all of MLSPlaintext <del> except for the "signature" and "membership_tag" fields.  If the sender is a member of the group, the signature also covers the GroupContext for the current epoch, so that signatures are specific to a given group and epoch. </del>  The "membership_tag" field in the MLSPlaintext object authenticates the sender's membership in the group.  For an MLSPlaintext with a "psk_secret" to 0. Construct an MLSPlaintext object containing the Commit object. <del> Use the "commit_secret" and the "psk_secret" to advance the key schedule and compute the "confirmation_tag" value in the MLSPlaintext.  Sign the MLSPlaintext using the current epoch's GroupContext as context. </del>  Update the tree in the provisional state by applying the direct path

As discussed in the mailing list, not having the signature contribute to the new epoch's key schedule leads to a one-off effect where parties stay in sync (with respect to keys) for one more epoch, before inevitably go out of sync. This pull request addresses this issue by including the signature in the confirmed transcript hash rather than the interim transcript hash. See the mailing list for a security consideration of this change. Credit: This pull request was primarily authored by Marta Mularczk; I'm just posting it here for administrative reasons.

Thus the "confirmed_transcript_hash" field in a GroupContext object represents a transcript over the whole history of MLSPlaintext Commit messages, up to the confirmation tag field in the current  <ins> MLSPlaintext message.  The confirmation tag is then included in the 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. </ins> As shown above, when a new group is created, the "interim_transcript_hash" field is set to the zero-length octet objects in cases where it is necessary for the Delivery Service to examine such messages. <ins> The field "confirmation_tag" MUST be present if "content_type" equals commit.  Otherwise, it MUST NOT be present. </ins> External sender types are sent as MLSPlaintext, see external- proposals for their use. signing private key corresponding to the credential at the leaf of the tree indicated by the sender field.  The signature covers the plaintext metadata and message content, which is all of MLSPlaintext  <ins> except for the "signature", the "confirmation_tag" and "membership_tag" fields.  If the sender is a member of the group, the signature also covers the GroupContext for the current epoch, so that signatures are specific to a given group and epoch. </ins> The "membership_tag" field in the MLSPlaintext object authenticates the sender's membership in the group.  For an MLSPlaintext with a "psk_secret" to 0. Construct an MLSPlaintext object containing the Commit object.  <ins> Sign the MLSPlaintext using the current epoch's GroupContext as context.  Use the signature, the "commit_secret" and the "psk_secret" to advance the key schedule and compute the "confirmation_tag" value in the MLSPlaintext. </ins> Update the tree in the provisional state by applying the direct path


supported by all members. Initialize a one-member group with the following initial values <del> (where "0" represents an all-zero vector of size Hash.length): </del>  Ratchet tree: A tree with a single node, a leaf containing an HPKE public key and credential for the creator

For the record: I asked NAME whether we could just update DeriveTreeSecret so that it always produces octets. But he pointed out that DeriveTreeSecret is also used to produce keys and nonces. Might be something to clean up later.

supported by all members. Initialize a one-member group with the following initial values  <ins> (where "0" represents an all-zero vector of size KDF.Nh): </ins> Ratchet tree: A tree with a single node, a leaf containing an HPKE public key and credential for the creator


from the closest node to the leaf to the root; each node MUST be the parent of its predecessor. <del> The number of ciphertexts in the "encrypted_path_secret" vector MUST be equal to the length of the resolution of the corresponding copath node.  Each ciphertext in the list is the encryption to the corresponding node in the resolution. </del>  The HPKECiphertext values are computed as the "path" field at its discretion. If populating the "path" field: Create a UpdatePath using the new <del> tree (which includes any new members).  The GroupContext for this operation uses the "group_id", "epoch", "tree_hash", and "confirmed_transcript_hash" values in the initial GroupContext object. </del>  Assign this UpdatePath to the "path" field in the Commit.

As briefly explained in the mailing list, commit messages currently contain encryptions for newly added group members that never even receive this commit message. This PR explicitely exlcudes them during the preparation of the UpdatePath.
I think this is a good point that wasn't really specified so far. The wording sounds a bit convoluted, maybe there is an easier way to describe it, like saying that the final resolution list should be filtered and all new leaf nodes should be removed from it.

from the closest node to the leaf to the root; each node MUST be the parent of its predecessor.  <ins> For each "UpdatePathNode", the resolution of the corresponding copath node MUST be filtered by removing all new leaf nodes added as part of this MLS Commit message.  The number of ciphertexts in the "encrypted_path_secret" vector MUST be equal to the length of the filtered resolution, with each ciphertext being the encryption to the respective resolution node. </ins> The HPKECiphertext values are computed as the "path" field at its discretion. If populating the "path" field: Create a 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 GroupContext object. </ins> Assign this UpdatePath to the "path" field in the Commit.


confirmation key and the "confirmed_transcript_hash" from the GroupInfo.  11.3. By default, a GroupInfo message only provides the joiner with a

As discussed on 2020-10-20, this field is not needed now that moved the signature under the confirmation tag.
We have a long-standing OPEN ISSUE on creating a group with a single Init message instead of N Add messages, which would change the work of initialization from O(N log N) to O(N). I would propose roughly the following: Then the processing would be: Find the welcome that's for you Initialize a one-member tree from the Welcome Use the UserInitKeys to populate the leaves of the tree Use the DirectPath to populate the creator's direct path (as in Update)
Fixed by URL
These keys are Client one-time use keys, so "UserInitKey" seems like a strange name.
I'm fine with ClientInitKey, as long as a client is not only a physical endpoint. Changes in the architecture doc should make this clear soon.

confirmation key and the "confirmed_transcript_hash" from the GroupInfo. <ins> Use the confirmed transcript hash and confirmation tag to compute the interim transcript hash in the new state. </ins> 11.3. By default, a GroupInfo message only provides the joiner with a


draft-10  Re-enable constant-time Add (*)  draft-09 Remove blanking of nodes on Add (*)

We have a changelog for the document by I-D release, but haven't been updating it as we go. This PR updates the changelog based on merged PRs and the few we expect to close before releasing draft-10. Marking this as draft right now, until the other PRs have merged.

draft-10 <ins> Allow new members to join via an external Commit (*) Enable proposals to be sent inline in a Commit (*) </ins> Re-enable constant-time Add (*) <ins> Change expiration extension to lifetime extension (*) Make the tree in the Welcome optional (*) PSK injection, re-init, sub-group branching (*) Require the initial init_secret to be a random value (*) Remove explicit sender data nonce (*) Do not encrypt to joiners in UpdatePath generation (*) Move MLSPlaintext signature under the confirmation tag (*) Explicitly authenticate group membership with MLSPLaintext (*) Clarify X509Credential structure (*) Remove uneeded interim transcript hash from GroupInfo (*) IANA considerations Derive an authentication secret Use Extract/Expand from HPKE KDF Clarify that application messages MUST be encrypted </ins> draft-09 Remove blanking of nodes on Add (*)