Change:
When we converted to the Proposal/Commit scheme, one of the consequences was that Adds became O(N) instead of O(1), since they had to Committed and commits encrypt the committer's direct path to the remainder of the tree. This is ~log(N) in the nice case and ~N in the worst case. At the time, we noted that one could special-case Add-only Commits in the future and make the update to the direct path optional in that case. This PR implements that suggestion. The leaf KeyPackage for the Commit is moved into the DirectPath struct, and the whole DirectPath is made optional. The optional value MUST be provided for: A Commit committing an update or remove A "just update my path" Commit covering no proposals
So we do go forward one epoch, but with an all-zero commit secret. Do I understand that correctly?
NAME That's correct. The idea is that you don't need to send the current group any additional entropy, you just need them to hash forward. Note that this could change the FS/PCS guarantees available. But I don't think we ever proposed that Add would cause any FS/PCS changes to the group, so maybe this doesn't matter.
Add should give FS guarantees in so far as that the new member shouldn't be able to decrypt old messages or derive old keys. But I think ratcheting forward should do the trick. We should note, though, that this breaks the (implicit) assumption that a commit always introduces fresh key material to the group. As far as I can see, that's not breaking anything right now, but we should keep it in mind.
Note: The latest commit fixes a gap that was discovered in .
See the flexibility question.

Discussion:
RFC EDITOR PLEASE DELETE THIS SECTION. <ins> draft-10 Re-enable constant-time Add (*) </ins> draft-09 Remove blanking of nodes on Add (*)

In the event that a valid proposal is omitted from the next Commit, the sender of the proposal SHOULD retransmit it in the new epoch. <del> [[ OPEN ISSUE: This structure loses the welcome_info_hash, because new participants are no longer expected to have access to the Commit message adding them to the group.  It might be we need to re- introduce this assumption, though it seems like the information confirmed by the welcome_info_hash is confirmed at the next epoch change anyway. ]] </del> <ins> The "path" field of a Commit message MUST be populated if the Commit covers at least one Update or Remove proposal, i.e., if the length of the "updates" or "removes vectors is greater than zero.  The "path"field MUST also be populated if the Commit covers no proposals at all (i.e., if all three proposal vectors are empty).  The "path"field MAY be omitted if the Commit covers only Add proposals. In pseudocode, the logic for whether the "path` field is required is as follows: </ins> A member of the group creates a Commit message and the corresponding Welcome message at the same time, by taking the following steps:

unoccupied leaf, or appended to the right edge of the tree if all leaves are occupied. <del> Create a DirectPath using the new tree (which includes any new members).  The GroupContext for this operation uses the "group_id", "epoch", "tree_hash", and "confirmed_transcript_hash" values in the initial GroupContext object. </del> <ins> Decide whether to populate the "path" field: If the "path" field is required based on the proposals that are in the commit (see above), then it MUST be populated.  Otherwise, the sender MAY omit the "path" field at its discretion. If populating the "path" field: Create a DirectPath using the new tree (which includes any new members).  The GroupContext for this operation uses the "group_id", "epoch", "tree_hash", and "confirmed_transcript_hash" values in the initial GroupContext object. </ins> <del> Assign this DirectPath to the "path" fields in the Commit. </del> <ins> Assign this DirectPath to the "path" field in the Commit. </ins> Apply the DirectPath to the tree, 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. <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 the same length as a "path_secret" value would be. </ins> Generate a new KeyPackage for the Committer's own leaf, with a "parent_hash" extension.  Store it in the ratchet tree and assign it to the "key_package" field in the Commit object.

Identify the lowest common ancestor in the tree of the new member's leaf node and the member sending the Commit <del> Compute the path secret corresponding to the common ancestor node </del> <ins> If the "path" field was populated above: Compute the path secret corresponding to the common ancestor node </ins> Compute an EncryptedGroupSecrets object that encapsulates the <del> "init_secret" for the current epoch and the path secret for the common ancestor. </del> <ins> "init_secret" for the current epoch and the path secret (if present). </ins> Construct a Welcome message from the encrypted GroupInfo object and the encrypted group secrets.

leaf, or appended to the right edge of the tree if all leaves are occupied. <del> Process the "path" value using the ratchet tree the provisional GroupContext, to update the ratchet tree and generate the "commit_secret": </del> <ins> Verify that the "path" value is populated if either of the "updates" or "removes" vectors has length greater than zero, or if all of the "updates", "removes", and "adds" vectors are empty. Otherwise, the "path" value MAY be omitted. If the "path" value is populated: Process the "path" value using the ratchet tree the provisional GroupContext, to update the ratchet tree and generate the "commit_secret": </ins> Apply the DirectPath to the tree, as described in synchronizing-views-of-the-tree, and store "key_package" at the

Define "commit_secret" as the value "path_secret[n+1]" derived from the "path_secret[n]" value assigned to the root node. <del> Update the new GroupContexts confirmed and interim transcript </del> <ins> If the "path" value is not populated: Define "commit_secret" as the all-zero vector of the same length as a "path_secret" value would be. Update the new GroupContext's confirmed and interim transcript </ins> hashes using the new Commit. Use the "commit_secret", the provisional GroupContext, and the

Update the leaf at index "index" with the private key corresponding to the public key in the node. <ins> If the "path_secret" value is set in the GroupSecrets object: </ins> Identify the lowest common ancestor of the leaves at "index" and at "GroupInfo.signer_index".  Set the private key for this <del> node to the private key derived from the "path_secret" in the GroupSecrets object. </del> <ins> node to the private key derived from the "path_secret". </ins> For each parent of the common ancestor, up to the root of the tree, derive a new path secret and set the private key for the