openmls/group/fork_resolution/
reboot.rs1use openmls_traits::signatures::Signer;
6
7use crate::{
8 credentials::CredentialWithKey,
9 extensions::errors::InvalidExtensionError,
10 group::{
11 commit_builder::{CommitBuilder, CommitMessageBundle, Initial},
12 mls_group::builder::MlsGroupBuilder,
13 CommitBuilderStageError, CreateCommitError, Extensions, GroupId, Member, MlsGroup,
14 NewGroupError,
15 },
16 prelude::GroupContext,
17 prelude::KeyPackage,
18 storage::OpenMlsProvider,
19};
20
21impl MlsGroup {
22 pub fn reboot(&'_ self, group_id: GroupId) -> RebootBuilder<'_> {
25 let group_builder = MlsGroup::builder()
26 .with_wire_format_policy(self.configuration().wire_format_policy)
27 .padding_size(self.configuration().padding_size)
28 .set_past_epoch_deletion_policy(self.configuration().past_epoch_deletion_policy.clone())
29 .number_of_resumption_psks(self.configuration().number_of_resumption_psks)
30 .use_ratchet_tree_extension(self.configuration().use_ratchet_tree_extension)
31 .sender_ratchet_configuration(self.configuration().sender_ratchet_configuration)
32 .ciphersuite(self.ciphersuite())
33 .with_group_id(group_id);
34
35 RebootBuilder {
36 group: self,
37 group_builder,
38 }
39 }
40}
41
42pub struct RebootBuilder<'a> {
44 group: &'a MlsGroup,
45 group_builder: MlsGroupBuilder,
46}
47
48impl<'a> RebootBuilder<'a> {
49 pub fn old_group_context_extensions(&self) -> &Extensions<GroupContext> {
52 self.group.context().extensions()
53 }
54
55 pub fn old_members(&self) -> impl Iterator<Item = Member> + 'a {
57 self.group
58 .members()
59 .filter(|member| member.index != self.group.own_leaf_index())
60 }
61
62 pub fn refine_group_builder(
64 self,
65 mut f: impl FnMut(MlsGroupBuilder) -> MlsGroupBuilder,
66 ) -> Self {
67 Self {
68 group_builder: f(self.group_builder),
69 ..self
70 }
71 }
72
73 #[openmls_traits::maybe_async]
77 pub async fn finish<Provider: OpenMlsProvider>(
78 self,
79 extensions: Extensions<GroupContext>,
80 new_members: Vec<KeyPackage>,
81 refine_commit_builder: impl FnMut(CommitBuilder<Initial>) -> CommitBuilder<Initial>,
82 provider: &Provider,
83 signer: &impl Signer,
84 credential_with_key: CredentialWithKey,
85 ) -> Result<(MlsGroup, CommitMessageBundle), RebootError<Provider::StorageError>> {
86 let group_builder = self.group_builder.with_group_context_extensions(extensions);
87
88 let mut new_group = group_builder
89 .build(provider, signer, credential_with_key)
90 .await?;
91
92 new_group
93 .commit_builder()
94 .propose_adds(new_members)
95 .pipe_through(refine_commit_builder)
96 .load_psks(provider.storage())
97 .await?
98 .build(provider.rand(), provider.crypto(), signer, |_| true)?
99 .stage_commit(provider)
100 .await
101 .map_err(RebootError::CommitBuilderStage)
102 .map(|message_bundle| (new_group, message_bundle))
103 }
104}
105
106#[derive(Debug, thiserror::Error)]
108pub enum RebootError<StorageError> {
109 #[error(transparent)]
111 InvalidExtension(#[from] InvalidExtensionError),
112 #[error(transparent)]
114 NewGroup(#[from] NewGroupError<StorageError>),
115 #[error(transparent)]
117 CreateCommit(#[from] CreateCommitError),
118 #[error(transparent)]
120 CommitBuilderStage(#[from] CommitBuilderStageError<StorageError>),
121}
122
123trait PipeThrough: Sized {
126 fn pipe_through<T: Sized, F: FnMut(Self) -> T>(self, mut f: F) -> T {
127 f(self)
128 }
129}
130
131impl<T> PipeThrough for T {}
132
133#[cfg(test)]
134mod test {
135 use crate::{
136 framing::MlsMessageIn,
137 group::{
138 mls_group::tests_and_kats::utils::{setup_alice_bob_group, setup_client},
139 tests_and_kats::utils::{generate_key_package, CredentialWithKeyAndSigner},
140 Extensions, GroupId, StagedWelcome,
141 },
142 };
143
144 #[openmls_test::openmls_test]
145 fn example_reboot() {
146 let alice_provider = &Provider::default();
147 let bob_provider = &Provider::default();
148 let charlie_provider = &Provider::default();
149 let dave_provider = &Provider::default();
150
151 let (mut alice_group, alice_signer, mut bob_group, bob_signer, alice_cwk, bob_cwk) =
153 setup_alice_bob_group(ciphersuite, alice_provider, bob_provider);
154
155 let (charlie_cwk, charlie_kpb, charlie_signer, _charlie_sig_pk) =
156 setup_client("Charlie", ciphersuite, charlie_provider);
157
158 let (_dave_cwk, dave_kpb, _dave_signer, _dave_sig_pk) =
159 setup_client("Dave", ciphersuite, dave_provider);
160
161 let bob_cwkas = CredentialWithKeyAndSigner {
162 credential_with_key: bob_cwk.clone(),
163 signer: bob_signer.clone(),
164 };
165
166 let charlie_cwkas = CredentialWithKeyAndSigner {
167 credential_with_key: charlie_cwk.clone(),
168 signer: charlie_signer.clone(),
169 };
170
171 alice_group
173 .commit_builder()
174 .propose_adds(Some(charlie_kpb.key_package().clone()))
175 .load_psks(alice_provider.storage())
176 .unwrap()
177 .build(
178 alice_provider.rand(),
179 alice_provider.crypto(),
180 &alice_signer,
181 |_| true,
182 )
183 .unwrap()
184 .stage_commit(alice_provider)
185 .unwrap();
186
187 bob_group
188 .commit_builder()
189 .propose_adds(Some(dave_kpb.key_package().clone()))
190 .load_psks(bob_provider.storage())
191 .unwrap()
192 .build(
193 bob_provider.rand(),
194 bob_provider.crypto(),
195 &bob_signer,
196 |_| true,
197 )
198 .unwrap()
199 .stage_commit(bob_provider)
200 .unwrap();
201
202 alice_group.merge_pending_commit(alice_provider).unwrap();
203 bob_group.merge_pending_commit(bob_provider).unwrap();
204
205 let bob_new_kpb =
207 generate_key_package(ciphersuite, Extensions::empty(), bob_provider, bob_cwkas);
208
209 let charlie_new_kpb = generate_key_package(
210 ciphersuite,
211 Extensions::empty(),
212 charlie_provider,
213 charlie_cwkas,
214 );
215
216 let (mut new_alice_group, message_bundle) = alice_group
218 .reboot(GroupId::from_slice(b"new group id"))
219 .finish(
220 Extensions::empty(),
221 vec![
222 bob_new_kpb.key_package().clone(),
223 charlie_new_kpb.key_package().clone(),
224 ],
225 |builder| builder,
226 alice_provider,
227 &alice_signer,
228 alice_cwk.clone(),
229 )
230 .unwrap();
231
232 let (_commit, welcome, _group_info) = message_bundle.into_messages();
233 new_alice_group
234 .merge_pending_commit(alice_provider)
235 .unwrap();
236
237 let welcome = welcome.unwrap();
239 let welcome: MlsMessageIn = welcome.into();
240 let welcome = welcome.into_welcome().unwrap();
241 let ratchet_tree = new_alice_group.export_ratchet_tree();
242
243 let new_bob_group = StagedWelcome::new_from_welcome(
244 bob_provider,
245 alice_group.configuration(),
246 welcome.clone(),
247 Some(ratchet_tree.clone().into()),
248 )
249 .unwrap()
250 .into_group(bob_provider)
251 .unwrap();
252
253 let new_group_charlie = StagedWelcome::new_from_welcome(
254 charlie_provider,
255 alice_group.configuration(),
256 welcome.clone(),
257 Some(ratchet_tree.clone().into()),
258 )
259 .unwrap()
260 .into_group(bob_provider)
261 .unwrap();
262
263 let alice_comparison = new_alice_group
264 .export_secret(alice_provider.crypto(), "comparison", b"", 32)
265 .unwrap();
266
267 let bob_comparison = new_bob_group
268 .export_secret(bob_provider.crypto(), "comparison", b"", 32)
269 .unwrap();
270
271 let charlie_comparison = new_group_charlie
272 .export_secret(charlie_provider.crypto(), "comparison", b"", 32)
273 .unwrap();
274
275 assert_eq!(alice_comparison, bob_comparison);
276 assert_eq!(alice_comparison, charlie_comparison);
277 }
278}