Draft: draft-ietf-mls-protocol

Commentary: Based on 
 Interim 2022 - 05 - 26 : This will require some optimized implementations Have people done performance evaulations ? and did evaluation on parent hash , this should be easier How does this affect the secret tree ? Would change the structure to be a full tree Always derivations to get to a leaf , instead of sometimes less TODO : Hold this for next week , and to do performance evaluation 
 Mailing list post with performance metrics : 
 Interim 2022 - 06 - 02 : presented performance data showing + /- ~20 % on join asked about removing leaf index from LeafNodeHashInput in order to allow for precomputing blank subtree hashes Even with leaf indices , can pre - generate , they just have to be in - place No action Clear to merge once editorial comments from are addressed 
 In order to use tree hashes to represent siblings in parent hashes , we had to compute a different tree hash on the right edge of the tree , pretending that the tree is full , in the sense of having leaves . Thus we actually have two tree hashes in play , one used for group agreement on the tree and one used for parent hash inputs . We should consider specifying that the logical tree used by MLS is always full . This change to the specification would not change the computations done by the protocol much , if at all . Thanks to the fact that we no longer generate redundant nodes ( ) , there is never anything non - blank to the right of the rightmost non - blank leaf anyway . So the main impact is to insert some blank nodes between a parent node on the right edge of the tree and its right child -- and all of the algorithms in the specification already skip over such nodes . Likewise , implementations ' internal represntations of the tree would n't have to change much , since the extra nodes could be " virtual " . The only time they would be noticed is when computing tree hashes . We would have to update the description of the extension to say that it only covers the tree up to the rightmost non - blank leaf . It would simplify the specification in a few ways , for example : The " original sibling tree hash " would just entail blanking unmerged leaves , not structure changes . The description of extending and truncating the tree would be much simpler . The main cost would be extra hashes when joining , or when updating a member close to the right edge of the tree . If you have a tree with leaves , then you have to compute tree hashes over blank leaves and their blank parents . 
 Thanks to and for helping inspire and develop this idea in discussions around 
 Virtual interim 2022 - 05 - 19 notes that the current tree hash is quite tricky to implement , so this would be a useful simplification Doubling the hashes on a fresh tree does n't sound too intimidating Folks would find more concreteness useful here , interested in PR Could we phrase this as only a change to TreeKEM ? Would be premised on the equivalence of the hashed tree an the tree you operate on 
 Discussed at 19 May interim : to draft a PR . 
 Holding a PR until after lands . Some notes on what would need to change : { { ratchet - tree - terminology } } would change from " the unique left - balanced tree with N leaves " to " the unique balanced binary tree with depth , where is the smallest number such that " . { { adding - and - removing - leaves } } would either be removed , or updated so that it talks about increasing or decreasing the depth of the tree ( equivalently , doubling or halving the number of leaves ) . { { add } } would talk about increasing depth instead of adding leaves . { { remove } } would talk about decreasing depth instead of removing leaves . { { ratchet - tree - extension } } would be explicit that it only carries nodes up to the rightmost non - blank leaf . The encoding / decoding algorithms would have to be updated . { { array - based - trees } } and { { link - based - trees } } could probably be simplified . We would need to make sure all figures showed full trees . 
 I went ahead and filed for this , based on top of . Once that merges , we can update the PR to be a little easier to read . The changes are basically as described above . One additional note : We have actually already incurred the cost to a new joiner of hashing all the extra nodes , because they have to do it for parent hash verification anyway . So this approach is actually a net reduction in the amount of hashing that has to be done , since they do n't have to also compute the non - full - tree hashes . 
 Using full tree + filtered direct path seems like a fine combo ! As you said in it not only faster , but also makes things a lot easier to understand in various places of the protocol . I also like a lot the two examples you ( the one for filtered direct path , and the one for parent - hash link ) , they have a nice pedagogical value !
