Change:
Depends on Some subsections of are more natural within the ratchet tree operations section. This PR pulls them up into the section.
Again, this diff looks better with
This issue accompanies the e-mail [1] pointing out editorial issues with the draft and suggesting improvements. TL;DR: I don't think the draft currently is what is needed to implement MLS. It needs an editorial overhaul with a better structure. [1] URL
If we want an implementation document, which is a legitimate ask, we should write an implementation document.
What's the purpose of the document if not for implementation? In order for this draft to become an RFC it needs implementations. I don't see a way this will happen with the draft as is.
Specifying a functional behavior and providing a guide for implementation are two very different things. Re: what you suggested in your email: Internal representations of the tree, the way you number the indices, how you implement them or the fact that you store some hashes in the nodes are completely irrelevant to this document. Also, there is nothing formal about this appendix. On the other hand the information you need from this document is how to perform computations : things such as providing the arguments to the nodeencap/nodedecap functions which are required to generate a correct payload and a standard way to serialize abstract structures... etc.
Right, the document is doing neither right now.
We have two separable issues here: We need better explanation of the overall structure of the protocol (as in Franziskus' email), and we could rearrange the text to be clearer. For the first, I have filed . That provides a pretty complete overview of the protocol, covering all of the syntax and high-level operations. For the second, we should do a second PR after the other outstanding PRs have landed. Looking at the table of contents, I would propose we refactor in the following way: For top-level sections: Move to after and For subsections: Move the tree-relevant subsections of into — including , , and Split into two: Put the GroupContext definition part in a new subsection under Put the transcript updating part in a new subsection under
I have an alternative organization of the first sections: Add the one sentence definition of before the last paragraph of Swap and This seems to all flow nicely with very few forward references. A more radical rewrite might Move forward before but I don't think that is necessary. I do like Richard's proposal to split
I'm considering this fixed after the series of reorg PRs that NAME NAME and I just finished. Hi all, While trying to implement the MLS protocol from scratch I ran into a couple issues and I think there’s a general problem regarding the structure of the draft. Note that these are all editorial issues. Right now I see no straightforward way to implement the spec as there’s no clear structure. To implement even the basic tree structure it’s necessary to jump through the entire document. There’s also a lot of prose and examples and very little implementable parts. Let me give some examples: Section 5 (Ratchet Trees) reads like a non-normative section (or paper). The really interesting bit related to trees is in Appendix A. I suggest moving the formal description of the tree from Appendix A to this section. It’s impossible to implement this from the hand full examples here. There’s no description in here on how the tree actually works. For example, adding a node to the tree is defined in 10.1.1 (Add). The ratchet tree nodes as described in Section 5 don't hold a KeyPackage. But Section 7 says “As the KeyPackage is a structure which is stored in the Ratchet Tree and updated depending on the evolution of this tree”. The tree nodes have a parent hash field, which is of course part of the ratchet tree but shouldn’t be necessary for the description of the tree structure. Also, the description of the tree hash (Section 7.5) follows the definition of the parenthash extension in Section 7.4 such that it is not entirely clear which parenthash is used there (extension or node value). Section 5 also talks about commit messages without linking to them or introducing them. Having to talk about commit messages in this section is also a little confusing. The term “handshake message” is first used in Section 7.8 (Key Schedule) but never introduced. This should probably happen in 10 (Group Evolution), which doesn’t talk about handshake messages at all until 10.3 (Ratchet Tree Extension). There are probably plenty more issues like this. But I don’t think addressing any of these issues individually makes sense. I therefore suggest having an overhaul of the document structure. The way I see MLS there are a set of high-level structures and components that should be clearly described and built upon each other. There’s a data structure (tree) that is used to represent a group. Then there is the MLS group that implements a specific semantic on the tree. Operations to modify the group are specified in the handshake protocol. And application messages define messages encrypted with keys derived in the handshake protocol with the key schedule. The tree is a basic left-balanced binary tree and defines functions to create a tree, and add and remove nodes. A group is the main part of MLS, a tree with nodes of a certain type. So this part should describe what nodes look like and how the tree is used to compute tree invariants and keys. The handshake protocol defines all messages needed to perform operations on the MLS group. This is certainly not exhaustive and would need further refinement. But it’s the high-level structure I’d expect from the document. I’m sure other structures would work as well. But what we currently have isn’t what is needed to implement MLS imho. Cheers, Franziskus

Discussion:
Each outdated node key pair that was replaced by the update. <ins> 8.4. 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. As some nodes may be blank while others contain data we use the following struct to include data if present. 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. Now the tree hash of any non-leaf node is recursively defined to be the hash of its "ParentNodeHashInput".  This includes an optional "ParentNode" object depending on whether the node is blank or not. The "left_hash" and "right_hash" fields hold the tree hashes of the node's left and right children, respectively. 8.5. The "parent_hash" extension carries information to authenticate the structure of the tree, as described below. Consider a ratchet tree with a parent node P and children V and S. The parent hash of P changes whenever an "UpdatePath" object is applied to the ratchet tree along a path traversing node V (and hence also P).  The new "Parent Hash of P (with Co-Path Child S)" is obtained by hashing P's "ParentHashInput" struct using the resolution of S to populate the "original_child_resolution" field.  This way, P's Parent Hash fixes the new HPKE public keys of all nodes on the path from P to the root.  Furthermore, for each such key PK the hash also binds the set of HPKE public keys to which PK's secret key was encrypted in the Commit that contained the "UpdatePath" object. The Parent Hash of P with Co-Path Child S is the hash of a "ParentHashInput" object populated as follows.  The field "public_key" contains the HPKE public key of P.  If P is the root, then "parent_hash" is set to a zero-length octet string.  Otherwise "parent_hash" is the Parent Hash of P's parent with P's sibling as the co-path child. 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 resolution-example 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. 8.5.1. The Parent Hash of P appears in three types of structs.  If V is itself a parent node then P's Parent Hash is stored in the "parent_hash" fields of both V's "ParentHashInput" struct and V's "ParentNode" struct.  (The "ParentNode" struct is used to encapsulate all public information about V that must be conveyed to a new member joining the group as well as to define the Tree Hash of node V.) If, on the other hand, V is a leaf and its KeyPackage contains the "parent_hash" extension then the Parent Hash of P (with V's sibling as co-path child) is stored in that field.  In particular, the extension MUST be present in the "leaf_key_package" field of an "UpdatePath" object.  (This way, the signature of such a KeyPackage also serves to attest to which keys the group member introduced into the ratchet tree and to whom the corresponding secret keys were sent. This helps prevent malicious insiders from constructing artificial ratchet trees with a node V whose HPKE secret key is known to the insider yet where the insider isn't assigned a leaf in the subtree rooted at V.  Indeed, such a ratchet tree would violate the tree invariant.) 8.5.2. To this end, when processing a Commit message clients MUST recompute the expected value of "parent_hash" for the committer's new leaf and verify that it matches the "parent_hash" value in the supplied "leaf_key_package".  Moreover, when joining a group, new members MUST authenticate each non-blank parent node P.  A parent node P is authenticated by performing the following check: Let L and R be the left and right children of P, respectively If L.parent_hash is equal to the Parent Hash of P with Co-Path Child R, the check passes If R is blank, replace R with its left child until R is either non-blank or a leaf node If R is a blank leaf node, the check fails If R.parent_hash is equal to the Parent Hash of P with Co-Path Child L, the check passes Otherwise, the check fails The left-child recursion under the right child of P is necessary because the expansion of the tree to the right due to Add proposals can cause blank nodes to be interposed between a parent node and its right child. 8.6. 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. 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. The HPKECiphertext values are computed as where "node_public_key" is the public key of the node that the path secret is being encrypted for, group_context is the current GroupContext object for the group, and the functions "SetupBaseS" and "Seal" are defined according to I-D.irtf-cfrg-hpke. Decryption is performed in the corresponding way, using the private key of the resolution node. </ins> 9. In order to facilitate the asynchronous addition of clients to a

9.5. <del> The "parent_hash" extension carries information to authenticate the structure of the tree, as described below. Consider a ratchet tree with a parent node P and children V and S. The parent hash of P changes whenever an "UpdatePath" object is applied to the ratchet tree along a path traversing node V (and hence also P).  The new "Parent Hash of P (with Co-Path Child S)" is obtained by hashing P's "ParentHashInput" struct using the resolution of S to populate the "original_child_resolution" field.  This way, P's Parent Hash fixes the new HPKE public keys of all nodes on the path from P to the root.  Furthermore, for each such key PK the hash also binds the set of HPKE public keys to which PK's secret key was encrypted in the Commit that contained the "UpdatePath" object. The Parent Hash of P with Co-Path Child S is the hash of a "ParentHashInput" object populated as follows.  The field "public_key" contains the HPKE public key of P.  If P is the root, then "parent_hash" is set to a zero-length octet string.  Otherwise "parent_hash" is the Parent Hash of P's parent with P's sibling as the co-path child. 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 resolution-example 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. 9.5.1. The Parent Hash of P appears in three types of structs.  If V is itself a parent node then P's Parent Hash is stored in the "parent_hash" fields of both V's "ParentHashInput" struct and V's "ParentNode" struct.  (The "ParentNode" struct is used to encapsulate all public information about V that must be conveyed to a new member joining the group as well as to define the Tree Hash of node V.) If, on the other hand, V is a leaf and its KeyPackage contains the "parent_hash" extension then the Parent Hash of P (with V's sibling as co-path child) is stored in that field.  In particular, the extension MUST be present in the "leaf_key_package" field of an "UpdatePath" object.  (This way, the signature of such a KeyPackage also serves to attest to which keys the group member introduced into the ratchet tree and to whom the corresponding secret keys were sent. This helps prevent malicious insiders from constructing artificial ratchet trees with a node V whose HPKE secret key is known to the insider yet where the insider isn't assigned a leaf in the subtree rooted at V.  Indeed, such a ratchet tree would violate the tree invariant.) 9.5.2. To this end, when processing a Commit message clients MUST recompute the expected value of "parent_hash" for the committer's new leaf and verify that it matches the "parent_hash" value in the supplied "leaf_key_package".  Moreover, when joining a group, new members MUST authenticate each non-blank parent node P.  A parent node P is authenticated by performing the following check: Let L and R be the left and right children of P, respectively If L.parent_hash is equal to the Parent Hash of P with Co-Path Child R, the check passes If R is blank, replace R with its left child until R is either non-blank or a leaf node If R is a blank leaf node, the check fails If R.parent_hash is equal to the Parent Hash of P with Co-Path Child L, the check passes Otherwise, the check fails The left-child recursion under the right child of P is necessary because the expansion of the tree to the right due to Add proposals can cause blank nodes to be interposed between a parent node and its right child. 9.6. 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. As some nodes may be blank while others contain data we use the following struct to include data if present. 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. Now the tree hash of any non-leaf node is recursively defined to be the hash of its "ParentNodeHashInput".  This includes an optional "ParentNode" object depending on whether the node is blank or not. The "left_hash" and "right_hash" fields hold the tree hashes of the node's left and right children, respectively. 9.7. </del> Each member of the group maintains a GroupContext object that summarizes the state of the group:

"interim_transcript_hash" field is set to the zero-length octet string. <del> 9.8. 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. 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. The HPKECiphertext values are computed as where "node_public_key" is the public key of the node that the path secret is being encrypted for, group_context is the current GroupContext object for the group, and the functions "SetupBaseS" and "Seal" are defined according to I-D.irtf-cfrg-hpke. Decryption is performed in the corresponding way, using the private key of the resolution node. </del> 10. Group keys are derived using the "Extract" and "Expand" functions