Draft: draft-ietf-mls-protocol

Commentary: This PR reorganizes the exposition of tree concept to make them read more clearly . A new  through the Ratchet Tree section is introduced , which covers direct paths , copaths , and filtered direct paths The Ratchet Tree Nodes section is moved forward , so that the definition of filtered direct paths can refer to the notion of resolutions ( see note on ) The path example expanded so that there are blanks in the tree , and so that it shows all three path types ( direct , copath , and filtered direct ) for every leaf . The section on verifying parent hashes uses the example tree from the path section to illustrate the concepts noted in 
 Latest commit adds coverage for the last part of . In addition to moving the figure forward , it refactors the explanation to be even more explicit about what must be consumed and what may be kept . 
 Currently defined only briefly in " RatchetTree Evolution " . While that 's where it 's primarily used , it would fit better in the terminology section , especially if we expand it a little and add an illustration . 
 The problem here is that the definition of " filtered direct path " depends on the definition of " resolution " which is n't defined until a later section . 
 Indeed , so proposes moving up the definition of resolution as well . 
 Add illustration of filtered direct path Add illustration of parent hash validation , including : Leaves with no parent hash  with invalid parent hash Skipping blank nodes Add corresponding derivations to    figure in Section 10.2 up to Section 10 as an overview 
 Note that I have taken a stab at parts of this in because it came along with some other tree stuff . 
 Duplicate 
 Regarding the Tree Hash the parentnode in the tree hash which is supposed to be computed from the leaves , makes the definition appear circular to a new reader . Since the situation when you have blank nodes in the middle of a tree but not at the root is a rather complicated , the situation when you could calculate a tree hash using the parentnode is non - obvious . the description of Tree Hash the document says that the ParentNode object is optional " depending on whether the node is blank or not . " This is n't clear specification language . Saying the parentnode object is present if the node is blank or present if the node is not blank would be clear . if the parent node is also blank ? What if the root is blank ? think an example with and without blank nodes would be appropriate . the reason why the parentnode is included will help the reader establish a better mental model . Regarding the Parent Hash I still find this section super confusing . A few points a tree hash ( siblingtreehash ) inside the parent hash is non - obvious . There should be some explanation about what property this is providing and that it is a tree hash and not a parent hash . ( Honestly it was the third time I read the section that I realized that it was really the tree hash and not the parent hash of the sibling . ) P , V , and S for Parent , target node , and sibling node is introduced here and never used anywhere else in the document . P for parent and S for sibling make sense . I ca n't tell what  is supposed to stand for , and it unfortunately could cause people to confuse the < Vvector notation used throughout , but shortly after introducing V for the target node . I propose using " N " for node or " X " instead . is one reference to the filtered direct path of U. Perhaps this is V , or a child of V ? calculations are sufficiently complicated that I think this warrants a complete , non - trivial , worked example ( possibly in an appendix ) . At some point we should consider providing test vectors in a future document , and this would be an ideal candidate . 
 Regarding the Tree Hash To be clear , the that is included in the tree hash is the node whose subtree is being hashed . The tree hash does n't include only the leaves , it includes all the nodes in the tree . If you mean , then yes , I agree the connection is a little subtle . I 've tried to explain this a little better . Updated this to be clear that the ParentNode is omitted if and only if the node is blank . Not clear what situation you mean here . I think the specific computations are pretty clearly specified for these cases . Something like this was in : I extended the example and put it in an appendix . Assuming you mean here , hopefully this is addressed by the above notes . Added a note to clarify that this is the tree hash and why . Changed to " D " for " direct path " , and explained the semantics . Actually it 's the leaf , which is now called " L " . I think the example linked above does a pretty good job . Feedback obviously welcome . Agreed that a test vectors document will be a useful follow - on . 
 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 . 
 In , we introduced the idea of a " filtered direct path " . It would be helpful to have an illustration of how a filtered direct path differs from a direct path .
Paragraph (version A):
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.  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 A):
A tree with "n" leaves has "2*n - 1" nodes.  For example, the above
tree has 7 leaves (A, B, C, D, E, F, G) and 13 nodes.
Paragraph (version A):
Each leaf is given an _index_ (or _leaf index_), starting at "0" from
the left to "n-1" at the right.
Paragraph (version A):
Finally, a node in the tree may also be _blank_, indicating that no
value is present at that node (i.e. no keying material).  This is
often the case when a leaf was recently removed from the tree.
Paragraph (version B):
Each leaf node in a ratchet tree is given an _index_ (or _leaf
index_), starting at "0" from the left to "n-1" at the right (for a
tree with "n" leaves).  A tree with "n" leaves has "2*n - 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 nodes key pair
would be equivalent to encrypting to its non-copath child.
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 B):
Since these chains collectively cover all non-blank parent nodes in
the tree, the tree is parent-hash valid.
Paragraph (version B):
Note that this tree, though valid, contains invalid parent-hash
links.  If a client were checking parent hashes top-down from X, for
example, they would find that Z has an invalid parent hash relative
to X, but that U has valid parent hash.  Likewise, if the client were
checking bottom-up, they would find that the chain from F ends in an
invalid link from Y to Z.  These invalid links are the natural result
of multiple clients having committed.
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):
Then if a client uses key K1 and nonce N1 during epoch n then it must
consume (at least) values G, F, D, AR0, AR1, K1, N1 as well as the
key schedule secrets used to derive G (the "encryption_secret"),
namely "init_secret" of epoch n-1 and "commit_secret",
"joiner_secret", "epoch_secret" of epoch n.  The client MAY retain
(not consume) the values K0 and N0 to allow for out-of-order
delivery, and SHOULD retain AR2 for processing future messages.