Mailing list commentary: Hi all , As promised on yesterday 's call , I ran some performance metrics on the " Always use a full tree " PR ( # 645 ) . I took a prototype I had of the tree - hash - based parent hash and extended it to use a full tree . Note that I did not implement the suppression of redundant nodes , so these trees have the redundant nodes in them . The only real bias that that would introduce here is to do log(n ) extra parent hash verifications in the full - tree case ; in other words , the full - tree case should be slightly better in reality than it is here . The testing methodology is basically the same as [ 1 ] : Make a tree with N members and see how long it takes the Nth joiner to import the tree . The main difference is that since [ 1 ] I got a new laptop , so I can go to N=512 instead of just N=256 :) PDF with plots of full results at [ 2 ] . Summary : Time to compute tree hash is up to 30 % worse , never better Time to verify parent hashes is up to 40 % better , never worse Overall time to ingest a tree ( sum of the above ) varies between 30 % worse and 20 % better The absolute total ingest time on the high end ( 512 participants ) is on the order of 60ms , so we ’re talking about variations of around plus or minus 15ms . This is on a current - year . The primary driver for whether the full tree does better is how filled - in the tree is , in the sense of parent nodes being populated . If even 1/4 of the tree is filled , then you ’re basically always better off with a full tree , even with 3/4 of leaves unmerged . Overall , these numbers do n’t seem intimidating to me , so I ’ m inclined to merge the PR — [ 1 ] [ 2 ]
Paragraph (version B):
MLS supports two ways to tie a new group to an existing group.
Reinitialization closes one group and creates a new group comprising
the same members with different parameters.  Branching starts a new
group with a subset of the original group's participants (with no
effect on the original group).  In both cases, the new group is
linked to the old group via a resumption PSK.
Paragraph (version B):
Every tree used in this protocol is a perfect binary tree, that is, a
complete balanced binary tree with "2^d" leaves all at the same depth
"d".  This structure is unique for a given depth "d".
Paragraph (version B):
There are multiple ways that an implementation might represent a
ratchet tree in memory.  A convenient property of left-balanced
binary trees (including the complete trees used here) is that they
can be represented as an array of nodes, with node relationships
computed based on the nodes' indices in the array.  A more
traditional representation based on linked node objects may also be
used.  array-based-trees and link-based-trees provide some details on
how to implement the tree operations required for MLS in these
representations.  MLS places no requirements on implementations'
internal representations of ratchet trees.  An implementation MAY use
any tree representation and associated algorithms, as long as they
produce correct protocol messages.
Paragraph (version B):
Each leaf node in a ratchet tree is given an _index_ (or _leaf
index_), starting at "0" from the left to "2^d - 1" at the right (for
a tree with "2^d" leaves).  A tree with "2^d" leaves has "2^(d+1) -
1" nodes, including parent nodes.
Paragraph (version B):
The _direct path_ of a root is the empty list, and of any other node
is the concatenation of that node's parent along with the parent's
direct path.
Paragraph (version B):
The _copath_ of a node is the node's sibling concatenated with the
list of siblings of all the nodes in its direct path, excluding the
root.
Paragraph (version B):
The _filtered direct path_ of a leaf node L is the node's direct
path, with any node removed whose child on the copath of L has an
empty resolution (keeping in mind that any unmerged leaves of the
copath child count toward its resolution).  The removed nodes do not
need their own key pairs because encrypting to the node's key pair
would be equivalent to encrypting to its non-copath child.
Paragraph (version B):
The application using MLS is responsible for specifying which
identifiers it finds acceptable for each member in a group.  In other
words, following the model that RFC6125 describes for TLS, the
application maintains a list of "reference identifiers" for the
members of a group, and the credentials provide "presented
identifiers".  A member of a group is authenticated by first
validating that the member's credential legitimately represents some
presented identifiers, and then ensuring that the reference
identifiers for the member are authenticated by those presented
identifiers.
Paragraph (version B):
When a member receives a KeyPackage that it will use in an Add
proposal to add a new member to the group.
Paragraph (version B):
When a member receives an Add proposal adding a member to the
group.
Paragraph (version B):
When a member receives an Update proposal whose LeafNode has a new
credential for the member.
Paragraph (version B):
When a member receives a Commit with an UpdatePath whose LeafNode
has a new credential for the committer.
Paragraph (version B):
When an "external_senders" extension is added to the group, or an
existing "external_senders" extension is updated.
Paragraph (version B):
In cases where a member's credential is being replaced, such as
Update and Commit cases above, the AS MUST also verify that the set
of presented identifiers in the new credential is valid as a
successor to the set of presented identifiers in the old credential,
according to the application's policy.
Paragraph (version B):
If needed, applications may add application-specific identifiers to
the "extensions" field of a LeafNode object with the "application_id"
extension.
Paragraph (version B):
However, applications SHOULD NOT rely on the data in an
"application_id" extension as if it were authenticated by the
Authentication Service, and SHOULD gracefully handle cases where the
identifier presented is not unique.
Paragraph (version B):
"new_member_commit": The signature key in the LeafNode in the
Commit's path (see joining-via-external-commits).  The
"content_type" of the message MUST be "commit".
Paragraph (version A):
Verify that the credential in the LeafNode is valid according to
the authentication service and the client's local policy.  These
actions MUST be the same regardless of at what point in the
protocol the LeafNode is being verified with the following
exception: If the LeafNode is an update to another LeafNode, the
authentication service MUST additionally validate that the set of
identities attested by the credential in the new LeafNode is
acceptable relative to the identities attested by the old
credential.
Paragraph (version B):
Verify that the credential in the LeafNode is valid as described
in credential-validation.
Paragraph (version A):
Verify that the "leaf_node_source" field has the appropriate value
for the context in which the LeafNode is being validated (as
defined in leaf-node-contents).
Paragraph (version A):
When validating a LeafNode in a KeyPackage before sending an
Add proposal, the current time MUST be within the "lifetime"
range.  A KeyPackage containing a LeafNode that is expired or
not yet valid MUST NOT be sent in an Add proposal.
Paragraph (version A):
When receiving an Add or validating a tree, checking the
"lifetime" is RECOMMENDED, if it is feasible in a given
application context.  Because of the asynchronous nature of
MLS, the "lifetime" may have been valid when the leaf node was
proposed for addition, even if it is expired at these later
points in the protocol.
Paragraph (version B):
If the LeafNode appears in a message being sent by the client,
e.g., a proposal or a commit, then the client MUST verify that
the current time is within the range of the "lifetime" field.
Paragraph (version B):
If instead the LeafNode appears in a message being received by
the client, e.g., a proposal, a commit, or a ratchet tree of
the group the client is joining, it is RECOMMENDED that the
client verifies that the current time is within the range of
the "lifetime" field.
Paragraph (version B):
If the LeafNode appears in a KeyPackage, verify that
"leaf_node_source" is set to "key_package".
Paragraph (version B):
If the LeafNode appears in an Update proposal, verify that
"leaf_node_source" is set to "update".
Paragraph (version B):
If the LeafNode appears in the "leaf_node" value of the
UpdatePath in a Commit, verify that "leaf_node_source" is set
to "commit".
Paragraph (version A):
Recall the definition of resolution from ratchet-tree-nodes.  To
begin with, the generator of the UpdatePath updates its leaf and its
leaf's _filtered direct path_ with new key pairs.  The filtered
direct path of a node is obtained from the node's direct path by
removing all nodes whose child on the nodes' copath has an empty
resolution (keeping in mind that any unmerged leaves of the copath
child count towards its resolution).  Such a removed node does not
need its own key pair since, if the copath child's resolution is
blank, then encrypting any data to the node's key pair would be
equivalent to encrypting only to the non-copath child.
Paragraph (version A):
To add a new leaf: Add leaf L as the new rightmost leaf of the tree.
Add a blank parent node P whose right child is L.  P is attached to
the tree as the right child of the only appropriate node to make the
updated tree left-balanced (or set it as a new root).  The former
right child of P's parent becomes P's left child (or the old root
becomes P's left child if P is the new root).
Paragraph (version A):
To remove the rightmost leaf: Remove the rightmost leaf node L and
its parent node P.  If P was the root of the tree, P's left child is
now the root of the tree.  Otherwise, set the right child of P's
parent to be P's left child.
Paragraph (version A):
Note that in the rest of the protocol, the rightmost leaf will only
be removed when it is blank.
Paragraph (version B):
In addition to the path-based updates to the tree described above, it
is also necessary to add and remove leaves of the tree in order to
reflect changes to the membership of the group (see add and remove).
Since the tree is always full, adding or removing leaves corresponds
to increasing or decreasing the depth of the tree, resulting in the
number of leaves being doubled or halved.  These operations are also
known as _extending_ and _truncating_ the tree.
Paragraph (version B):
Leaves are always added and removed at the right edge of the tree.
When the size of the tree needs to be increased, a new blank root
node is added, whose left subtree is the existing tree and right
subtree is a new all-blank subtree.  This operation is typically done
when adding a member to the group.
Paragraph (version B):
When the right subtree of the tree no longer has any non-blank nodes,
it can be safely removed.  The root of the tree and the right subtree
are discarded (whether or not the root node is blank).  The left
child of the root becomes the new root node, and the left subtree
becomes the new tree.  This operation is typically done after
removing a member from the group.
Paragraph (version A):
After generating fresh key material and applying it to ratchet
forward their local tree state as described in the ratchet-tree-
evolution, 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 an UpdatePath containing a set of public keys
and encrypted path secrets for intermediate nodes in the filtered
direct path of its leaf.  The other members of the group use these
values to update their view of the tree, aligning their copy of the
tree to the sender's.
Paragraph (version B):
After generating fresh key material and applying it to ratchet
forward their local tree state as described in the ratchet-tree-
evolution, the generator broadcasts 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 an UpdatePath containing a set of public keys
and encrypted path secrets for intermediate nodes in the filtered
direct path of its leaf.  The other members of the group use these
values to update their view of the tree, aligning their copy of the
tree to the sender's.
Paragraph (version A):
The "left_hash" and "right_hash" fields hold the tree hashes of the
node's left and right children, respectively.
Paragraph (version B):
The tree hash of an entire tree corresponds to the tree hash of the
root node, which is computed recursively by starting at the leaf
nodes and building up.
Paragraph (version A):
Extend the subtree of S by adding blank leaves until it is full,
i.e., until its number of leaves is a power of 2 (see adding-and-
removing-leaves).
Paragraph (version A):
For each leaf L in "P.unmerged_leaves", blank L and remove it from
the "unmerged_leaves" sets of all parent nodes.
Paragraph (version B):
Finally, "original_sibling_tree_hash" is the tree hash of S in the
ratchet tree modified as follows: For each leaf L in
"P.unmerged_leaves", blank L and remove it from the "unmerged_leaves"
sets of all parent nodes.
Paragraph (version A):
Note that no recomputation is needed if the tree hash of S is
unchanged since the last time P was updated.  This is the case for
computing or processing a Commit whose UpdatePath traverses P, since
the Commit itself resets P.  (In other words, it is only necessary to
recompute the original sibling tree hash when validating a group's
tree on joining.)  More generally, if none of the entries in
"P.unmerged_leaves" is in the subtree under S (and thus no nodes were
truncated), then the original tree hash at S is the tree hash of S in
the current tree.
Paragraph (version B):
Note that no recomputation is needed if the tree hash of S is
unchanged since the last time P was updated.  This is the case for
computing or processing a Commit whose UpdatePath traverses P, since
the Commit itself resets P.  (In other words, it is only necessary to
recompute the original sibling tree hash when validating a group's
tree on joining.)  More generally, if none of the entries in
"P.unmerged_leaves" is in the subtree under S (and thus no leaves
were blanked), then the original tree hash at S is the tree hash of S
in the current tree.
Paragraph (version B):
A "confirmed_transcript_hash" that represents a transcript over
the whole history of Commit messages, up to and including the
signature of the most recent Commit.
Paragraph (version B):
An "interim_transcript_hash" that covers the confirmed transcript
hash plus the "confirmation_tag" of the most recent Commit.
Paragraph (version B):
New members compute the interim transcript hash using the
"confirmation_tag" field of the GroupInfo struct, while existing
members can compute it directly.
Paragraph (version B):
The injection of one or more PSKs into the key schedule is signaled
in two ways: Existing members are informed via PreSharedKey proposals
covered by a Commit, and new members added in the Commit are informed
by the GroupSecrets object in the Welcome message corresponding to
the Commit.  To ensure that existing and new members compute the same
PSK input to the key schedule, the Commit and GroupSecrets objects
MUST indicate the same set of PSKs, in the same order.
Paragraph (version B):
Each time a client injects a PSK into a group, the "psk_nonce" of its
PreSharedKeyID MUST be set to a fresh random value of length
"KDF.Nh", where "KDF" is the KDF for the ciphersuite of the group
into which the PSK is being injected.  This ensures that even when a
PSK is used multiple times, the value used as an input into the key
schedule is different each time.
Paragraph (version A):
Each application SHOULD provide a unique label to "MLS-Exporter" that
identifies its use case.  This is to prevent two exported outputs
from being generated with the same values and used for different
functionalities.
Paragraph (version A):
The exported values are bound to the group epoch from which the
"exporter_secret" is derived, hence reflects a particular state of
the group.
Paragraph (version B):
The main MLS key schedule provides an "exporter_secret" which can be
used by an application to derive new secrets for use outside of MLS.
Paragraph (version B):
The exported values are bound to the group epoch from which the
"exporter_secret" is derived, and hence reflect a particular state of
the group.
Paragraph (version B):
The main MLS key schedule provides a "resumption_psk" that is used as
a PSK to inject entropy from one epoch into another.  This
functionality is used in the reinitialization and branching processes
described in reinitialization and subgroup-branching, but may be used
by applications for other purposes.
Paragraph (version B):
The following figure shows a secret tree for a four-member group,
with the handshake and application ratchets that member D will use
for sending and the first two application keys and nonces.
Paragraph (version A):
Here, AEAD.Nn and AEAD.Nk denote the lengths in bytes of the nonce
and key for the AEAD scheme defined by the ciphersuite.
Paragraph (version B):
Where "Generation" is encoded as a big endian uint32.
Paragraph (version B):
Here, "AEAD.Nn" and "AEAD.Nk" denote the lengths in bytes of the
nonce and key for the AEAD scheme defined by the ciphersuite.
Paragraph (version A):
KeyPackages are intended to be used only once and SHOULD NOT be
reused except in the case of last resort.  (See keypackage-reuse).
Clients MAY generate and publish multiple KeyPackages to support
multiple ciphersuites.
Paragraph (version B):
KeyPackages are intended to be used only once and SHOULD NOT be
reused except in the case of last resort (see keypackage-reuse).
Clients MAY generate and publish multiple KeyPackages to support
multiple ciphersuites.
Paragraph (version B):
The signature is computed by the function "SignWithLabel" with a
label "KeyPackageTBS" and a content comprising of all of the fields
except for the signature field.
Paragraph (version A):
Verify the "leaf_node" field of the KeyPackage according to the
process defined in leaf-node-validation.
Paragraph (version B):
Verify that the "leaf_node" of the KeyPackage is valid for a
KeyPackage according to leaf-node-validation.
Paragraph (version B):
Group IDs SHOULD be constructed in such a way that there's an
overwhelmingly low probability of honest group creators generating
the same group ID, even without assistance from the Delivery Service.
For example, by making the group ID a freshly generated random value
of size "KDF.Nh".  The Delivery Service MAY attempt to ensure that
group IDs are globally unique by rejecting the creation of new groups
with a previously used ID.
Paragraph (version B):
This extension lists the extensions, proposals, and credential types
that must be supported by all members of the group.  The "default"
proposal and extension types defined in this document are assumed to
be implemented by all clients, and need not be listed in
RequiredCapabilities in order to be safely used.  Note that this is
not true for credential types.
Paragraph (version B):
For new members, support for required capabilities is enforced by
existing members during the application of Add commits.  Existing
members should of course be in compliance already.  In order to
ensure this continues to be the case even as the group's extensions
are updated, a GroupContextExtensions proposal is deemed invalid if
it contains a "required_capabilities" extension that requires non-
default capabilities not supported by all current members.
Paragraph (version B):
The "group_id", "version", "cipher_suite", and "extensions"
fields in the Welcome message MUST be the same as the
corresponding fields in the ReInit proposal.  The "epoch" in
the Welcome message MUST be 1.
Paragraph (version B):
The Welcome MUST specify a PreSharedKeyID of type "resumption"
with usage "reinit", where the "group_id" field matches the old
group and the "epoch" field indicates the epoch after the
Commit covering the ReInit.
Paragraph (version B):
Note that these three steps may be done by the same group member or
different members.  For example, if a group member sends a Commit
with an inline ReInit proposal (steps 1 and 2) but then goes offline,
another group member may recreate the group instead.  This
flexibility avoids situations where a group gets stuck between steps
2 and 3.
Paragraph (version B):
Fetch a new KeyPackage for each group member that should be
included in the subgroup.
Paragraph (version B):
Create an initial Commit message that sets up the new group and
contains a PreSharedKey proposal of type "resumption" with usage
"branch".  To avoid key re-use, the "psk_nonce" included in the
"PreSharedKeyID" object MUST be a randomly sampled nonce of length
"KDF.Nh".
Paragraph (version B):
Send the corresponding Welcome message to the subgroup members.
Paragraph (version B):
The "version" and "ciphersuite" values in the Welcome are the same
as those used by the old group.
Paragraph (version B):
The "epoch" in the Welcome message MUST be 1.
Paragraph (version A):
On receiving an MLSMessageContent containing a Proposal, a client
MUST verify the signature inside MLSMessageAuth.  If the signature
verifies successfully, then the Proposal should be cached in such a
way that it can be retrieved by hash (as a ProposalOrRef object) in a
later Commit message.
Paragraph (version A):
An Add proposal requests that a client with a specified KeyPackage be
added to the group.  The proposer of the Add MUST verify the validity
of the KeyPackage, as specified in keypackage-validation.
Paragraph (version B):
On receiving an MLSMessageContent containing a Proposal, a client
MUST verify the signature inside MLSMessageAuth and that the "epoch"
field of the enclosing MLSMessageContent is equal to the "epoch"
field of the current GroupContext object.  If the verification is
successful, then the Proposal should be cached in such a way that it
can be retrieved by hash (as a ProposalOrRef object) in a later
Commit message.
Paragraph (version B):
An Add proposal is invalid if the KeyPackage is invalid according to
keypackage-validation.
Paragraph (version A):
Validate the KeyPackage as specified in keypackage-validation.
The "leaf_node_source" field in the LeafNode MUST be set to
"key_package".
Paragraph (version A):
Validate the LeafNode as specified in leaf-node-validation.  The
"leaf_node_source" field MUST be set to "update".
Paragraph (version A):
Verify that the "encryption_key" value in the LeafNode is
different from the corresponding field in the LeafNode being
replaced.
Paragraph (version B):
An Update proposal is invalid if the LeafNode is invalid for an
Update proposal according to leaf-node-validation.
Paragraph (version B):
A Remove proposal is invalid if the "removed" field does not identify
a non-blank leaf node.
Paragraph (version B):
Truncate the tree by removing the right subtree until there is at
least one non-blank leaf node in the right subtree.  If the
rightmost non-blank leaf has index L, then this will result in the
tree having "2^d" leaves, where "d" is the smallest value such
that "2^d > L".
Paragraph (version A):
The "psktype" of the pre-shared key MUST be "external" and the
"psk_nonce" MUST be a randomly sampled nonce of length "KDF.Nh".
When processing a Commit message that includes one or more
PreSharedKey proposals, group members derive "psk_secret" as
described in pre-shared-keys, where the order of the PSKs corresponds
to the order of the "PreSharedKey" proposals in the Commit.
Paragraph (version B):
The "psk_nonce" is not of length "KDF.Nh".
Paragraph (version B):
The "psk_nonce" MUST be randomly sampled.  When processing a Commit
message that includes one or more PreSharedKey proposals, group
members derive "psk_secret" as described in pre-shared-keys, where
the order of the PSKs corresponds to the order of the "PreSharedKey"
proposals in the Commit.
Paragraph (version A):
If a ReInit proposal is included in a Commit, it MUST be the only
proposal referenced by the Commit.  If other non-ReInit proposals
have been sent during the epoch, the committer SHOULD prefer them
over the ReInit proposal, allowing the ReInit to be resent and
applied in a subsequent epoch.  The "version" field in the ReInit
proposal MUST be no less than the version for the current group.
Paragraph (version B):
A ReInit proposal is invalid if the "version" field is less than the
version for the current group.
Paragraph (version A):
If the new extensions include a "required_capabilities" extension,
verify that all members of the group support the required
capabilities (including those added in the same commit, and
excluding those removed).
Paragraph (version B):
A GroupContextExtensions proposal is invalid if it includes a
"required_capabilities" extension and some members of the group do
not support some of the required capabilities (including those added
in the same commit, and excluding those removed).
Paragraph (version B):
The other case, indicated by a "new_member_proposal" SenderType is
useful when existing members of the group can independently authorize
the addition of an MLS client proposing it be added to the group.
External proposals which are not authorized are considered invalid.
Paragraph (version B):
The "external_senders" extension is a group context extension that
contains the credentials and signature keys of senders that are
permitted to send external proposals to the group.
Paragraph (version A):
The sender of a Commit MUST include all valid proposals that it has
received during the current epoch.  Invalid proposals include, for
example, proposals with an invalid signature or proposals that are
semantically invalid, such as an Add when the sender does not have
the application-level permission to add new users.  Proposals with a
non-default proposal type MUST NOT be included in a commit unless the
proposal type is supported by all the members of the group that will
process the Commit (i.e., not including any members being added or
removed by the Commit).
Paragraph (version A):
If there are multiple proposals that apply to the same leaf, or
multiple PreSharedKey proposals that reference the same
PreSharedKeyID, the committer MUST choose one and include only that
one in the Commit, considering the rest invalid.  The committer MUST
NOT include any Update proposals generated by the committer, since
they would be duplicative with the "path" field in the Commit.  The
committer MUST prefer any Remove received, or the most recent Update
for the leaf if there are no Removes.  If there are multiple Add
proposals containing KeyPackages that the committer considers to
represent the same client or a client already in the group (for
example, identical KeyPackages or KeyPackages sharing the same
Credential), the committer again chooses one to include and considers
the rest invalid.  The committer MUST consider invalid any Add or
Update proposal if the Credential in the contained KeyPackage shares
the same signature key with a Credential in any leaf of the group, or
if the LeafNode in the KeyPackage shares the same "encryption_key"
with another LeafNode in the group.
Paragraph (version A):
The Commit MUST NOT combine proposals sent within different epochs.
Due to the asynchronous nature of proposals, receivers of a Commit
SHOULD NOT enforce that all valid proposals sent within the current
epoch are referenced by the next Commit.  In the event that a valid
proposal is omitted from the next Commit, and that proposal is still
valid in the current epoch, the sender of the proposal MAY resend it
after updating it to reflect the current epoch.
Paragraph (version B):
A sender and a receiver of a Commit MUST verify that the committed
list of proposals is valid as specified in proposal-list-validation.
A list is invalid if, for example, it includes an Update and a Remove
for the same member, or an Add when the sender does not have the
application-level permission to add new users.
Paragraph (version B):
The sender of a Commit SHOULD include all proposals that it has
received during the current epoch, that are valid according to the
rules for their proposal types and according to application policy,
as long as this results in a valid proposal list.
Paragraph (version B):
Due to the asynchronous nature of proposals, receivers of a Commit
SHOULD NOT enforce that all valid proposals sent within the current
epoch are referenced by the next Commit.  In the event that a valid
proposal is omitted from the next Commit, and that proposal is still
valid in the current epoch, the sender of the proposal MAY resend it
after updating it to reflect the current epoch.
Paragraph (version B):
When creating or processing a Commit, a client updates the ratchet
tree and GroupContext for the group.  These values advance from an
"old" state reflecting the current epoch to a "new" state reflecting
the new epoch initiated by the Commit.  When the Commit includes an
UpdatePath, a "provisional" group context is constructed that
reflects changes due to the proposals and UpdatePath, but with the
old confirmed transcript hash.
Paragraph (version B):
Verify that the list of proposals to be committed is valid as
specified in proposal-list-validation.
Paragraph (version A):
If populating the "path" field: Create an UpdatePath using the
provisional ratchet tree and GroupContext.  Any new member (from
an Add proposal) MUST be excluded from the resolution during the
computation of the UpdatePath.  The "leaf_node" for this
UpdatePath MUST have "leaf_node_source" set to "commit".  Note
that the LeafNode in the "UpdatePath" effectively updates an
existing LeafNode in the group and thus MUST adhere to the same
restrictions as LeafNodes used in "Update" proposals (aside from
"leaf_node_source").
Paragraph (version B):
If not populating the "path" field: Set the "path" field in the
Commit to the null optional.  Define "commit_secret" as the all-
zero vector of length "KDF.Nh" (the same length as a "path_secret"
value would be).
Paragraph (version B):
Verify that the "proposals" vector is valid as specified in
proposal-list-validation.
Paragraph (version B):
Verify that all PreSharedKey proposals in the "proposals" vector
are available.
Paragraph (version B):
Define "commit_secret" as the value "path_secret[n+1]" derived
from the last path secret value ("path_secret[n]") derived for
the UpdatePath.
Paragraph (version B):
New members MUST verify that "group_id" is unique among the groups
they're currently participating in.
Paragraph (version A):
There MUST be a single ExternalInit proposal.
Paragraph (version A):
There MAY be a single Remove proposal, where the LeafNode in
the "path" field MUST meet the same criteria as the LeafNode in
an Update for the removed leaf (see update).  In particular,
the "credential" in the LeafNode MUST present a set of
identifiers that is acceptable to the application for the
removed participant.
Paragraph (version A):
There MAY be one or more PreSharedKey proposals.
Paragraph (version A):
There MUST NOT be any other proposals.
Paragraph (version A):
The sender of a Commit message is responsible for sending a single
Welcome message to all the new members added via Add proposals.  The
Welcome message provides the new members with the current state of
the group, after the application of the Commit message.  The new
members will not be able to decrypt or verify the Commit message, but
will have the secrets they need to participate in the epoch initiated
by the Commit message.
Paragraph (version A):
Decrypt the "encrypted_group_secrets" using HPKE with the
algorithms indicated by the ciphersuite and the HPKE private key
corresponding to the GroupSecrets.  If a "PreSharedKeyID" is part
of the GroupSecrets and the client is not in possession of the
corresponding PSK, return an error.
Paragraph (version B):
If a "PreSharedKeyID" is part of the GroupSecrets and the client
is not in possession of the corresponding PSK, return an error.
Additionally, if a "PreSharedKeyID" has type "resumption" with
usage "reinit" or "branch", verify that it is the only such PSK.
Paragraph (version B):
Verify the signature on the GroupInfo object.  The signature input
comprises all of the fields in the GroupInfo object except the
signature field.  The public key is taken from the LeafNode of the
ratchet tree with leaf index "signer".  If the node is blank or if
signature verification fails, return an error.
Paragraph (version B):
Verify that the "group_id" is unique among the groups that the
client is currently participating in.
Paragraph (version B):
Verify that the tree hash of the ratchet tree matches the
"tree_hash" field in GroupInfo.
Paragraph (version B):
Identify a leaf whose LeafNode is identical to the one in the
KeyPackage.  If no such field exists, return an error.  Let
"my_leaf" represent this leaf in the tree.
Paragraph (version B):
If a "PreSharedKeyID" was used that has type "resumption" with
usage "reinit" or "branch", verify that the "epoch" field in the
GroupInfo is equal to 1.
Paragraph (version B):
For usage "reinit", verify that the last Commit to the
referenced group contains a ReInit proposal and that the
"group_id", "version", "cipher_suite", and
"group_context.extensions" fields of the GroupInfo match the
ReInit proposal.  Additionally, verify that all the members of
the old group are also members of the new group, according to
the application.
Paragraph (version B):
For usage "branch", verify that the "version" and
"cipher_suite" of the new group match those of the old group,
and that the members of the new group compose a subset of the
members of the old group, according to the application.
Paragraph (version B):
By default, a GroupInfo message only provides the joiner with a hash
of the group's ratchet tree.  In order to process or generate
handshake messages, the joiner will need to get a copy of the ratchet
tree from some other source.  (For example, the DS might provide a
cached copy.)  The inclusion of the tree hash in the GroupInfo
message means that the source of the ratchet tree need not be trusted
to maintain the integrity of tree.
Paragraph (version B):
Each entry in the "ratchet_tree" vector provides the value for a node
in the tree, or the null optional for a blank node.
Paragraph (version B):
A group member creating a commit and a group member processing a
commit MUST verify that the list of committed proposals is valid
using one of the following procedures, depending on whether the
commit is external or not.
Paragraph (version B):
It contains an individual proposal that is invalid as specified in
proposals.
Paragraph (version B):
It contains an Update proposal generated by the committer.
Paragraph (version B):
It contains a Remove proposal that removes the committer.
Paragraph (version B):
It contains multiple Update and/or Remove proposals that apply to
the same leaf.  If the committer has received multiple such
proposals they SHOULD prefer any Remove received, or the most
recent Update if there are no Removes.
Paragraph (version B):
It contains multiple Add proposals that contain KeyPackages that
represent the same client according to the application (for
example, identical signature keys).
Paragraph (version B):
It contains an Add proposal with a KeyPackage that represents a
client already in the group according to the application, unless
there is a Remove proposal in the list removing the matching
client from the group.
Paragraph (version B):
It contains multiple PreSharedKey proposals that reference the
same PreSharedKeyID.
Paragraph (version B):
It contains multiple GroupContextExtensions proposals.
Paragraph (version B):
It contains a ReInit proposal together with any other proposal.
If the committer has received other proposals during the epoch,
they SHOULD prefer them over the ReInit proposal, allowing the
ReInit to be resent and applied in a subsequent epoch.
Paragraph (version B):
It contains an ExternalInit proposal.
Paragraph (version B):
It contains a proposal with a non-default proposal type that is
not supported by some members of the group that will process the
Commit (i.e., members being added or removed by the Commit do not
need to support the proposal type).
Paragraph (version B):
After processing the commit the ratchet tree is invalid, in
particular, if it contains any leaf node that is invalid according
to leaf-node-validation.
Paragraph (version B):
An application may extend the above procedure by additional rules,
for example, requiring application-level permissions to add members,
or rules concerning non-default proposal types.
Paragraph (version B):
At most one Remove proposal, with which the joiner removes an old
version of themselves.  If a Remove proposal is present, then the
LeafNode in the "path" field of the external commit MUST meet the
same criteria as would the LeafNode in an Update for the removed
leaf (see update).  In particular, the "credential" in the
LeafNode MUST present a set of identifiers that is acceptable to
the application for the removed participant.
Paragraph (version B):
Zero or more PreSharedKey proposals.
Paragraph (version B):
No other proposals.
Paragraph (version B):
Commit messages do not have an extension field because the set of
proposals is extensible.  As discussed in commit, Proposals with a
non-default proposal type MUST NOT be included in a commit unless the
proposal type is supported by all the members of the group that will
process the Commit.
Paragraph (version B):
In order to ensure that MLS provides meaningful authentication it is
important that each member is able to authenticate some identity
information for each other member.  Identity information is encoded
in Credentials, so this property is provided by ensuring that members
use compatible credential types.
Paragraph (version B):
In future extensions to MLS, it may be useful to allow a member to
present more than one credential.  For example, such credentials
might present different attributes attested by different authorities.
To be consistent with the general principle stated at the beginning
of this section, such an extension would need to ensure that each
member can authenticate some identity for each other member.  For
each pair of members (Alice, Bob), Alice would need to present at
least one credential of a type that Bob supports.
Paragraph (version B):
This need for sequencing is not a problem as long as each time a
group member sends a Commit message, it is based on the most current
state of the group.  In practice, however, there is a risk that two
members will generate Commit messages simultaneously based on the
same state.
Paragraph (version B):
The primary purpose of handshake messages are to provide an
authenticated group key exchange to clients.  In order to protect
application messages sent among the members of a group, the
"encryption_secret" provided by the key schedule is used to derive a
sequence of nonces and keys for message encryption.  Every epoch
moves the key schedule forward which triggers the creation of a new
secret tree, as described in secret-tree, along with a new set of
symmetric ratchets of nonces and keys for each member.
Paragraph (version B):
Each client maintains their own local copy of the key schedule for
each epoch during which they are a group member.  They derive new
keys, nonces, and secrets as needed while deleting old ones as soon
as they have been used.
Paragraph (version B):
The group identifier and epoch allow a recipient to know which group
secrets should be used and from which "epoch_secret" to start
computing other secrets.  The sender identifier and content type is
used to identify which symmetric ratchet to use from the secret tree.
The "generation" counter determines how far into the ratchet to
iterate in order to produce the required nonce and key for encryption
or decryption.
Paragraph (version B):
Application messages MAY be padded to provide some resistance against
traffic analysis techniques over encrypted traffic CLINIC HCJ16.
While MLS might deliver the same payload less frequently across a lot
of ciphertexts than traditional web servers, it might still provide
the attacker enough information to mount an attack.  If Alice asks
Bob "When are we going to the movie?", then the answer "Wednesday"
could be leaked to an adversary solely by the ciphertext length.
Paragraph (version B):
During each epoch, senders MUST NOT encrypt more data than permitted
by the security bounds of the AEAD scheme used I-D.irtf-cfrg-aead-
limits.
Paragraph (version B):
Since each application message contains the group identifier, the
epoch, and a generation counter, a client can receive messages out of
order.  When messages are received out of order, the client moves the
sender ratchet forward to match the received generation counter.  Any
unused nonce and key pairs from the ratchet are potentially stored so
that they can be used to decrypt the messages which were delayed or
reordered.
Paragraph (version B):
Forward-secrecy between epochs is provided by deleting private keys
from past versions of the ratchet tree, as this prevents old group
secrets from being re-derived.  Forward secrecy _within_ an epoch is
provided by deleting message encryption keys once they've been used
to encrypt or decrypt a message.
Paragraph (version B):
An application MAY allow for reuse of a "last resort" KeyPackage in
order to prevent denial-of-service attacks.  Since a KeyPackage is
needed to add a client to a new group, an attacker could prevent a
client being added to new groups by exhausting all available
KeyPackages.  To prevent such a denial-of-service attack, the
KeyPackage publication system SHOULD rate-limit KeyPackage requests,
especially if not authenticated.
Paragraph (version B):
It is possible for a malicious member of a group to "fragment" the
group by crafting an invalid UpdatePath.  Recall that an UpdatePath
encrypts a sequence of path secrets to different subtrees of the
group's ratchet trees.  These path secrets should be derived in a
sequence as described in ratchet-tree-evolution, but the UpdatePath
syntax allows the sender to encrypt arbitrary, unrelated secrets.
The syntax also does not guarantee that the encrypted path secret for
a given node corresponds to the public key provided for that node.
Paragraph (version B):
Both of these types of corruption will cause processing of a Commit
to fail for some members of the group.  If the public key for a node
does not match the path secret, then the members that decrypt that
path secret will reject the commit based on this mismatch.  If the
path secret sequence is incorrect at some point, then members that
can decrypt nodes before that point will compute a different public
key for the mismatched node than the one in the UpdatePath, which
also causes the Commit to fail.  Applications SHOULD provide
mechanisms for failed commits to be reported, so that group members
who were not able to recognize the error themselves can reinitialize
the group if necessary.
Paragraph (version B):
Even with such an error reporting mechanism in place, however, it is
still possible for members to get locked out of the group by a
malformed commit.  Since malformed Commits can only be recognized by
certain members of the group, in an asynchronous application, it may
be the case that all members that could detect a fault in a Commit
are offline.  In such a case, the Commit will be accepted by the
group, and the resulting state possibly used as the basis for further
Commits.  When the affected members come back online, they will
reject the first commit, and thus be unable to catch up with the
group.  These members will either need to add themselves back with an
external Commit, or reinitialize the group from scratch.