Draft: draft-ietf-mls-protocol

Commentary: Depends on This PR implements the suggestion from in , taking into account the discussion on the issue and on the call last week . There are a bunch of small changes ( e.g. , changing a lot of to ) , but the critical change is the following split of KeyPackage into LeafNode and KeyPackage : This does a few things we had discussed : KeyPackage is the thing you pre - publish ; LeafNode is what goes in the tree Sign the LeafNode and the KeyPackage separately , nesting the LeafNode inside the signed KeyPackage information  the when possible . ( This PR takes the chattiest approach , replicating the in each leaf . ) Promote mandatory extensions to fields Add continues to refer to KeyPackage . Update and Commit use LeafNode directly . I did a walk through the document grepping for KeyPackage , and I think I found all the instances that needed to be changed . But some review to make sure I did n't miss anything would be appreciated . 
 I have n't done a full review yet , but one thing to note is that if a member commits an Update proposal by another member , there should be a , but there ca n't be a . One way to tackle that issue would be to remove the notion of an update proposal entirely such that key material can only be updated via commit . I have no data to back it up , of course , but I doubt that there are going to be many cases where clients will issue update proposals without actually committing them themselves . 
 I think this is already taken into account : Maybe it would be clearer to say MUST be the zero - length octet string . Or make and two independent optionals ( this is a bit messy with the TLS syntax , but doable ) . I kind of liked the MembershipInfo approach because it gave you a clean signal of whether the LeafNode was a prepublished one or not . 
 After the virtual interim today , I did a little thinking on how to structure these objects to take into account point about having the signature be lifetime - constrained in the prepublished case . I think we all agree on which fields should appear in which cases : ( Where the Credential / Capabilities in KeyPackage might be indirect , via containing a pre - pub LeafNode . ) So it 's mainly a question of how we lay this out syntactically . A couple of options follow . I have not split the Update and Commit cases ; they would differ only in whether the field has a value . Personally , I feel pretty strongly that Option 1 will be easier to work with . Option 2 basically means that there are three types of tree node , and two different algorithms for verifying leaf signatures . ( ! ) That seems like a bigger distinction than is merited here , given that the two types of leaf have mostly the same content . Having selects here is helpful , though . ( Once again ! ) In addition to avoiding the chattiness of replicating the everywhere ( since now there 's a clear signal of when to inject the as context ) , it provides a simple way to check that a LeafNode has the right contents for a given context in the protocol . In either case , we will need to be clear about how the field should be interpreted in a pre - published LeafNode . I got the impression from the call today that there was agreement that the limitation here was purely advisory , i.e. , that we would not require that a client reject as invalid a leaf with an expired . I 'm OK with that , but it does seem like it reduces the utility of including the lifetime in the leaf node . 
 I think I also prefer the first option ! This refactor looks great , it makes things about leaf content a lot more clear . I also like the new consistency between the names and . I think we should advise to reject expired LeafNode when adding them to the tree ( and also check that they have the same lifetime as the corresponding KeyPackage ? ) However when joining a group , we should not reject a tree containing expired leaf nodes ( as they might have been when they were not expired ) 
 - Thanks for the feedback . With regard to when expired LeafNodes should be rejected -- With the scheme you propose , why does the  need to be in the LeafNode instead of the KeyPackage ? Since the KeyPackage is in the Add , having the lifetime there would still allow it to be validated at add time . And if you 're not going to do anything with the lifetime after add time , why do you need it to still be in the tree ? 
 The latest commit updates the PR to match Option 1 . It still seems awkward to have the Lifetime in the LeafNode . 
 I think I also prefer the first option . On lifetimes :  are the only thing that allow us to get some degree of PCS for pre - published key material . If  is compromised and  wo n't check lifetimes if he is invited into a group that includes  , then the adversary can impersonate  towards  indefinitely after compromise : He creates a group using  's KeyPackage and invites  . Of course there are a few caveats to this attack : the credential could have its own lifetime a higher - level signature key could have been rotated in the meantime , leading to  's check with the AS to fail the credential could be revoked ( with a mandatory stapling scheme that implements a lifetime ) depending on how the application handles " duplicated " groups , this might or might not work if  already has a 1 - to-1 group with  although the adversary could just include another party in the group , weakening the attack slightly 
 Given the two options , I also prefer option 1 . That being said , it still bothers me that we now have two signatures instead of one , which doubles the creation time for large groups . 
 note that Option 2 does avoid double signature . But in my experience , the slow thing about MLS in large groups is not so much the cryptography ( signature or otherwise ) , but the moving around of the large tree . 
 - To refine your analysis a bit -- the interesting case here is when the  's HPKE private key is compromised , but not her signature key . If her signature key is compromised , then all bets are off ; the attacker can impersonate  in any way , until the AS invalidates the signature key ( e.g. , via credential expiry / revocation ) . If we take on that constraint , then your attack actually does n't work . The attacker ca n't create sign a GroupInfo message that  will accept . The best the attacker could do is create a group with the attacker ,  , and  , such that if  removes the attacker , the attacker still has access to the group via knowledge of  's HPKE private key ( since  's UpdatePath will TreeKEM - encrypt to  's compromised HPKE key ) . And here the attacker is pretty limited , since they ca n't send any MLSPlaintext / MLSCiphertext signatures . But at that point , it seems like you 're in the same posture for pre - published keys as for keys in the group . Any leaf signed by  can be used in that attack in the same way . So it seems like the answer for PCS in any case is " Update / Commit " ? 
 you 're right , the attack does n't quite work the way I thought it would . It all depends a bit on what the AS does . If the app naively checks the signature based on the key package ( credential ) provided in the Welcome , the attack has a chance . Although I admit that 's quite an oversight on part of the AS / app , the lifetime would be useful here and add some robustness . Similarly , if the app uses per - group credentials that do n't expire , the attack still works . If we had lifetimes , the client could recover by updating all groups and cycling the key packages that were uploaded to the server . This is again assuming the AS does n't invalidate credentials after a while . Good point also about the adversary being able to just pluck key packages from any given group at the time of compromise . Those would have to be fitted with lifetimes as well to make this work . I still like lifetimes as a conservative , defense - in - depth approach in case the AS is very minimal or just not very good . I think as a general rule , it 's good to attach an expiration date to key material that is not single - use . That being said , I understand that due to the asynchronicity in many use - cases it 's going to be hard to enforce in a meaningful way that does n't impede usability too much . 
 Discussion at virtual interim : points out that a  might be processed some time after it is created , so the lifetime might have expired notes that a lifetime in the leaf node at least provides a signal as to how old the leaf is ( being more useful than here , since it roughly corresponds to creation time )  ( Implement the following , then merge : Leave lifetime in LeafNode MUST verify lifetime when creating an Add from a prepublished KP SHOULD verify lifetime when receive an Add ( modulo time skew between send and receive ) SHOULD NOT enforice lifetime when receiving a tree It 's also possible to put a " last updated epoch " in the leaf alongside the group ID , useful ? Does n't obviously correspond to time , use cases not obvious No action for now 
 s currently have two usages in the spec : -published key packages that you can download and put in a leaf when adding a new member  content that you update when doing a full Currently , things have been designed to use only one structure for both uses , leading to some weirdness such as : and are useful in the usage 1 , but are parameters of the group in usage 2 . is a good name in usage 1 , but would better be named in usage 2 . some data is important in usage 2 and have no meaning in usage 1 , hence they were designed as " mandatory - when - updating extensions " ( such as , and we were also discussing about adding the in to help prevent cross - group attacks ) How about using different structures for these ? // Essentially identical to KeyPackage struct { ProtocolVersion version ; CipherSuite ciphersuite ; HPKEPublicKey hpkeinitkey ; opaque endpointid<0 .. 255 ; Credential credential ; Extension extensions<8 .. 2 ^ 32 - 1 ; opaque signature<0 .. 2 ^ 16 - 1 ; } PrePublishedKeyPackage ; struct { HPKEPublicKey hpkekey ; opaque endpointid<0 .. 255 ; Credential credential ; opaque parenthash<0 .. 255 ; opaque groupid<0 .. 255 ; Extension extensions<8 .. 2 ^ 32 - 1 ; opaque signature<0 .. 2 ^ 16 - 1 ; } LeafNodeContent ; enum { reserved(0 ) , prepublishedkeypackage(1 ) , leafnodecontent(2 ) , ( 255 ) } LeafNodeType ; struct LeafNode { // This is not included in the signature , but with the " unambiguous signature " PR that 's not a problem LeafNodeType type ; select ( ) { case prepublishedkeypackage : PrePublishedKeyPackage data ; case leafnode_content : LeafNodeContent data ; } } What would you think about refactoring like this ? 
 If we 're doing this , I 'd suggest including a dedicated public key to encrypt Welcome messages to for the . suggested this at some point . It gives us better FS guarantees , because the receiver of the can delete the corresponding private key immediately after decryption , while they might have to keep the other key around for a bit until they can actually issue an update . This might also help clarify . 
 Ties into as well because is an example of an extension that is required and should be a field instead 
 one concern I have is how is that field in created ? 
 When sending a full , the participant includes a new in the ( it replaces the field ) . It computes the signature with where : corresponds to all the fields of except ( the usual thing ) comes from 
 What about the scenario where  pulls down  's key package to add him to the group ? In that case  ca n't do the conversion between formats on  's behalf . 
  do n't have to do the conversion , this is why either contain a ( when adding someone to the group ) or a ( when updating your own key package ) . 
 Ah somehow I missed that part in your original post . Makes sense 
 I like this general direction . It solves a few things . Instead of having leaf nodes contain either a prepublished thing or a normal leaf node , how about having the prepublished thing wrap the leaf node thing ? As a bonus , we get separation between the key pair used for Welcome encryption and the key pair that goes in the leaf for TreeKEM . Then when  adds  , she extracts his LeafNodeContent and sends that in the Add . That does n't let the other members of the group verify that  did the right thing w.r.t .  , but I 'm not heartbroken about that . We might also be able to remove the extensions field from the outer ( KeyPackage ) wrapping if we turn the extension into KeyPackage field . I expect we probably need leaf extensions , but would be open to making into a field . 
 Why not keep sending the KeyPackage in the Add ? That would n't cost much and would allow everyone to validate the lifetime . Note , that the lifetime is crucial in achieving PCS for pre - published key material . If I compromise  and get hold of the private key material of a bunch of his KeyPackages , I can add "  " to any group I want as long as the Credential inside the LeafNodeContent remains valid and  does n't revoke the LeafNodeContent somehow . Also , we 'll have to do this if we want to enable any member to send the Welcome message as you proposed here : 
 I agree that including inside is quite clean , but something is bothering me with this approach : I 'd like that the is signed by the participant occupying that leaf , to protect against cross - group attacks . In the first message of this issue I did this by including in , but in fact we do n't need to store it in the leaf , we just need to include it in the . In any case , if we want to include in the signature , we need a way to differentiate between leaves that triggered a commit and the ones that are " fresh " . And for that I think using a tagged - union struct is the cleanest way to do it . 
 It always feels a bit lazy to use an extension ( and implementations have to be careful to check for their presence ) , but we could use a extension in the same way that we use a extension . You will find both of them only on LeafContents that were updated at least once . Another result of including  in the KeyPackage is that the signature will explicitly sign the and I 'm not sure if that does n't completely destroy any deniability guarantees we can still get from MLS . 
 I do n't think there 's really a good way to get the into the leaf . Because of prepublication , it would have to be optional , but in order to prevent attacks , you need rules to decide whether it 's required in a given situation . It seems like the best you can do is basically TOFU : When you first get the tree , you ca n't validate whether the groupid should be in a given leaf or not Once you 're a member , you require that groupid is sent in Update and UpdatePath.leafkey_package I could live with a solution of that character , but given it 's a bit complex , might prefer to leave it out unless we get a useful property from it . - Re : Sending KeyPackage in Add - Yep , that makes sense . So you would send KeyPackage in Add , and LeafNode in Update . 
 I tried to worked through what would be necessary to implement this . It looks like it would require some fairly invasive changes to be made to rationalize the document 's structure before making this change . But those changes should probably be made anyway . Proposal : Preparatory PR : Rationalize layout Move " Cryptograhic Objects " to before " Ratchet Trees " Update " Ratchet Tree Nodes " to define ParentNode and LeafNode structs , with LeafNode initially just equal to KeyPackage Move subsections of " Key Packages " to under " Ratchet Trees " Parent Hash Tree Hashes Update Paths Move Group State to its own top - level section Main PR : Change LeafNode to have the required content : HPKE key for TreeKEM Credential Parent hash Capabilities Extensions Signature Change KeyPackage to refer to LeafNode for internals , plus : HPKE key for Welcome Lifetime Extensions Signature Add continues to send KeyPackage Update and Commit send LeafNode instead of KeyPackage Remove and Sender refer to LeafNodeRef instead of KeyPackageRef We should probably do the preparatory PR regardless of this issue . 
 Discussion on working call : General agreement on the approach Should sign group_id when possible Include in TBS if we at least have a flag for pre - published / in - group Or we can store it Or we could have independent structs for pre - published / in - group leaves Do we need extensions at both levels ? Capabilities / lifetime should probably be promoted to fields
Paragraph (version B):
The signature algorithm specified in the ciphersuite is the mandatory
algorithm to be used for signatures in MLSMessageAuth and the tree
signatures.  It MUST be the same as the signature algorithm specified
in the credentials in the leaves of the tree (including the leaf node
information in KeyPackages used to add new members).
Paragraph (version B):
For a KeyPackageRef, the "value" input is the encoded KeyPackage, and
the ciphersuite specified in the KeyPackage determines the KDF used.
For a LeafNodeRef, the "value" input is the LeafNode object for the
leaf node in question.  For a ProposalRef, the "value" input is the
MLSMessageContentAuth carrying the proposal.  In the latter two
cases, the KDF is determined by the group's ciphersuite.
Paragraph (version A):
A BasicCredential is a raw, unauthenticated assertion of an identity/
key binding.  The format of the key in the "public_key" field is
defined by the relevant ciphersuite: the group ciphersuite for a
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 the uncompressed Elliptic-
Curve-Point-to-Octet-String conversion according to SECG.
Paragraph (version B):
A BasicCredential is a raw, unauthenticated assertion of an identity/
key binding.  The format of the key in the "public_key" field is
defined by the relevant ciphersuite: the group ciphersuite for a
credential in a leaf node of a ratchet tree or 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
the uncompressed Elliptic-Curve-Point-to-Octet-String conversion
according to SECG.
Paragraph (version A):
Internally to the the protocol, group members are uniquely identified
by their leaves, expressed as KeyPackageRef objects.  These
identifiers are unstable: They change whenever the member sends a
Commit, or whenever an Update proposal from the member is committed.
Paragraph (version B):
Internally to the the protocol, group members are uniquely identified
by their leaves, expressed as LeafNodeRef objects.  These identifiers
are unstable: They change whenever the member sends a Commit, or
whenever an Update proposal from the member is committed.
Paragraph (version A):
The application may also provide application-specific unique
identifiers in the "extensions" field of KeyPackage object.
Paragraph (version B):
The application may also provide application-specific unique
identifiers in the "extensions" field of KeyPackage or LeafNode
objects.
Paragraph (version A):
The two main structures involved are MLSPlaintext and MLSCiphertext.
MLSCiphertext represents a signed and encrypted message, with
protections for both the content of the message and related metadata.
MLSPlaintext represents a message that is only signed, and not
encrypted.  Applications MUST use MLSCiphertext to encrypt
application messages and SHOULD use MLSCiphertext to encode handshake
messages, but MAY transmit handshake messages encoded as MLSPlaintext
objects in cases where it is necessary for the Delivery Service to
examine such messages.
Paragraph (version B):
The main structure is MLSMessageContent, which contains the content
of the message.  This structure is authenticated using MLSMessageAuth
(see content-authentication).  The two structures are combined in
MLSMessageContentAuth, which can then be encoded/decoded from/to
MLSPlaintext or MLSCiphertext, which are then included in the
MLSMessage structure.
Paragraph (version B):
MLSCiphertext represents a signed and encrypted message, with
protections for both the content of the message and related metadata.
MLSPlaintext represents a message that is only signed, and not
encrypted.  Applications MUST use MLSCiphertext to encrypt
application messages and SHOULD use MLSCiphertext to encode handshake
messages, but MAY transmit handshake messages encoded as MLSPlaintext
objects in cases where it is necessary for the Delivery Service to
examine such messages.
Paragraph (version A):
The field "confirmation_tag" MUST be present if "content_type" equals
commit.  Otherwise, it MUST NOT be present.
Paragraph (version A):
Encrypt the sender data using a key and nonce derived from the
"sender_data_secret" for the epoch and a sample of the encrypted
MLSCiphertextContent.
Paragraph (version A):
Decryption is done by decrypting the sender data, then the message,
and then verifying the content signature.
Paragraph (version A):
Note that the "membership_tag" only needs to be computed for
MLSPlaintext messages that will be sent over the wire ("wire_format
== mls_plaintext").  It isn't needed for messages that will be
encrypted and transmitted as MLSCiphertext messages ("wire_format ==
mls_ciphertext").
Paragraph (version B):
The following structure is used to fully describe the data
transmitted in plaintexts or ciphertexts.
Paragraph (version B):
MLSMessageContent is authenticated using the MLSMessageAuth
structure.
Paragraph (version B):
The "signature" field in an MLSMessageAuth object is computed using
the signing private key corresponding to the public key, which was
authenticated by the credential at the leaf of the tree indicated by
the sender field.  The signature is computed using "SignWithLabel"
with label ""MLSMessageContentTBS"" and with a content that covers
the message content and the wire format that will be used for this
message.  If the sender is a member of the group, the content also
covers the GroupContext for the current epoch, so that signatures are
specific to a given group and epoch.
Paragraph (version B):
The confirmation tag value confirms that the members of the group
have arrived at the same state of the group.
Paragraph (version B):
A MLSMessageAuth is said to be valid when both the "signature" and
"confirmation_tag" fields are valid.
Paragraph (version B):
Plaintexts are encoded using the MLSPlaintext structure.
Paragraph (version B):
When decoding a MLSPlaintext into a MLSMessageContentAuth, the
application MUST check "membership_tag", and MUST check that the
MLSMessageAuth is valid.
Paragraph (version B):
Ciphertexts are encoded using the MLSCiphertext structure.
Paragraph (version B):
"encrypted_sender_data" and "ciphertext" are encrypted using the AEAD
function specified by the ciphersuite in use, using as input the
structures MLSSenderData and MLSCiphertextContent.
Paragraph (version B):
The ciphertext content is encoded using the MLSCiphertextContent
structure.
Paragraph (version B):
When decoding a MLSCiphertextContent, the application MUST check that
the MLSMessageAuth is valid.
Paragraph (version B):
When parsing a SenderData struct as part of message decryption, the
recipient MUST verify that the LeafNodeRef indicated in the "sender"
field identifies a member of the group.
Paragraph (version B):
The "public_key" field contains a HPKE public key whose private key
is held only by the members at the leaves among its descendants.  The
"parent_hash" field contains a hash of this node's parent node, as
described in parent-hash.  The "unmerged_leaves" field lists the
leaves under this parent node that are unmerged, according to their
indices among all the leaves in the tree.
Paragraph (version B):
The "public_key" field conains an HPKE public key whose private key
is held only by the member occupying this leaf.  The "credential"
contains authentication information for this member, as described in
credentials.
Paragraph (version B):
The "capabilities" field indicates what protocol versions,
ciphersuites, protocol extensions, and non-default proposal types are
supported by a client.  Proposal types defined in this document are
considered "default" and thus need not be listed.  Extensions that
appear in the "extensions" field of a LeafNode MUST be included in
the "extensions" field of the "capabilities" field.
Paragraph (version B):
The "leaf_node_source" field indicates how this LeafNode came to be
added to the tree.
Paragraph (version B):
In the case where the leaf was added to the tree based on a pre-
published KeyPackage, the "lifetime" field represents the times
between which clients will consider a LeafNode valid.  These times
are represented as absolute times, measured in seconds since the Unix
epoch (1970-01-01T00:00:00Z).  Applications MUST define a maximum
total lifetime that is acceptable for a LeafNode, and reject any
LeafNode where the total lifetime is longer than this duration.
Paragraph (version B):
In the case where the leaf node was inserted into the tree via a
Commit message, the "parent_hash" field contains the parent hash for
this leaf node (see parent-hash).
Paragraph (version B):
The LeafNodeTBS structure covers the fields above the signature in
the LeafNode.  In addition, when the leaf node was created in the
context of a group (the update and commit cases), the group ID of the
group is added as context to the signature.
Paragraph (version B):
Since the LeafNode is a structure which is stored in the group's
ratchet tree and updated depending on the evolution of the tree, each
modification of its content MUST be reflected by a change in its
signature.  This allows other members to verify the validity of the
LeafNode at any time, particularly in the case of a newcomer joining
the group.
Paragraph (version B):
Verify that the LeafNode is compatible with the group's
parameters.  The ciphersuite and protocol version of the
KeyPackage must match those in use in the group.  If the
GroupContext has a "required_capabilities" extension, then the
required extensions and proposals MUST be listed in the
KeyPackage's "capabilities" extension.
Paragraph (version B):
When validating a downloaded KeyPackage, the current time MUST
be within the "lifetime" range.  A KeyPackage that is expired
or not yet valid MUST NOT be sent in an Add proposal.
Paragraph (version B):
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):
Verify that the "membership" field has the appropriate contents
for the context in which the KeyPackage is being validated (as
defined in ratchet-tree-node-contents).
Paragraph (version A):
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.
Paragraph (version B):
To begin, the generator of the UpdatePath updates its leaf node and
its direct path to the root with new secret values.  The HPKE leaf
public key within the KeyPackage MUST be derived from a freshly
generated HPKE secret key to provide post-compromise security.
Paragraph (version A):
To allow group members to verify that they agree on the public
cryptographic state of the group, this section defines a scheme for
generating a hash value (called the "tree hash") that represents the
contents of the group's ratchet tree and the members' KeyPackages.
The tree hash of a tree is the tree hash of its root node, which we
define recursively, starting with the leaves.
Paragraph (version A):
The tree hash of a leaf node is the hash of leaf's
"LeafNodeHashInput" object which might include a Key Package
depending on whether or not it is blank.
Paragraph (version B):
Finally, "original_child_resolution" is the array of "HPKEPublicKey"
values of the nodes in the resolution of S but with the
"unmerged_leaves" of P omitted.  For example, in the ratchet tree
depicted in ratchet-tree-nodes the "ParentHashInput" of node Z with
co-path child C would contain an empty "original_child_resolution"
since C's resolution includes only itself but C is also an unmerged
leaf of Z.  Meanwhile, the "ParentHashInput" of node Z with co-path
child D has an array with one element in it: the HPKE public key of
D.
Paragraph (version A):
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.
Paragraph (version B):
As described in commit, each MLS Commit message may optionally
transmit a LeafNode and parent 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.
Paragraph (version A):
Thus the "confirmed_transcript_hash" field in a GroupContext object
represents a transcript over the whole history of MLSPlaintext Commit
messages, up to the confirmation_tag field of the most recent Commit.
The confirmation tag is then included in the transcript for the next
epoch.  The interim transcript hash is computed by new members using
the confirmation_tag of the GroupInfo struct, while existing members
can compute it directly.
Paragraph (version A):
a public key that others can use for key agreement,
Paragraph (version A):
The value for hpke_init_key MUST be a public key for the asymmetric
encryption scheme defined by cipher_suite, and it MUST be unique
among the set of KeyPackages created by this client.  The whole
structure is signed using the client's signature key.  A KeyPackage
object with an invalid signature field MUST be considered malformed.
Paragraph (version A):
KeyPackage objects MUST contain at least two extensions, one of type
"capabilities", and one of type "lifetime".  The "capabilities"
extension allows MLS session establishment to be safe from downgrade
attacks on the parameters described (as discussed in group-creation),
while still only advertising one version / ciphersuite per
KeyPackage.
Paragraph (version A):
As the "KeyPackage" is a structure which is stored in the Ratchet
Tree and updated depending on the evolution of the tree, each
modification of its content MUST be reflected by a change in its
signature.  This allows other members to verify the validity of the
KeyPackage at any time, particularly in the case of a newcomer
joining the group.
Paragraph (version B):
the content of the leaf node that should be added to the tree to
represent this client.
Paragraph (version B):
The value for "init_key" MUST be a public key for the asymmetric
encryption scheme defined by cipher_suite, and it MUST be unique
among the set of KeyPackages created by this client.  Likewise, the
"leaf_node" field MUST be valid for the ciphersuite, including both
the "hpke_key" and "credential" fields.  The whole structure is
signed using the client's signature key.  A KeyPackage object with an
invalid signature field MUST be considered malformed.
Paragraph (version B):
Note that the "capabilties" field in the "leaf_node" allows MLS
session establishment to be safe from downgrade attacks on the
parameters described (as discussed in group-creation), while still
only advertising one version / ciphersuite per KeyPackage.
Paragraph (version B):
Fetch KeyPackages for the members to be added, and select a
version and ciphersuite according to the capabilities of the
members.  To protect against downgrade attacks, the creator MUST
use the "capabilities" information in these KeyPackages to verify
that the chosen version and ciphersuite is the best option
supported by all members.
Paragraph (version B):
Each LeafNode in the new group's tree MUST be a leaf in the old
group's tree at the epoch indicated in the PreSharedKey.
Paragraph (version A):
On receiving an MLSPlaintext containing a Proposal, a client MUST
verify the signature on the enclosing MLSPlaintext.  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 B):
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):
Verify that the "hpke_init_key" value is different from the
corresponding field in the KeyPackage being replaced.
Paragraph (version B):
Set the leaf node L to a new node containing the LeafNode object
carried in the "leaf_node" field of the KeyPackage in the Add.
Paragraph (version B):
An Update proposal is a similar mechanism to Add with the distinction
that it replaces the sender's LeafNode in the tree instead of adding
a new leaf to the tree.
Paragraph (version B):
Verify that the "public_key" value is different from the
corresponding field in the LeafNode being replaced.
Paragraph (version B):
A Remove proposal requests that the member with LeafNodeRef "removed"
be removed from the group.
Paragraph (version B):
Identify a leaf node matching "removed".  This lookup MUST be done
on the tree before any non-Remove proposals have been applied (the
"old" tree in the terminology of commit), since proposals such as
Update can change the LeafNode stored at a leaf.  Let L be this
leaf node.
Paragraph (version B):
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 "public_key" with
another LeafNode in the group.
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_key_package" for this
UpdatePath must have a "parent_hash" extension.  Note that the
KeyPackage in the "UpdatePath" effectively updates an existing
KeyPackage in the group and thus MUST adhere to the same
restrictions as KeyPackages used in "Update" proposals.
Paragraph (version B):
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_key_package" for this
UpdatePath must have a "parent_hash" extension.  Note that the
LeafNode in the "UpdatePath" effectively updates an existing
LeafNode in the group and thus MUST adhere to the same
restrictions as LeafNodess used in "Update" proposals.
Paragraph (version A):
Construct an MLSPlaintext object containing the Commit object.
Sign the MLSPlaintext using the old GroupContext as context.
Paragraph (version A):
Use the MLSPlaintext to update the confirmed transcript hash
and generate the new GroupContext.
Paragraph (version B):
Construct an MLSMessageContent object containing the Commit
object.  Sign the MLSMessageContent using the old GroupContext as
context.
Paragraph (version A):
Calculate the interim transcript hash using the new confirmed
transcript hash and the "confirmation_tag" from the
MLSPlaintext.
Paragraph (version B):
Calculate the interim transcript hash using the new confirmed
transcript hash and the "confirmation_tag" from the
MLSMessageAuth.
Paragraph (version A):
Verify that the signature on the MLSPlaintext message verifies
using the public key from the credential stored at the leaf in the
tree indicated by the "sender" field.
Paragraph (version A):
Verify that all PSKs specified in any PreSharedKey proposals in
the "proposals" vector are available.
Paragraph (version B):
Verify that the signature on the MLSMessageContent message
verifies using the public key from the credential stored at the
leaf in the tree indicated by the "sender" field.
Paragraph (version A):
Use the "confirmation_key" for the new epoch to compute the
confirmation tag for this message, as described below, and verify
that it is the same as the "confirmation_tag" field in the
MLSPlaintext object.
Paragraph (version B):
Use the "confirmation_key" for the new epoch to compute the
confirmation tag for this message, as described below, and verify
that it is the same as the "confirmation_tag" field in the
MLSMessageAuth object.
Paragraph (version A):
If a Remove proposal is present, then the leaf KeyPackage in
the "path" field MUST meet the same criteria as the KeyPackage
in an Update for the removed leaf (see update).  In particular,
the "credential" in the KeyPackage MUST present a set of
identifiers that is acceptable to the application for the
removed participant.
Paragraph (version B):
If a Remove proposal is present, then the LeafNode in the
"path" field MUST meet the same criteria as the LeafNode in an
Update for the removed leaf (see update).  In particular, the
"credential" in the LeafNode MUST present a set of identifiers
that is acceptable to the application for the removed
participant.
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 and algorithm are taken from the
credential in the leaf node of the member with LeafNodeRef
"signer".  If there is no matching leaf node, or if signature
verification fails, return an error.
Paragraph (version A):
For each non-empty parent node, verify that exactly one of the
node's children are non-empty and have the hash of this node
set as their "parent_hash" value (if the child is another
parent) or has a "parent_hash" extension in the KeyPackage
containing the same value (if the child is a leaf).  If either
of the node's children is empty, and in particular does not
have a parent hash, then its respective children's
"parent_hash" values have to be considered instead.
Paragraph (version B):
For each non-empty parent node, verify that exactly one of the
node's children are non-empty and have the hash of this node
set as their "parent_hash" value (if the child is another
parent) or has a "parent_hash" field in the LeafNode containing
the same value (if the child is a leaf).  If either of the
node's children is empty, and in particular does not have a
parent hash, then its respective children's "parent_hash"
values have to be considered instead.
Paragraph (version B):
For each non-empty leaf node, validate the LeafNode as
described in leaf-node-validation.
Paragraph (version B):
If the "path_secret" value is set in the GroupSecrets object:
Identify the lowest common ancestor of the leaf node "my_leaf"
and of the node of the member with LeafNodeRef
"GroupInfo.signer".  Set the private key for this node to the
private key derived from the "path_secret".
Paragraph (version B):
In other words, an application can use GroupContext extensions to
ensure that all members of the group agree on a set of parameters.
Clients indicate their support for parameters in the "capabilities"
field of their LeafNode.  New members of a group are informed of the
group's GroupContext extensions via the "group_context_extensions"
field in the GroupInfo object.  The "other_extensions" field in a
GroupInfo object can be used to provide additional parameters to new
joiners that are used to join the group.
Paragraph (version B):
A client adding a new member to a group MUST verify that the
LeafNode for the new member is compatible with the group's
extensions.  The "capabilities" field MUST indicate support for
each extension in the GroupContext.
Paragraph (version B):
If any extension in a GroupInfo message is unrecognized (i.e., not
contained in the "capabilities" of the corresponding KeyPackage),
then the client MUST reject the Welcome message and not join the
group.
Paragraph (version A):
The signature keys held by group members are critical to the security
of MLS against active attacks.  If a member's signature key is
compromised, then an attacker can create KeyPackages impersonating
the member; depending on the application, this can then allow the
attacker to join the group with the compromised member's identity.
For example, if a group has enabled external parties to join via
external commits, then an attacker that has compromised a member's
signature key could use an external commit to insert themselves into
the group - even using a "resync"-style external commit to replace
the compromised member in the group.
Paragraph (version B):
The signature keys held by group members are critical to the security
of MLS against active attacks.  If a member's signature key is
compromised, then an attacker can create LeafNodes and KeyPackages
impersonating the member; depending on the application, this can then
allow the attacker to join the group with the compromised member's
identity.  For example, if a group has enabled external parties to
join via external commits, then an attacker that has compromised a
member's signature key could use an external commit to insert
themselves into the group - even using a "resync"-style external
commit to replace the compromised member in the group.