Original text:
Every node, regardless of whether the node is blank or populated, has a corresponding _hash_ that summarizes the contents of the subtree below that node.  The rules for computing these hashes are described <del> in tree-hashes. </del>  5.3. is first added, because the process of adding the leaf does not give it access to all of the nodes above it in the tree.  Leaves are "merged" as they receive the private keys for nodes, as described in <del> ratchet-tree-updates. </del>  5.4. The "tree_hash" field contains a commitment to the contents of the group's ratchet tree and the credentials for the members of the <del> group, as described in tree-hashes. </del>  The "confirmed_transcript_hash" field contains a running hash over the handshake messages that led to this state. 7.5. <del> As described in ratchet-tree-commits, each MLS message needs to transmit a ClientInitKey leaf and node values along its direct path. The path contains a public key and encrypted secret value for all intermediate nodes in the path above the leaf.  The path is ordered from the closest node to the leaf to the root; each node MUST be the parent of its predecessor. </del>  The number of ciphertexts in the "encrypted_path_secret" vector MUST be equal to the length of the resolution of the corresponding copath list of discarded proposals in the Commit. Each proposal covered by the Commit is identified by a ProposalID <del> structure.  The "sender" field in this structure indicates the member of the group that sent the proposal (according to their index in the ratchet tree).  The "hash" field contains the hash of the MLSPlaintext in which the Proposal was sent, using the hash function for the group's ciphersuite. </del>  The sender of a Commit message MUST include in it all proposals that it has received during the current epoch.  Proposals that recipients confirmed by the welcome_info_hash is confirmed at the next epoch change anyway. ]]  A member of the group applies a Commit message by taking the following steps: leaf, or appended to the right edge of the tree if all leaves are occupied. <del> Process the "path" value to update the ratchet tree referenced by the provisional GroupContext and generate the "commit_secret": </del>  Update the ratchet tree by replacing nodes in the direct path of the sender with the corresponding nodes in the path (see direct-paths). The "commit_secret" is the value "path_secret[n+1]" derived <del> from the "path_secret[n]" value associated with the root node. </del>  Use the "commit_secret", the provisional GroupContext, and the init secret from the previous epoch to compute the epoch secret message, return an error. Decrypt the "encrypted_key_package" using HPKE with the algorithms <del> indicated by the ciphersuite and the HPKE public key in the ClientInitKey. </del>  <del> Decrypt the "encrypted_group_info" field using the key and nonce in the decrypted KeyPackage object. </del>  Verify the signature on the GroupInfo object.  The signature input comprises all of the fields in the GroupInfo object except the the "tree" array divided by two. Construct a new group state using the information in the GroupInfo <del> object and verify the integrity of the tree using the "tree_hash". The new member's position in the tree is "index", as defined above. Identify the lowest node at which the direct paths from "index" and "signer_index" overlap.  Set private keys for that node and its parents up to the root of the tree, using the "path_secret" from the KeyPackage and following the algorithm in ratchet-tree- commits to move up the tree. </del>  11.

