Change:
above), then it MUST be populated.  Otherwise, the sender MAY omit the "path" field at its discretion. <del> 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"). </del> <ins> If populating the "path" field: </ins> <del> Assign this UpdatePath to the "path" field in the Commit. </del> <ins> If this is an external commit, assign the sender the leftmost blank leaf node in the new ratchet tree.  If there are no blank leaf nodes in the new ratchet tree, add a blank leaf to the right side of the new ratchet tree and assign it to the sender. </ins> <del> 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. </del> <ins> 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. 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. The new leaf node MUST have "leaf_node_source" set to "commit". 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"). The application MAY specify other changes to the leaf node, e.g., providing a new signature key, updated capabilities, or different extensions. Construct a provisional GroupContext object containing the following values: "group_id": Same as the old GroupContext "epoch": The epoch number for the new epoch "tree_hash": The tree hash of the new ratchet tree "confirmed_transcript_hash": Same as the old GroupContext "extensions": The new GroupContext extensions (possibly updated by a GroupContextExtensions proposal) 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. Assign this UpdatePath to the "path" field in the Commit. </ins> 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" <del> value would be).  In this case, the new ratchet tree is the same as the provisional ratchet tree. </del> <ins> value would be). </ins> Derive the "psk_secret" as specified in pre-shared-keys, where the order of PSKs in the derivation corresponds to the order of

Discussion:
via option (3)
Bringing up the issue mentioned in the comments to : this can be inconvenient for implementations because this way the update path can't be generated and encrypted at the same time. From security perspective, I think it can'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 doesn't mention when the new joiner is added. 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: Compute the path secrets Compute the parent hash and the new leaf node Compute the new tree hash Encrypt 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 MLSpp does now). The options here are basically: Re-add the Add proposal, thus having a throw-away temporary LeafNode for the joiner Specify 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 Remove.) 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 haven'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 doesn'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: Do nothing. The context tree doesn'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 Ensure 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 doesn't match new tree TreeKEM encryption can operate on the context tree, because the structure is correct Use the "new" ratchet tree as the TreeKEM context. Context tree is exactly the new tree -- could probably eliminate provisional Requires 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 don'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.