Draft: draft-ietf-mls-protocol

Commentary: After discussion at the interim 2021 - 10 - 04 , there seemed to be agreement to move from having two sets of indices ( leaf and node indices ) to just using node indices . The main impact of this is that wherever there is a node index in the protocol that is only ever supposed to refer to a leaf node ( , , , ) , the recipient of the message needs to verify that the node index is actually a leaf . However , implementations already needed to check that these fields were populated leaves , so the change should be minor . Implementations that were using typed message structures to separate node and leaf indices can continue to use those types to assure that validation is done . They 'll just have to multiply by two on serialize and divide on deserialize if they use leaf indices as before internally . There are also a couple of small , unrelated , " drive - by " fixes in here : Clean up of notation in the ratchet tree section Including PublicGroupState signer index in signature ( in parallel with GroupInfo ) Replaces 
 I think we should still use " node index " instead of just " index " for clarity . 
 I am concerned that concatenating the children to get the name of a node becomes awkward with blank nodes . As we already have the node index , why not just use it to identify non - leaf nodes ? ~~ This prevents node 5 from being D or _ D 
 Fair enough . It also helps emphasize that parent nodes are of a different character than leaf nodes . Just pushed a commit with numeric parent   and otherwise improved ASCII art . 
 Under the list of changes , one of these says that leafindex is removed . However a search for index and indeces still shows several references to the leafindex . These should be removed / replaced as well , or explain why we need Leaf Node Reference and leaf_index and when one gets used . 
 was first removed in in favor of node indices , and afterward re - introduced to remove node indices in . Draft 12 was out between these two pull requests , I 'm guessing the latter change will appear in the changelog of draft 13 ! 
 It looks to me like the following mentions of leaf index still exist : leaf node input to the tree hash ratchet tree serialization scheme ( for the extension ) example code for array - based trees  ( 3 ) is optional , so I do n't think it 's an issue . For case ( 2 ) , removing the notion of indices would make it a lot harder to describe the serialization . For case ( 1 ) , removing the index would remove some of the rigidity of the tree hash ( in particular , a blank subtree of the same size would have the same hash anywhere ) . Overall , I 'm not inclined to make this change . 
 The description of is clear that proposals defined in the RFC are not to be included because they are considered standard , however there is no such clarification around extensions . In general , extension requirements are not all that clear because in several places ( such as Lifetime ) it is mentioned that an extension must be included , however they are all listed as recommended rather than required in the table . It also raises a question in that if an extension is required to be implemented as part of the specification , and required in every KeyPackage , should it just be a field rather than an extension ? 
 Even that is not quite clear to me . At least to me it does n't follow from Section 10.1 . The last one at least , I can shed some light on , as we had this discussion before and I was arguing the same thing . From what I remember , the argument was the keeping it an extension rather than a field would force implementers to implement the Extension mechanism . I do n't have a super strong opinion here , but I seem to remember that that 's what it boiled down to . I generally agree , though , that some things around RequiredCapabilities ( RC ) and Capabilities in general is unclear . I discussed this with and here are some examples / questions that came up : If a group does n't have an RC extension , can we assume that all extensions / proposals in the spec are supported by everyone , or do we have to check all KeyPackages ? Do we want all extensions / proposals in the spec to be MTI and thus implicitly supported by everyone ? It might be useful to distinguish between basic ( MTI ) ones like Add , Remove , etc . and consider some optional such as ExternalInit or AppAck . Relatedly , should the extension in the KeyPackage list all the  proposals / extensions , too ? Are they invalid if they do or if they do n't ? We might want to discuss the difference between RC and a potential group policy . The way I understand it , RC is not meant to be an allow list or a block list , but instead should specify a minimum requirement for all clients in the group . A question here would be : If it 's not in the RC , but everyone 's capabilities support it , can I use / send it ? 
 Reacting to  's points here : It seems like to to be sure , you always need to check . RC just helps ensure that the check will produce the right answer . I would propose the answer to this be " Yes , the proposals / extensions in the spec are MTI " If the answer to the prior question is as I propose , then you do n't need to list spec proposals / extensions in Capabilities The difference between RC and checking everyone supports it is whether we expect it to be supported in the future . For Proposals , that might not matter ; for GroupContext extensions , it matters more . It also seems like there 's a fair argument that RequiredCapabilities should just be removed from the spec and replaced by some application - layer coordinated policy . I would be OK with that . To point about mandatory extensions -- I would probably be OK with converting mandatory extensions to fields , especially with the better division of labor proposed in . 
 I kind of like the idea of RequiredCapabilities because it enforces the proper checks lower down . Application layer would need to proxy packets via a middleware type of layer which could potentially be messy . 
 Discussion on working call : Agreement to not list in - spec capabilities in RC / Capabilities , so all those things are MTI 
 We currently distinguish between leaf indices and node indices , where node indices are a straight - forward index in the array representing a ratcheting tree and leaf indices index what ( I think ) used be called the roster , an array just containing the members . However , the roster was removed and instead , we always use the ratcheting tree array when referring to both nodes and leaves . As a result , whenever we use a leaf index , we have to translate it to a node index when actually indexing the array . I do n't see a reason for this complexity and propose only use node indices . This was already discussed a bit in , where pointed out that leaf indices make sure that a Remove only ever references a leaf . I think having a little check here that the index actually refers to a leaf is preferable over having to translate back and forth in many other places . Note , that we have to check anyway if the index in Remove is within the bounds of the tree , regardless of it being a leaf or a node index . I 'm happy to provide a PR if this is something we want to do . 
 When I mentioned Remove in the context of , that was only an initial example . Here 's a more fulsome list of where leaf indices are used : All of those are cases where a node index would be nonsensical . So while even with a leaf index , you still have to bounds - check , you 're basically asking for us to allow more nonsensical cases to be expressible in the syntax . As far as comprehensibility / implementability : The difference between node indices and leaf indices is clearly laid out in the , and AFAICT , all of the references to indices are clear about which one they refer to . This distinction is a good one for implementors to have in mind , and not just to simplify parsing / input validation on messages as above . There are lots of internal methods that only make sense to refer to leaves as well . For example , a TreeKEM implementation internal to an MLS implementation will need to know the index of the caller of so that it can make the right UpdatePath . Here again , providing a node index is nonsensical . So implementations will likely end up needing to differentiate leaves from nodes anyway . Net of all that , I propose closing this with no action . 
 Fair enough . Closing this , then . 
 I suggest to re - open this in order to remove the concept of lead indices from the spec . We 'll do so in OpenMLS in because it 's a footgun causing a ton of issues . If you really want to keep the concept in the spec , it must be made clear when a leaf index and when a node index is used . Taking the text for remove proposals for example . This sounds like the index is a node index . This sounds like it could be a leaf index . But it 's not defined either way . So your claim that " all of the references to indices are clear about which one they refer to . " is not true . I totally agree with this . But the current concept introduces more issues than it actually solves . So this definitely needs work , either by removing the concept ( which I 'd prefer ) or actually making clear which index is used . In my opinion the tree should only be indexed by one type of indices to reduce ambiguity and the potential for bugs . This way we can reduce the places of conversion while still making it clear to implementors that there is a distinction between the two . 
 I 'm happy to clean up references , but I would like to avoid explicitly allowing invalid requests . You should n't be able to ask to remove node 5 , or say you signed with node 7 . You risk bugs either way ; in the " all indices are node indices " case you just have to catch them at runtime , as opposed to using the right type at build time . So I continue to believe this is better handled with type systems . Nonetheless , I 'll do a review and try to clean up any ambiguous cases . 
 LGTM in general ! It might be worth specifying in the tree math section that the number of leaves in a tree is , where is the total number of nodes in the tree . Some functions like take the number of leaves as an argument and this might now be confusing .
