Draft: draft-ietf-mls-protocol

Commentary: via option ( 3 ) 
 Bringing up the issue mentioned in the comments to : this can be inconvenient for implementations because this way the update path ca n't be generated and encrypted at the same time . From security perspective , I think it ca n't break anything , since we include strictly more in the context . On the other hand , it 's not clear to me what it buys . 
 I sympathize with the idea that this constraints applications . MLSpp does exactly what you 're talking about , updating and encrypting at the same time . But I 'm not really seeing why that refactoring matters all that much . It 's not any more net work , aside from maybe computing a filtered direct path twice . 
 In , we removed the requirement for an external Commit to have an Add proposal , under the theory that the required already has a leaf node for the new joiner . When I went to implement this , however , I noticed some ambiguities . First , the Commit processing section does n't mention when the new joiner is . The obvious approach is to do the add as part of the processing , but that processing presumes that the sender is already in the tree ( for example , there is no provision for extending the tree ) . Second , the UpdatePath for an external commit needs to be done in a specific order in order to avoid a circular dependency . The TreeKEM encapsulation done by the joiner uses the GroupContext a context parameter , including the hash of the tree being used . But the LeafNode in the has to have a parent hash in it , which can only be computed after the encapsulation operation . This circularity can be resolved , but only by preparing the UpdatePath with the following specific steps : the path secrets the parent hash and the new leaf node the new tree hash the path secrets to the rest of the group That rules out some implementations that compute path secrets and encrypt them in one traversal of the direct path ( this is what  does now ) . The options here are basically : -add the Add proposal , thus having a throw - away temporary LeafNode for the joiner that the UpdatePath must be computed in the specific order above To me , ( 2 ) seems like a pretty clear winner . 
 Actually , it seems like there 's a simpler option here : Extend the definition of an ExternalInit proposal to say that part of how it is applied is to ensure there is a blank leaf in the tree , extending the tree if necessary . ( This implies that ExternalInit must be done after any  . ) The leftmost blank leaf is the sender 's leaf for purposes of UpdatePath processing . The provisional tree used in encrypting path secrets is still the provisional tree , in the sense that all proposals have been applied . This tree has a blank leaf for the UpdatePath sender . If we clarify these points , I think everything else works unmodified . 
 I agree with the first point . Even if the Add is not explicitly present , the external commit effectively emulates an Add anyway . To the second point : The solution is only simpler if that 's how you want to implement it . I have n't tried it , but if you have chosen a different order of implementation , i.e. perform all changes to the provisional tree ( including the update path and parent hash ) , then compute the tree hash and then encrypt the path , you now have to go and compute an additional tree hash before applying the update path , then apply the update path and keep the partial tree hash around for later encryption / decryption . This was not a part of MLS that I have looked at from a provable security perspective , but I 'm wondering what the consequences are from a security perspective if we 're including a parent hash for an incomplete provisional tree . 
 It seems like the main question here is what tree is provided to TreeKEM encryptions of path secrets . I 'll call this the " context tree " below . It sounds like you have in mind that the tree provided to TreeKEM has the UpdatePath applied as well as the proposals , but that 's not what the spec says right now : The starting point for this issue is that the " provisional " tree right now does n't really faithfully capture the tree you 're encrypting to , at least in the sense that it has a different number of leaves . That seemed wrong to me . It seems like we have the following options here : nothing . The context tree does n't necessarily have a blank for the joiner . Maximal divergence between the new tree and the context tree ( among these options ) TreeKEM encryption operates on a different tree than the context tree the provisional tree has a blank leaf for the joiner ( as up - thread ) . Context tree has a blank leaf for the joiner , but direct path does n't match new tree TreeKEM encryption can operate on the context tree , because the structure is correct the " new " ratchet tree as the TreeKEM context . Context tree is exactly the new tree -- could probably eliminate provisional  separate steps for ( 1 ) updating tree nodes and ( 2 ) encrypting path secrets ... when these are currently described together as effectively one operation Personally , it seems like the answer is ( 2 ) or ( 3 ) , and I probably lean toward ( 3 ) . I do n't think ( 1 ) is insecure , because the context tree does capture all of the nodes to which path secrets are encrypted . But the divergence is worrisome and it 's cheap to do better . Between ( 2 ) and ( 3 ) , there 's a trade - off between fidelity and spec change size . The impact of ( 2 ) is more localized ( just the ExternalInit section ) . Updating the spec to do ( 3 ) will require amending the description of update paths as well as the Commit creation / processing rules ( to separate update / encrypt and remove provisional , respectively ) . But honestly , removing the provisional tree seems like a worthwhile simplification , so between that and the greater fidelity of the context tree , ( 3 ) seems worth the cost . 
 Actually , as I look into implementing this : We still need a provisional GroupContext , because the will differ . But we can at least have two trees instead of three .
