From 44b45e6924840d74a7006f79acc5f03ca5e607b3 Mon Sep 17 00:00:00 2001 From: SimonThormeyer Date: Tue, 1 Jul 2025 10:59:52 +0200 Subject: [PATCH 1/3] feat: allow committing to inline proposals directly This allows creating a commit from proposals without sending the proposals over the wire first. --- openmls/src/group/mls_group/processing.rs | 46 +++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/openmls/src/group/mls_group/processing.rs b/openmls/src/group/mls_group/processing.rs index 82d4b8b900..b5d70d0e29 100644 --- a/openmls/src/group/mls_group/processing.rs +++ b/openmls/src/group/mls_group/processing.rs @@ -125,6 +125,52 @@ impl MlsGroup { )) } + /// Like [Self::commit_to_pending_proposals] but with additional inline proposals. + #[allow(clippy::type_complexity)] + pub async fn commit_to_inline_proposals( + &mut self, + backend: &impl OpenMlsCryptoProvider, + signer: &impl Signer, + inline_proposals: Vec, + ) -> Result< + (MlsMessageOut, Option, Option), + CommitToPendingProposalsError, + > { + self.is_operational()?; + + let empty_proposal_store = Default::default(); + + // Create Commit over all inline proposals + // TODO #751 + let params = CreateCommitParams::builder() + .framing_parameters(self.framing_parameters()) + .proposal_store(&empty_proposal_store) + .inline_proposals(inline_proposals) + .build(); + let create_commit_result = self.group.create_commit(params, backend, signer).await?; + + // Convert PublicMessage messages to MLSMessage and encrypt them if required by + // the configuration + let mls_message = self.content_to_mls_message(create_commit_result.commit, backend)?; + + // Set the current group state to [`MlsGroupState::PendingCommit`], + // storing the current [`StagedCommit`] from the commit results + self.group_state = MlsGroupState::PendingCommit(Box::new(PendingCommitState::Member( + create_commit_result.staged_commit, + ))); + + // Since the state of the group might be changed, arm the state flag + self.flag_state_change(); + + Ok(( + mls_message, + create_commit_result + .welcome_option + .map(|w| MlsMessageOut::from_welcome(w, self.group.version())), + create_commit_result.group_info, + )) + } + /// Merge a [StagedCommit] into the group after inspection. As this advances /// the epoch of the group, it also clears any pending commits. pub async fn merge_staged_commit( From 8d44e3836b836f9785b8de139fd712bcfb613ca6 Mon Sep 17 00:00:00 2001 From: SimonThormeyer Date: Wed, 2 Jul 2025 14:11:49 +0200 Subject: [PATCH 2/3] fix: trim diff tree after processing all proposals Previously, the tree shrink function would be called eagerly after each remove proposal. If the shrink threshold was reached, the blanked out nodes were removed from the diff tree. If there were additional add proposals in a commit that triggered the tree to grow again, this resulted in incorrect state if there were FEWER add proposals than remove proposals. That's because the blanked out nodes removed during shrinking were not re-added during growing. To fix this, we don't shrink/trim the tree eagerly after each remove proposal, but after remove and add proposals have been processed. --- openmls/src/group/public_group/diff/apply_proposals.rs | 2 ++ openmls/src/treesync/diff.rs | 3 +-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/openmls/src/group/public_group/diff/apply_proposals.rs b/openmls/src/group/public_group/diff/apply_proposals.rs index 5cbe32a85d..a744f186c2 100644 --- a/openmls/src/group/public_group/diff/apply_proposals.rs +++ b/openmls/src/group/public_group/diff/apply_proposals.rs @@ -153,6 +153,8 @@ impl<'a> PublicGroupDiff<'a> { invitation_list.push((leaf_index, add_proposal.clone())) } + self.diff.trim_tree(); + // Process PSK proposals let presharedkeys: Vec = proposal_queue .filtered_by_type(ProposalType::PreSharedKey) diff --git a/openmls/src/treesync/diff.rs b/openmls/src/treesync/diff.rs index 5a1e58fa94..e7ce6b7a48 100644 --- a/openmls/src/treesync/diff.rs +++ b/openmls/src/treesync/diff.rs @@ -144,7 +144,7 @@ impl TreeSyncDiff<'_> { /// Trims the tree by shrinking it until the last full leaf is in the /// right part of the tree. - fn trim_tree(&mut self) { + pub(crate) fn trim_tree(&mut self) { // Nothing to trim if there's only one leaf left. if self.leaf_count() == MIN_TREE_SIZE { return; @@ -257,7 +257,6 @@ impl TreeSyncDiff<'_> { // This also erases any cached tree hash in the direct path. self.diff .set_direct_path_to_node(leaf_index, &TreeSyncParentNode::blank()); - self.trim_tree(); } /// Derive a new direct path for the leaf with the given index. From 056492bdfe314ac04e6b7fdaaea89bdb092d048e Mon Sep 17 00:00:00 2001 From: SimonThormeyer Date: Fri, 4 Jul 2025 11:32:58 +0200 Subject: [PATCH 3/3] chore: fix clippy warnings --- openmls/src/credentials/tests.rs | 2 +- openmls/src/extensions/mod.rs | 2 +- openmls/src/messages/proposals.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/openmls/src/credentials/tests.rs b/openmls/src/credentials/tests.rs index 9a39f936f9..8f345a193b 100644 --- a/openmls/src/credentials/tests.rs +++ b/openmls/src/credentials/tests.rs @@ -44,7 +44,7 @@ fn that_unknown_credential_types_are_de_serialized_correctly() { CredentialType::Unknown(got_proposal_type) => { assert_eq!(credential_type, got_proposal_type); } - other => panic!("Expected `CredentialType::Unknown`, got `{:?}`.", other), + other => panic!("Expected `CredentialType::Unknown`, got `{other:?}`."), } // Test serialization. diff --git a/openmls/src/extensions/mod.rs b/openmls/src/extensions/mod.rs index 7beacd2074..fce46b3010 100644 --- a/openmls/src/extensions/mod.rs +++ b/openmls/src/extensions/mod.rs @@ -604,7 +604,7 @@ mod test { assert_eq!(extension_type, got_extension_type); assert_eq!(extension_data, &got_extension_data.0); } - other => panic!("Expected `Extension::Unknown`, got {:?}", other), + other => panic!("Expected `Extension::Unknown`, got {other:?}"), } // Test serialization. diff --git a/openmls/src/messages/proposals.rs b/openmls/src/messages/proposals.rs index 87c3f0d3a0..c76a7a721c 100644 --- a/openmls/src/messages/proposals.rs +++ b/openmls/src/messages/proposals.rs @@ -597,7 +597,7 @@ mod tests { ProposalType::Unknown(got_proposal_type) => { assert_eq!(proposal_type, got_proposal_type); } - other => panic!("Expected `ProposalType::Unknown`, got `{:?}`.", other), + other => panic!("Expected `ProposalType::Unknown`, got `{other:?}`."), } // Test serialization.