core_crypto/mls/conversation/mutable/
commit.rs1use std::{borrow::Borrow, collections::HashMap};
4
5use openmls::prelude::KeyPackageIn;
6
7use super::history_sharing::HistoryClientUpdateOutcome;
8use crate::{
9 ClientId, ClientIdRef, CredentialRef, GroupInfoBundle, OpenMlsError, RecursiveError,
10 mls::{
11 conversation::{ConversationMut, Error, Result, commit::CommitBundle},
12 credential::Credential,
13 },
14};
15
16impl ConversationMut {
17 pub(super) async fn send_and_merge_commit(&mut self, commit: CommitBundle) -> Result<()> {
18 let history_client_update_result = self.update_history_client().await?;
19 if history_client_update_result == HistoryClientUpdateOutcome::CommitSentAndMerged {
20 return Ok(());
21 }
22
23 match self.send_commit(commit).await {
24 Ok(()) => self.merge_commit().await,
25 e @ Err(_) => {
26 self.clear_pending_commit().await?;
27 e
28 }
29 }
30 }
31
32 pub(super) async fn merge_commit(&mut self) -> Result<()> {
33 self.commit_accepted().await?;
34 let conversation_id = self.id().to_owned();
35 let epoch = self.epoch().await;
36
37 self.tx_context
38 .queue_epoch_changed(conversation_id, epoch)
39 .await
40 .map_err(RecursiveError::context("queueing epoch changed notification"))?;
41
42 Ok(())
43 }
44
45 pub(super) async fn send_commit(&mut self, commit: CommitBundle) -> Result<()> {
47 let transport = self.transport().await?;
48
49 transport
50 .send_commit_bundle(commit)
51 .await
52 .map_err(RecursiveError::context("sending commit bundle"))
53 .map_err(Into::into)
54 }
55
56 pub async fn add_members(&mut self, key_packages: Vec<KeyPackageIn>) -> Result<()> {
58 let commit = self.add_members_inner(key_packages).await?;
59
60 self.send_and_merge_commit(commit).await?;
61
62 Ok(())
63 }
64
65 pub(super) async fn add_members_inner(&mut self, key_packages: Vec<KeyPackageIn>) -> Result<CommitBundle> {
66 self.ensure_no_pending_commit().await?;
67 let backend = self.crypto_provider().await?;
68 let credential = self.credential().await?;
69
70 self.mutate_group(async |_, group, _| {
71 let signer = credential.signature_key();
72 let (commit, welcome, group_info) = group
73 .add_members(&backend, signer, key_packages.clone())
74 .await
75 .map_err(|err| {
76 if Self::err_is_duplicate_signature_key(&err) {
77 Self::clients_with_duplicate_signature_keys(key_packages.as_ref())
78 .map(|affected_clients| Error::DuplicateSignature { affected_clients })
79 .unwrap_or_else(|e| e)
80 } else {
81 OpenMlsError::wrap("group add members")(err).into()
82 }
83 })?;
84
85 Ok(CommitBundle {
86 commit,
87 welcome: Some(welcome),
88 group_info: Self::group_info(group_info)?,
89 encrypted_message: None,
90 })
91 })
92 .await
93 }
94
95 fn err_is_duplicate_signature_key(
96 err: &openmls::prelude::AddMembersError<core_crypto_keystore::CryptoKeystoreError>,
97 ) -> bool {
98 matches!(
99 err,
100 openmls::prelude::AddMembersError::CreateCommitError(
101 openmls::prelude::CreateCommitError::ProposalValidationError(
102 openmls::prelude::ProposalValidationError::DuplicateSignatureKey
103 )
104 )
105 )
106 }
107
108 fn clients_with_duplicate_signature_keys(key_packages: &[KeyPackageIn]) -> Result<Vec<(ClientId, ClientId)>> {
109 let mut seen_signature_keys = HashMap::new();
110 let mut duplicate_pairs = Vec::new();
111
112 for key_package in key_packages {
113 let signature_key = key_package.unverified_credential().signature_key.as_slice().to_vec();
114
115 let client_id: ClientId = key_package
116 .credential()
117 .identity()
118 .try_into()
119 .map_err(RecursiveError::context("client id from bytes"))?;
120
121 if let Some(previous_client_id) = seen_signature_keys.insert(signature_key, client_id.clone()) {
122 duplicate_pairs.push((previous_client_id, client_id));
123 }
124 }
125
126 Ok(duplicate_pairs)
127 }
128
129 pub async fn remove_members(&mut self, clients: &[impl Borrow<ClientIdRef>]) -> Result<()> {
135 self.remove_members_or_history_clients(clients.iter().map(|e| e.borrow().as_ref()))
136 .await
137 }
138
139 pub(crate) async fn remove_members_or_history_clients(
140 &mut self,
141 clients: impl Iterator<Item = &[u8]> + Clone,
142 ) -> Result<()> {
143 self.ensure_no_pending_commit().await?;
144 let backend = self.crypto_provider().await?;
145 let credential = self.credential().await?;
146 let signer = credential.signature_key();
147 let (commit, welcome, group_info) = self
148 .mutate_group(async |_, group, _| {
149 let members = group
150 .members()
151 .filter_map(|member| {
152 clients
153 .clone()
154 .any(move |client_id| client_id == member.credential.identity())
155 .then_some(member.index)
156 })
157 .collect::<Vec<_>>();
158 group
159 .remove_members(&backend, signer, &members)
160 .await
161 .map_err(OpenMlsError::wrap("group remove members"))
162 .map_err(Into::into)
163 })
164 .await?;
165
166 let group_info = Self::group_info(group_info)?;
167
168 self.send_and_merge_commit(CommitBundle {
169 commit,
170 welcome,
171 group_info,
172 encrypted_message: None,
173 })
174 .await
175 }
176
177 pub async fn update_key_material(&mut self) -> Result<()> {
179 let credential = self.credential().await?;
180 let commit = self.set_credential_inner(&credential).await?;
181 self.send_and_merge_commit(commit).await
182 }
183
184 pub async fn set_credential_by_ref(&mut self, credential_ref: &CredentialRef) -> Result<()> {
186 let database = self.database().await?;
187 let credential = credential_ref
188 .load(&*database)
189 .await
190 .map_err(RecursiveError::context("loading credential from ref"))?;
191 let commit = self.set_credential_inner(&credential).await?;
192
193 self.send_and_merge_commit(commit).await
194 }
195
196 pub(crate) async fn set_credential_inner(&mut self, credential: &Credential) -> Result<CommitBundle> {
199 self.ensure_no_pending_commit().await?;
200 let backend = self.crypto_provider().await?;
201 let credential = credential.clone();
202
203 self.mutate_group(async |_, group, _| {
204 let updated_leaf_node = {
208 let leaf_node = group
209 .own_leaf()
210 .ok_or(Error::MlsGroupInvalidState("own leaf node not found"))?;
211 if leaf_node.credential() == &credential.mls_credential {
212 None
213 } else {
214 let mut leaf_node = leaf_node.clone();
215 leaf_node.set_credential_with_key(credential.to_mls_credential_with_key());
216 Some(leaf_node)
217 }
218 };
219
220 let (commit, welcome, group_info) = group
221 .explicit_self_update(&backend, &credential.signature_key_pair, updated_leaf_node)
222 .await
223 .map_err(OpenMlsError::wrap("group self update"))?;
224
225 let group_info = group_info.ok_or(Error::MissingGroupInfo)?;
227 let group_info = GroupInfoBundle::try_new_full_plaintext(group_info)?;
228
229 Ok(CommitBundle {
230 welcome,
231 commit,
232 group_info,
233 encrypted_message: None,
234 })
235 })
236 .await
237 }
238
239 pub async fn commit_pending_proposals(&mut self) -> Result<()> {
241 self.ensure_no_pending_commit().await?;
242 let commit = self.commit_pending_proposals_inner().await?;
243 let Some(commit) = commit else {
244 return Ok(());
245 };
246 self.send_and_merge_commit(commit).await
247 }
248
249 pub(crate) async fn commit_pending_proposals_inner(&mut self) -> Result<Option<CommitBundle>> {
250 if self.group().await.pending_proposals().next().is_none() {
251 return Ok(None);
252 }
253
254 let crypto_provider = self.crypto_provider().await?;
255 let credential = self.credential().await?;
256
257 let (commit, welcome, openmls_group_info) = self
258 .mutate_group(async |_, group, _| {
259 let signer = &credential.signature_key_pair;
260 group
261 .commit_to_pending_proposals(&crypto_provider, signer)
262 .await
263 .map_err(OpenMlsError::wrap("group commit to pending proposals"))
264 .map_err(Into::into)
265 })
266 .await?;
267 let group_info = GroupInfoBundle::try_new_full_plaintext(
268 openmls_group_info.expect("creating a commit always produces a group info"),
269 )?;
270
271 Ok(Some(CommitBundle {
272 welcome,
273 commit,
274 group_info,
275 encrypted_message: None,
276 }))
277 }
278
279 pub(crate) async fn commit_inline_proposals(
280 &mut self,
281 proposals: Vec<openmls::prelude::Proposal>,
282 ) -> Result<Option<CommitBundle>> {
283 if proposals.is_empty() {
284 return Ok(None);
285 }
286
287 let provider = &self.crypto_provider().await?;
288 let credential = self.credential().await?;
289
290 let (commit, welcome, openmls_group_info) = self
291 .mutate_group(async |_, group, _| {
292 let signer = &credential.signature_key_pair;
293 group
294 .commit_to_inline_proposals(provider, signer, proposals)
295 .await
296 .map_err(OpenMlsError::wrap("group commit to pending proposals"))
297 .map_err(Into::into)
298 })
299 .await?;
300 let group_info = GroupInfoBundle::try_new_full_plaintext(
301 openmls_group_info.expect("creating a commit always produces a group info"),
302 )?;
303
304 Ok(Some(CommitBundle {
305 welcome,
306 commit,
307 group_info,
308 encrypted_message: None,
309 }))
310 }
311}