Reviewer comments:
I have managed to implement Proposal / Commit in mlspp, but it required some modifications to the Welcome and Commit structs and processes. Two major things to point out: This PR proposes to send the same DirectPath to both new members and current members. This avoids the need to have two code paths and special tree math to implant path secrets, at the cost of some bloat in the Welcome and Commit messages. As a result, the sequencing of GroupContexts is a bit tricky. The GroupContext used for encrypting/decrypting DirectPaths is an intermediate context -- the new group context, but before the transcript is updated. All that said, this version is . The implementation exercise highlighted that the net effect of Proposal/Commit was to isolate basically all of the state management complexity to three methods in the client stack: Processing of Welcome messages Generation of Commit+Welcome messages Processing of Commit messages This PR reflects the algorithms for those three things.
NAME I added one minor change (order of fields) and rebased. I think this is ready to merge. I'm also about to drop offline for a bit, so please pull the trigger if it looks good to you.
Ack, I’ll take a look in tonight or tomorrow morning and merge it.
I am merging this now because it includes fixes we definitely need even though I note that I have one philosophical concern and one security concern... Multiplying the numbers of transcript hashes in the state is an implementation concern and not a protocol concern, I would much prefer keep only the real (confirmed) transcript hash. (The implementation should keep the current state and one provisional updated state but the struct shouldn't have to keep both) Sending the initsecret and the updatesecret at once seem equivalent to sending the epoch secret but I don't think it is actually the case since it could be rejected by the DS, breaking the encryption key of that message would hence still have consequences even if the message is rejected. I'll open an issue for that.
Here's the Weekly Digest for : - - Last week 8 issues were created. Of these, 0 issues have been closed and 8 issues are still open. :greenheart: , by :greenheart: , by :greenheart: , by :greenheart: , by :greenheart: , by :greenheart: , by :greenheart: , by :greenheart: , by :speaker: , by It received 1 comments. - - Last week, 6 pull requests were created, updated or merged. Last week, 1 pull request was opened. :greenheart: , by Last week, 3 pull requests were updated. :yellowheart: , by :yellowheart: , by :yellowheart: , by Last week, 2 pull requests were merged. :purpleheart: , by :purpleheart: , by - - Last week there were 10 commits. :hammerandwrench: by :hammerandwrench: by :hammerandwrench: by :hammerandwrench: by :hammerandwrench: by :hammerandwrench: by :hammerandwrench: by :hammerandwrench: by :hammerandwrench: by :hammerandwrench: by - - Last week there were 2 contributors. :bustinsilhouette: :bustin_silhouette: - - Last week there was 1 stargazer. :star: You are the star! :star2: - - Last week there were no releases. - - That's all for last week, please :eyes: Watch and :star: Star the repository to receive next weekly updates. :smiley: You can also .
Here's the Weekly Digest for : - - Last week 4 issues were created. Of these, 0 issues have been closed and 4 issues are still open. :greenheart: , by :greenheart: , by :greenheart: , by :greenheart: , by - - Last week, 14 pull requests were created, updated or merged. Last week, 8 pull requests were updated. :yellowheart: , by :yellowheart: , by :yellowheart: , by :yellowheart: , by :yellowheart: , by :yellowheart: , by :yellowheart: , by :yellowheart: , by Last week, 6 pull requests were merged. :purpleheart: , by :purpleheart: , by :purpleheart: , by :purpleheart: , by :purpleheart: , by :purpleheart: , by - - Last week there were 10 commits. :hammerandwrench: by :hammerandwrench: by :hammerandwrench: by :hammerandwrench: by :hammerandwrench: by :hammerandwrench: by :hammerandwrench: by :hammerandwrench: by :hammerandwrench: by :hammerandwrench: by - - Last week there were 3 contributors. :bustinsilhouette: :bustinsilhouette: :bustinsilhouette: - - Last week there were no stargazers. - - Last week there were no releases. - - That's all for last week, please :eyes: Watch and :star: Star the repository to receive next weekly updates. :smiley: You can also .
Ok, this looks good to me. I think we don't have to derive that many keys in the main KS but that's ok for now, we can optimize later.

Revised text:
Every node, regardless of whether the node is blank or populated, has a corresponding _hash_ that summarizes the contents of the subtree below that node.  The rules for computing these hashes are described  <ins> in tree-hashes-and-signatures. </ins> 5.3. is first added, because the process of adding the leaf does not give it access to all of the nodes above it in the tree.  Leaves are "merged" as they receive the private keys for nodes, as described in  <ins> ratchet-tree-evolution. </ins> 5.4. The "tree_hash" field contains a commitment to the contents of the group's ratchet tree and the credentials for the members of the  <ins> group, as described in tree-hashes-and-signatures. </ins> The "confirmed_transcript_hash" field contains a running hash over the handshake messages that led to this state. 7.5.  <ins> As described in commit, each MLS Commit message needs to transmit a ClientInitKey leaf and node values along its direct path.  The path contains a public key and encrypted secret value for all intermediate nodes in the path above the leaf.  The path is ordered from the closest node to the leaf to the root; each node MUST be the parent of its predecessor. </ins> The number of ciphertexts in the "encrypted_path_secret" vector MUST be equal to the length of the resolution of the corresponding copath list of discarded proposals in the Commit. Each proposal covered by the Commit is identified by a ProposalID  <ins> value, which contains the hash of the MLSPlaintext in which the Proposal was sent, using the hash function for the group's ciphersuite. </ins> The sender of a Commit message MUST include in it all proposals that it has received during the current epoch.  Proposals that recipients confirmed by the welcome_info_hash is confirmed at the next epoch change anyway. ]] <ins> A member of the group creates a Commit message and the corresponding Welcome message at the same time, by taking the following steps: Construct an initial Commit object with "updates", "removes", "adds", and "ignored" fields populated from Proposals received during the current epoch, and an empty "path" field. Generate a provisional GroupContext object by applying the proposals referenced in the initial Commit object in the order provided, as described in proposals.  Add proposals are applied left to right: Each Add proposal is applied at the leftmost unoccupied leaf, or appended to the right edge of the tree if all leaves are occupied. Create an initial, partial GroupInfo object reflecting the following values: Group ID: The group ID for the group Epoch: The epoch ID for the next epoch Tree: The group's ratchet tree after the commit has been applied Prior confirmed transcript hash: The confirmed transcript hash for the current state of the group (not the provisional state) Create a DirectPath using the new tree (which includes any new members).  The GroupContext for this operation uses the "group_id", "epoch", "tree", and "prior_confirmed_transcript_hash" values in the initial GroupInfo object. Assign this DirectPath to the "path" fields in the Commit and GroupInfo objects. The "commit_secret" is the value "path_secret[n+1]" derived from the "path_secret[n]" value associated to the root node. Construct an MLSPlaintext object containing the Commit object. Use the "commit_secret" to advance the key schedule and compute the "confirmation" value in the MLSPlaintext.  Sign the MLSPlaintext using the current epoch's GroupContext as context. Complete the GroupInfo by populating the following fields: Confirmed transcript hash: The confirmed transcript hash including the current Commit object Interim transcript hash: The interim transcript hash including the current Commit object Confirmation: The confirmation from the MLSPlaintext Sign the GroupInfo using the member's private signing key Encrypt the GroupInfo using the key and nonce derived from the "init_secret" for the current epoch (see welcoming-new-members) For each new member in the group, compute an EncryptedKeyPackage object that encapsulates the "init_secret" for the current epoch. Construct a Welcome message from the encrypted GroupInfo object and the encrypted key packages. </ins> A member of the group applies a Commit message by taking the following steps: leaf, or appended to the right edge of the tree if all leaves are occupied.  <ins> Process the "path" value using the ratchet tree the provisional GroupContext, to update the ratchet tree and generate the "commit_secret": </ins> Update the ratchet tree by replacing nodes in the direct path of the sender with the corresponding nodes in the path (see direct-paths). The "commit_secret" is the value "path_secret[n+1]" derived  <ins> from the "path_secret[n]" value associated to the root node. Update the new GroupContexts confirmed and interim transcript hashes using the new Commit. </ins> Use the "commit_secret", the provisional GroupContext, and the init secret from the previous epoch to compute the epoch secret message, return an error. Decrypt the "encrypted_key_package" using HPKE with the algorithms  <ins> indicated by the ciphersuite and the HPKE private key corresponding to the ClientInitKey. </ins>  <ins> From the "init_secret" in the decrypted KeyPackage object, derive the "group_info_secret", "group_info_key", and "group_info_nonce". Use the key and nonce to decrypt the "encrypted_group_info" field. </ins> Verify the signature on the GroupInfo object.  The signature input comprises all of the fields in the GroupInfo object except the the "tree" array divided by two. Construct a new group state using the information in the GroupInfo  <ins> object.  The new member's position in the tree is "index", as defined above.  In particular, the confirmed transcript hash for the new state is the "prior_confirmed_transcript_hash" in the GroupInfo object. Process the "path" field in the GroupInfo to update the new group state: Update the ratchet tree by replacing nodes in the direct path of the sender with the corresponding nodes in the path (see direct-paths). The "commit_secret" is the value "path_secret[n+1]" derived from the "path_secret[n]" value associated to the root node. Use the "init_secret" from the KeyPackage object together with the decrypted "commit_secret" to generate the epoch secret and other derived secrets for the current epoch. Set the confirmed transcript hash in the new state to the value of the "confirmed_transcript_hash" in the GroupInfo. Verify the confirmation MAC in the GroupInfo using the derived confirmation key and the "confirmed_transcript_hash" from the GroupInfo. </ins> 11.