Paragraph (version A):
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 RFC9180.
Paragraph (version B):
where "node_public_key" is the public key of the node that the path
secret is being encrypted for, group_context is the provisional
GroupContext object for the group, and the functions "SetupBaseS" and
"Seal" are defined according to RFC9180.
Paragraph (version A):
"Old" refers to the ratchet tree and GroupContext for the epoch
before the commit.  The old GroupContext is used when signing the
MLSPlainText so that existing group members can verify the
signature before processing the commit.
Paragraph (version A):
"Provisional" refers to the ratchet tree and GroupContext
constructed after applying the proposals that are referenced by
the Commit.  The provisional GroupContext uses the epoch number
for the new epoch, and the old confirmed transcript hash.  This is
used when creating the UpdatePath, if the UpdatePath is needed.
Paragraph (version A):
"New" refers to the ratchet tree and GroupContext constructed
after applying the proposals and the UpdatePath (if any).  The new
GroupContext uses the epoch number for the new epoch, and the new
confirmed transcript hash.  This is used when deriving the new
epoch secrets, and is the only GroupContext that newly-added
members will have.
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 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 A):
Apply the UpdatePath to the tree, as described in
synchronizing-views-of-the-tree, creating the new ratchet tree.
Define "commit_secret" as the value "path_secret[n+1]" derived
from the "path_secret[n]" value assigned to the root node.
Paragraph (version A):
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).  In this case, the new ratchet tree is the same
as the provisional ratchet tree.
Paragraph (version B):
Generate path secrets for the parent nodes along the sender's
filtered direct path, as described in synchronizing-views-of-
the-tree.  Define "commit_secret" as the value
"path_secret[n+1]" derived from the "path_secret[n]" value
assigned to the root node.
Paragraph (version B):
Update the new ratchet tree by setting the parent nodes on the
sender's filtered direct path based on the path secrets.
Compute parent hashes for these nodes.  Set the sender's leaf
node in the new tree to a new leaf node including the resulting
parent hash for its nearest ancestor.
Paragraph (version B):
The new leaf node MUST have "leaf_node_source" set to
"commit".
Paragraph (version B):
Since the new leaf node effectively updates an existing leaf
node in the group, it MUST adhere to the same restrictions
as LeafNodes used in "Update" proposals (aside from
"leaf_node_source").
Paragraph (version B):
The application MAY specify other changes to the leaf node,
e.g., providing a new signature key, updated capabilities,
or different extensions.
Paragraph (version B):
Create an UpdatePath that encrypts the path secrets for the
nodes along the sender's filtered direct path to the new
ratchet tree, using the provisional GroupContext as context.
Any new member (from an Add proposal) MUST be excluded from the
resolution during the computation of the UpdatePath.
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 A):
Use the MLSMessageContent to update the confirmed transcript
hash and generate the new GroupContext.
Paragraph (version B):
Use the MLSMessageContent to update the confirmed transcript
hash and update the new GroupContext.
Paragraph (version A):
Apply the UpdatePath to the tree, as described in
synchronizing-views-of-the-tree, and store "leaf_node" at the
committer's leaf.
Paragraph (version A):
Verify that the LeafNode has a "parent_hash" field and that its
value matches the new parent of the sender's leaf node.
Paragraph (version B):
Compute parent hashes for the parent nodes in the UpdatePath
(relative to the new ratchet tree) and verify that the
"parent_hash" field of the leaf node matches the parent hash
for the first node its filtered direct path.
Paragraph (version B):
Apply the UpdatePath to the tree, as described in
synchronizing-views-of-the-tree, using the provisional
GroupContext when decrypting the path secret and storing
"leaf_node" at the committer's leaf.