Paragraph (version B):
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.  The root of a
tree with "n" leaves is always the node with index "2^k - 1", where
"k" is the largest number such that "2^k < n".
Paragraph (version A):
In this table, the value pk(ns[X]) represents the public key derived
from the node secret X, whereas pk(X) represents the public leaf key
for user X.  The value E(K, S) represents the public-key encryption
of the path secret S to the public key K.
Paragraph (version B):
In this table, the value pk(ns[X]) represents the public key derived
from the node secret X, whereas pk(X) represents the public leaf key
for user X.  The value E(K, S) represents the public-key encryption
of the path secret S to the public key K (using HPKE).
Paragraph (version A):
Note that the "node_index" field contains the index of the leaf among
the nodes in the tree, not its index among the leaves; "node_index =
2 * leaf_index".
Paragraph (version A):
Now the tree hash of any non-leaf node is recursively defined to be
the hash of its "ParentNodeTreeHashInput".  This includes an optional
"ParentNode" object depending on if the node is blank or not.
Paragraph (version B):
Now the tree hash of any non-leaf node is recursively defined to be
the hash of its "ParentNodeTreeHashInput".  This includes an optional
"ParentNode" object depending on whether the node is blank or not.
Paragraph (version A):
When parsing a SenderData struct as part of message decryption, the
recipient MUST verify that the sender field represents an occupied
leaf in the ratchet tree.  In particular, the sender index value MUST
be less than the number of leaves in the tree.
Paragraph (version B):
When parsing a SenderData struct as part of message decryption, the
recipient MUST verify that the sender field represents an occupied
leaf in the ratchet tree.
Paragraph (version A):
An Add is applied after being included in a Commit message.  The
position of the Add in the list of proposals determines the leaf
index "index" where the new member will be added.  For the first Add
in the Commit, "index" is the leftmost empty leaf in the tree, for
the second Add, the next empty leaf to the right, etc.
Paragraph (version B):
An Add is applied after being included in a Commit message.  The
position of the Add in the list of proposals determines the node
index "index" of the leaf node where the new member will be added.
For the first Add in the Commit, "index" is the leftmost empty leaf
in the tree, for the second Add, the next empty leaf to the right,
etc.
Paragraph (version B):
Verify that the node index "removed" corresponds to a leaf node
(i.e., that it is divisible by two) and the leaf at that node is
populated.
Paragraph (version A):
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 at position "signer_index".  If this
verification fails, return an error.
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 at position "signer_index".  If the
node at position "signer_index" is not a leaf node, or if
signature verification fails, return an error.
Paragraph (version A):
Identify a leaf in the "tree" array (any even-numbered node) whose
"key_package" field is identical to the KeyPackage.  If no such
field exists, return an error.  Let "index" represent the index of
this node among the leaves in the tree, namely the index of the
node in the "tree" array divided by two.
Paragraph (version B):
Identify a leaf in the "tree" array (any even-numbered node) whose
"key_package" field is identical to the KeyPackage.  If no such
field exists, return an error.  Let "index" represent the index of
this node in the tree.