1use openmls_traits::{crypto::OpenMlsCrypto, signatures::Signer, types::Ciphersuite};
2use tls_codec::Serialize;
3
4#[cfg(feature = "extensions-draft")]
5use crate::schedule::application_export_tree::ApplicationExportTree;
6use crate::{
7 binary_tree::{array_representation::TreeSize, LeafNodeIndex},
8 credentials::CredentialWithKey,
9 error::LibraryError,
10 extensions::Extensions,
11 group::{
12 config::PastEpochDeletionPolicy, past_secrets::MessageSecretsStore,
13 public_group::errors::PublicGroupBuildError, BranchInfo, CommitBuilderStageError,
14 CommitMessageBundle, CreateCommitError, GroupContext, GroupId, MlsGroup,
15 MlsGroupCreateConfig, MlsGroupCreateConfigBuilder, MlsGroupState, NewGroupError,
16 PublicGroup, ReInitInfo, WireFormatPolicy,
17 },
18 key_packages::{KeyPackage, Lifetime},
19 schedule::{
20 psk::{load_psks, store::ResumptionPskStore, PskSecret},
21 EpochSecretsResult, InitSecret, JoinerSecret, KeySchedule, PreSharedKeyId,
22 },
23 storage::OpenMlsProvider,
24 tree::sender_ratchet::SenderRatchetConfiguration,
25 treesync::{
26 errors::LeafNodeValidationError,
27 node::leaf_node::{Capabilities, CapabilitiesPolicy, LeafNode},
28 },
29};
30
31#[cfg(feature = "virtual-clients-draft")]
32use crate::treesync::node::leaf_node::{resolve_capabilities, LeafNodeConstraints};
33
34#[derive(Default, Debug)]
36pub struct MlsGroupBuilder {
37 group_id: Option<GroupId>,
38 mls_group_create_config_builder: MlsGroupCreateConfigBuilder,
39 replace_old_group: bool,
40 psk_ids: Vec<PreSharedKeyId>,
41 #[cfg(feature = "virtual-clients-draft")]
44 vc_emulation_group_id: Option<GroupId>,
45}
46
47impl MlsGroupBuilder {
48 pub(super) fn new() -> Self {
49 Self::default()
50 }
51
52 pub fn with_group_id(mut self, group_id: GroupId) -> Self {
54 self.group_id = Some(group_id);
55 self
56 }
57
58 #[cfg(feature = "virtual-clients-draft")]
75 pub fn vc_emulation(mut self, emulation_group_id: &GroupId) -> Self {
76 self.vc_emulation_group_id = Some(emulation_group_id.clone());
77 self
78 }
79
80 pub fn replace_old_group(mut self) -> Self {
82 self.replace_old_group = true;
83 self
84 }
85
86 pub fn branch(self, branch_info: BranchInfo) -> BranchGroupBuilder {
99 BranchGroupBuilder {
100 group_builder: self,
101 branch_info,
102 extensions: None,
103 force_self_update: false,
104 }
105 }
106
107 pub fn reinit(self, reinit_info: ReInitInfo) -> ReInitGroupBuilder {
120 ReInitGroupBuilder {
121 group_builder: self,
122 reinit_info,
123 force_self_update: false,
124 }
125 }
126
127 #[openmls_traits::maybe_async]
129 pub async fn build<Provider: OpenMlsProvider>(
130 self,
131 provider: &Provider,
132 signer: &impl Signer,
133 credential_with_key: CredentialWithKey,
134 ) -> Result<MlsGroup, NewGroupError<Provider::StorageError>> {
135 self.build_internal(provider, signer, credential_with_key, None)
136 .await
137 }
138
139 #[openmls_traits::maybe_async]
148 pub(super) async fn build_internal<Provider: OpenMlsProvider>(
149 self,
150 provider: &Provider,
151 signer: &impl Signer,
152 credential_with_key: CredentialWithKey,
153 mls_group_create_config_option: Option<MlsGroupCreateConfig>,
154 ) -> Result<MlsGroup, NewGroupError<Provider::StorageError>> {
155 let mls_group_create_config = mls_group_create_config_option
156 .unwrap_or_else(|| self.mls_group_create_config_builder.build());
157 let group_id = self
158 .group_id
159 .unwrap_or_else(|| GroupId::random(provider.rand()));
160 let ciphersuite = mls_group_create_config.ciphersuite;
161
162 provider
163 .crypto()
164 .supports(ciphersuite)
165 .map_err(|_| NewGroupError::UnsupportedCiphersuite(ciphersuite))?;
166
167 #[cfg(feature = "virtual-clients-draft")]
168 if let Some(emulation_group_id) = &self.vc_emulation_group_id {
169 let epoch_id =
170 crate::components::vc_derivation_info::require_newest_vc_derivation_epoch(
171 provider.storage(),
172 emulation_group_id,
173 )
174 .await?;
175 return build_vc_internal(
176 provider,
177 signer,
178 credential_with_key,
179 mls_group_create_config,
180 group_id,
181 self.replace_old_group,
182 epoch_id,
183 )
184 .await;
185 }
186
187 if !self.replace_old_group
188 && MlsGroup::load(provider.storage(), &group_id)
189 .await
190 .map_err(NewGroupError::StorageError)?
191 .is_some()
192 {
193 return Err(NewGroupError::GroupAlreadyExists);
194 }
195
196 let (public_group_builder, commit_secret, leaf_keypair) =
197 PublicGroup::builder(group_id, ciphersuite, credential_with_key)
198 .with_group_context_extensions(
199 mls_group_create_config.group_context_extensions.clone(),
200 )
201 .with_leaf_node_extensions(mls_group_create_config.leaf_node_extensions.clone())
202 .with_lifetime(*mls_group_create_config.lifetime())
203 .with_capabilities(mls_group_create_config.capabilities.clone())
204 .with_capabilities_policy(mls_group_create_config.capabilities_policy)
205 .get_secrets(provider, signer)
206 .map_err(|e| match e {
207 PublicGroupBuildError::LibraryError(e) => NewGroupError::LibraryError(e),
208 PublicGroupBuildError::InvalidExtensions(e) => e.into(),
209 PublicGroupBuildError::LeafNodeBuild(e) => e.into(),
210 })?;
211
212 let serialized_group_context = public_group_builder
213 .group_context()
214 .tls_serialize_detached()
215 .map_err(LibraryError::missing_bound_check)?;
216
217 let joiner_secret = JoinerSecret::new(
221 provider.crypto(),
222 ciphersuite,
223 commit_secret,
224 &InitSecret::random(ciphersuite, provider.rand())
225 .map_err(LibraryError::unexpected_crypto_error)?,
226 &serialized_group_context,
227 )
228 .map_err(LibraryError::unexpected_crypto_error)?;
229
230 let mut resumption_psk_store = ResumptionPskStore::new(32);
232
233 let psk_secret = load_psks(provider.storage(), &resumption_psk_store, &self.psk_ids)
235 .await
236 .and_then(|psks| PskSecret::new(provider.crypto(), ciphersuite, psks))
237 .map_err(|e| {
238 log::debug!("Unexpected PSK error: {e:?}");
239 LibraryError::custom("Unexpected PSK error")
240 })?;
241
242 let mut key_schedule =
243 KeySchedule::init(ciphersuite, provider.crypto(), &joiner_secret, psk_secret)?;
244 key_schedule
245 .add_context(provider.crypto(), &serialized_group_context)
246 .map_err(|_| LibraryError::custom("Using the key schedule in the wrong state"))?;
247
248 let EpochSecretsResult {
249 epoch_secrets,
250 #[cfg(feature = "extensions-draft")]
251 application_exporter,
252 } = key_schedule
253 .epoch_secrets(provider.crypto(), ciphersuite)
254 .map_err(|_| LibraryError::custom("Using the key schedule in the wrong state"))?;
255
256 let (group_epoch_secrets, message_secrets) = epoch_secrets.split_secrets(
257 serialized_group_context,
258 TreeSize::new(1),
259 LeafNodeIndex::new(0u32),
260 );
261
262 let initial_confirmation_tag = message_secrets
263 .confirmation_key()
264 .tag(provider.crypto(), ciphersuite, &[])
265 .map_err(LibraryError::unexpected_crypto_error)?;
266
267 let message_secrets_store = MessageSecretsStore::new_with_secret(
268 mls_group_create_config
269 .join_config
270 .past_epoch_deletion_policy(),
271 message_secrets,
272 );
273
274 let public_group = public_group_builder
275 .with_confirmation_tag(initial_confirmation_tag)
276 .build(provider.crypto())?;
277
278 let resumption_psk = group_epoch_secrets.resumption_psk();
280 resumption_psk_store.add(public_group.group_context().epoch(), resumption_psk.clone());
281
282 #[cfg(feature = "extensions-draft")]
283 #[cfg_attr(not(feature = "virtual-clients-draft"), allow(unused_mut))]
284 let mut application_export_tree = ApplicationExportTree::new(application_exporter);
285
286 #[cfg(feature = "virtual-clients-draft")]
288 if mls_group_create_config.emulation_group {
289 crate::components::vc_derivation_info::register_vc_derivation_epoch(
290 provider.crypto(),
291 provider.storage(),
292 Some(&mut application_export_tree),
293 crate::components::vc_derivation_info::VcDerivationEpochParams::for_public_group(
294 &public_group,
295 LeafNodeIndex::new(0),
296 mls_group_create_config
297 .join_config
298 .vc_derivation_epoch_retention_policy()
299 .clone(),
300 ),
301 )
302 .await?;
303 }
304
305 let mls_group = MlsGroup {
306 mls_group_config: mls_group_create_config.join_config.clone(),
307 own_leaf_nodes: vec![],
308 aad: vec![],
309 #[cfg(feature = "extensions-draft")]
310 safe_aad: crate::framing::SafeAad::empty(),
311 group_state: MlsGroupState::Operational,
312 public_group,
313 group_epoch_secrets,
314 own_leaf_index: LeafNodeIndex::new(0),
315 message_secrets_store,
316 resumption_psk_store,
317 #[cfg(feature = "extensions-draft")]
318 application_export_tree: Some(application_export_tree),
319 #[cfg(feature = "virtual-clients-draft")]
320 emulation_group: mls_group_create_config.emulation_group,
321 };
322
323 mls_group
324 .store(provider.storage())
325 .await
326 .map_err(NewGroupError::StorageError)?;
327 mls_group
328 .store_epoch_keypairs(provider.storage(), &[leaf_keypair])
329 .await
330 .map_err(NewGroupError::StorageError)?;
331
332 Ok(mls_group)
333 }
334
335 pub fn with_wire_format_policy(mut self, wire_format_policy: WireFormatPolicy) -> Self {
339 self.mls_group_create_config_builder = self
340 .mls_group_create_config_builder
341 .wire_format_policy(wire_format_policy);
342 self
343 }
344
345 pub fn padding_size(mut self, padding_size: usize) -> Self {
347 self.mls_group_create_config_builder = self
348 .mls_group_create_config_builder
349 .padding_size(padding_size);
350 self
351 }
352
353 pub fn max_past_epochs(mut self, max_past_epochs: usize) -> Self {
371 self.mls_group_create_config_builder = self
372 .mls_group_create_config_builder
373 .max_past_epochs(max_past_epochs);
374 self
375 }
376
377 pub fn set_past_epoch_deletion_policy(mut self, policy: PastEpochDeletionPolicy) -> Self {
391 self.mls_group_create_config_builder = self
392 .mls_group_create_config_builder
393 .set_past_epoch_deletion_policy(policy);
394 self
395 }
396
397 #[cfg(feature = "virtual-clients-draft")]
400 pub fn set_vc_derivation_epoch_retention_policy(
401 mut self,
402 policy: crate::group::VcDerivationEpochRetentionPolicy,
403 ) -> Self {
404 self.mls_group_create_config_builder = self
405 .mls_group_create_config_builder
406 .set_vc_derivation_epoch_retention_policy(policy);
407 self
408 }
409
410 pub fn number_of_resumption_psks(mut self, number_of_resumption_psks: usize) -> Self {
412 self.mls_group_create_config_builder = self
413 .mls_group_create_config_builder
414 .number_of_resumption_psks(number_of_resumption_psks);
415 self
416 }
417
418 pub fn use_ratchet_tree_extension(mut self, use_ratchet_tree_extension: bool) -> Self {
420 self.mls_group_create_config_builder = self
421 .mls_group_create_config_builder
422 .use_ratchet_tree_extension(use_ratchet_tree_extension);
423 self
424 }
425
426 pub fn sender_ratchet_configuration(
429 mut self,
430 sender_ratchet_configuration: SenderRatchetConfiguration,
431 ) -> Self {
432 self.mls_group_create_config_builder = self
433 .mls_group_create_config_builder
434 .sender_ratchet_configuration(sender_ratchet_configuration);
435 self
436 }
437
438 pub fn lifetime(mut self, lifetime: Lifetime) -> Self {
440 self.mls_group_create_config_builder =
441 self.mls_group_create_config_builder.lifetime(lifetime);
442 self
443 }
444
445 pub fn ciphersuite(mut self, ciphersuite: Ciphersuite) -> Self {
447 self.mls_group_create_config_builder = self
448 .mls_group_create_config_builder
449 .ciphersuite(ciphersuite);
450 self
451 }
452
453 pub fn with_group_context_extensions(mut self, extensions: Extensions<GroupContext>) -> Self {
455 self.mls_group_create_config_builder = self
456 .mls_group_create_config_builder
457 .with_group_context_extensions(extensions);
458 self
459 }
460
461 pub fn with_leaf_node_extensions(
463 mut self,
464 extensions: Extensions<LeafNode>,
465 ) -> Result<Self, LeafNodeValidationError> {
466 self.mls_group_create_config_builder = self
467 .mls_group_create_config_builder
468 .with_leaf_node_extensions(extensions)?;
469 Ok(self)
470 }
471
472 pub fn with_capabilities(mut self, capabilities: Capabilities) -> Self {
477 self.mls_group_create_config_builder = self
478 .mls_group_create_config_builder
479 .capabilities(capabilities);
480 self
481 }
482
483 pub fn with_capabilities_policy(mut self, policy: CapabilitiesPolicy) -> Self {
488 self.mls_group_create_config_builder = self
489 .mls_group_create_config_builder
490 .capabilities_policy(policy);
491 self
492 }
493}
494
495pub struct BranchGroupBuilder {
502 group_builder: MlsGroupBuilder,
503 branch_info: BranchInfo,
504 extensions: Option<Extensions<GroupContext>>,
505 force_self_update: bool,
506}
507
508impl BranchGroupBuilder {
509 pub fn propose_group_context_extensions(
513 mut self,
514 extensions: Extensions<GroupContext>,
515 ) -> Self {
516 self.extensions = Some(extensions);
517 self
518 }
519
520 pub fn force_self_update(mut self, force_self_update: bool) -> Self {
523 self.force_self_update = force_self_update;
524 self
525 }
526
527 #[openmls_traits::maybe_async]
540 pub async fn build_branch<Provider: OpenMlsProvider>(
541 self,
542 provider: &Provider,
543 signer: &impl Signer,
544 credential_with_key: CredentialWithKey,
545 new_members: Vec<KeyPackage>,
546 ) -> Result<(MlsGroup, CommitMessageBundle), BranchError<Provider::StorageError>> {
547 let group_builder = self
549 .group_builder
550 .ciphersuite(self.branch_info.ciphersuite());
551 let mut group = group_builder
552 .build(provider, signer, credential_with_key)
553 .await?;
554
555 let mut builder = group
556 .commit_builder()
557 .branch(provider.rand(), &self.branch_info)?
558 .propose_adds(new_members);
559 if let Some(extensions) = self.extensions {
560 builder = builder.propose_group_context_extensions(extensions)?;
561 }
562 if self.force_self_update {
563 builder = builder.force_self_update(true);
564 }
565 let bundle = builder
566 .load_psks(provider.storage())
567 .await?
568 .build(provider.rand(), provider.crypto(), signer, |_| true)?
569 .stage_commit(provider)
570 .await?;
571
572 Ok((group, bundle))
573 }
574}
575
576#[derive(Debug, thiserror::Error)]
579pub enum BranchError<StorageError> {
580 #[error(transparent)]
582 NewGroup(#[from] NewGroupError<StorageError>),
583 #[error(transparent)]
585 CreateCommit(#[from] CreateCommitError),
586 #[error(transparent)]
588 CommitBuilderStage(#[from] CommitBuilderStageError<StorageError>),
589}
590
591pub struct ReInitGroupBuilder {
601 group_builder: MlsGroupBuilder,
602 reinit_info: ReInitInfo,
603 force_self_update: bool,
604}
605
606impl ReInitGroupBuilder {
607 pub fn force_self_update(mut self, force_self_update: bool) -> Self {
610 self.force_self_update = force_self_update;
611 self
612 }
613
614 #[openmls_traits::maybe_async]
629 pub async fn build_reinit<Provider: OpenMlsProvider>(
630 self,
631 provider: &Provider,
632 signer: &impl Signer,
633 credential_with_key: CredentialWithKey,
634 members: impl IntoIterator<Item = KeyPackage>,
635 ) -> Result<(MlsGroup, CommitMessageBundle), ReInitError<Provider::StorageError>> {
636 let ReInitInfo {
638 proposal,
639 old_group_id,
640 old_group_epoch,
641 resumption_psk_secret,
642 member_credentials: _,
643 } = self.reinit_info;
644 let group_builder = self
645 .group_builder
646 .ciphersuite(proposal.ciphersuite)
647 .with_group_id(proposal.group_id)
648 .with_group_context_extensions(proposal.extensions);
649 let mut group = group_builder
650 .build(provider, signer, credential_with_key)
651 .await?;
652
653 let mut commit_builder = group
654 .commit_builder()
655 .reinit(
656 provider.rand(),
657 old_group_id,
658 old_group_epoch,
659 resumption_psk_secret,
660 )?
661 .propose_adds(members);
662 if self.force_self_update {
663 commit_builder = commit_builder.force_self_update(true);
664 }
665 let bundle = commit_builder
666 .load_psks(provider.storage())
667 .await?
668 .build(provider.rand(), provider.crypto(), signer, |_| true)?
669 .stage_commit(provider)
670 .await?;
671
672 Ok((group, bundle))
673 }
674}
675
676#[derive(Debug, thiserror::Error)]
679pub enum ReInitError<StorageError> {
680 #[error(transparent)]
682 NewGroup(#[from] NewGroupError<StorageError>),
683 #[error(transparent)]
685 CreateCommit(#[from] CreateCommitError),
686 #[error(transparent)]
688 CommitBuilderStage(#[from] CommitBuilderStageError<StorageError>),
689}
690
691#[cfg(feature = "virtual-clients-draft")]
706#[allow(clippy::too_many_arguments)]
707#[openmls_traits::maybe_async]
708async fn build_vc_internal<Provider: OpenMlsProvider>(
709 provider: &Provider,
710 signer: &impl Signer,
711 credential_with_key: CredentialWithKey,
712 mls_group_create_config: MlsGroupCreateConfig,
713 group_id: GroupId,
714 replace_old_group: bool,
715 epoch_id: crate::components::vc_derivation_info::EpochId,
716) -> Result<MlsGroup, NewGroupError<Provider::StorageError>> {
717 use openmls_traits::storage::StorageProvider as _;
718
719 use crate::{
720 components::vc_derivation_info::{
721 load_vc_epoch_state_and_tree, DerivationInfo, DerivationInfoTbe,
722 VirtualClientOperationType, VirtualClientsError,
723 },
724 schedule::EpochSecrets,
725 treesync::TreeSync,
726 };
727
728 if !replace_old_group
729 && MlsGroup::load(provider.storage(), &group_id)
730 .await
731 .map_err(NewGroupError::StorageError)?
732 .is_some()
733 {
734 return Err(NewGroupError::GroupAlreadyExists);
735 }
736
737 let ciphersuite = mls_group_create_config.ciphersuite;
738 let (capabilities, capabilities_policy) = resolve_capabilities(
739 mls_group_create_config.capabilities.clone(),
740 mls_group_create_config.capabilities_policy,
741 );
742
743 let resolved_dictionary = crate::components::vc_derivation_info::resolve_vc_leaf_dictionary(
747 Some(&capabilities),
748 Some(&mls_group_create_config.leaf_node_extensions),
749 None,
750 )?;
751
752 let (state, mut operation_tree) = load_vc_epoch_state_and_tree(provider, &epoch_id).await?;
757 let (emulation_leaf_index, epoch_encryption_key, emulation_ciphersuite) = state.into_parts();
758 let (generation, operation_secret) = operation_tree.next_operation_secret(
759 provider.crypto(),
760 emulation_ciphersuite,
761 &epoch_id,
762 emulation_leaf_index,
763 VirtualClientOperationType::KeyPackage,
764 b"",
765 )?;
766 provider
767 .storage()
768 .write_vc_operation_tree(&epoch_id, &operation_tree)
769 .await
770 .map_err(NewGroupError::StorageError)?;
771
772 let key_package_index = 0;
779 let key_package_seed = operation_secret.derive_key_package_seed_secret(
780 provider.crypto(),
781 ciphersuite,
782 key_package_index,
783 )?;
784 let leaf_encryption_keypair = key_package_seed
785 .derive_encryption_key_secret(provider.crypto(), ciphersuite)?
786 .generate_encryption_key_pair(provider.crypto(), ciphersuite)?;
787 let epoch_secret =
788 key_package_seed.derive_group_creation_secret(provider.crypto(), ciphersuite)?;
789
790 let leaf_encryption_key = leaf_encryption_keypair
793 .public_key()
794 .tls_serialize_detached()
795 .map_err(VirtualClientsError::from)?;
796 let tbe = DerivationInfoTbe::KeyPackage {
797 leaf_index: emulation_leaf_index,
798 generation,
799 key_package_index,
800 };
801 let derivation_info = DerivationInfo::encrypt(
802 provider.crypto(),
803 emulation_ciphersuite,
804 &epoch_encryption_key,
805 epoch_id.clone(),
806 &leaf_encryption_key,
807 &tbe,
808 )?;
809 let derivation_info_bytes = derivation_info
810 .tls_serialize_detached()
811 .map_err(VirtualClientsError::from)?;
812 let leaf_extensions = crate::components::vc_derivation_info::merge_vc_derivation_info(
813 Some(&mls_group_create_config.leaf_node_extensions),
814 resolved_dictionary,
815 derivation_info_bytes,
816 )?;
817
818 let (treesync, leaf_keypair) = TreeSync::new_vc(
820 provider,
821 signer,
822 ciphersuite,
823 credential_with_key,
824 *mls_group_create_config.lifetime(),
825 capabilities,
826 leaf_extensions,
827 leaf_encryption_keypair,
828 LeafNodeConstraints::from_group_context_extensions(
829 &mls_group_create_config.group_context_extensions,
830 ),
831 capabilities_policy,
832 )?;
833 let group_context = GroupContext::create_initial_group_context(
834 ciphersuite,
835 group_id.clone(),
836 treesync.tree_hash().to_vec(),
837 mls_group_create_config.group_context_extensions.clone(),
838 );
839 let serialized_group_context = group_context
840 .tls_serialize_detached()
841 .map_err(LibraryError::missing_bound_check)?;
842
843 let epoch_secrets =
845 EpochSecrets::from_epoch_secret(provider.crypto(), ciphersuite, epoch_secret)
846 .map_err(LibraryError::unexpected_crypto_error)?;
847 let (group_epoch_secrets, message_secrets) = epoch_secrets.split_secrets(
848 serialized_group_context,
849 TreeSize::new(1),
850 LeafNodeIndex::new(0),
851 );
852 let initial_confirmation_tag = message_secrets
853 .confirmation_key()
854 .tag(provider.crypto(), ciphersuite, &[])
855 .map_err(LibraryError::unexpected_crypto_error)?;
856 let message_secrets_store = MessageSecretsStore::new_with_secret(
857 mls_group_create_config
858 .join_config
859 .past_epoch_deletion_policy(),
860 message_secrets,
861 );
862 let public_group = PublicGroup::new(
863 provider.crypto(),
864 treesync,
865 group_context,
866 initial_confirmation_tag,
867 )?;
868
869 let mut resumption_psk_store = ResumptionPskStore::new(32);
870 let resumption_psk = group_epoch_secrets.resumption_psk();
871 resumption_psk_store.add(public_group.group_context().epoch(), resumption_psk.clone());
872
873 let mls_group = MlsGroup {
874 mls_group_config: mls_group_create_config.join_config.clone(),
875 own_leaf_nodes: vec![],
876 aad: vec![],
877 safe_aad: crate::framing::SafeAad::empty(),
878 group_state: MlsGroupState::Operational,
879 public_group,
880 group_epoch_secrets,
881 own_leaf_index: LeafNodeIndex::new(0),
882 message_secrets_store,
883 resumption_psk_store,
884 application_export_tree: None,
887 emulation_group: false,
889 };
890
891 let max_entries = mls_group.message_secrets_store.max_epochs.saturating_add(1);
897 crate::components::vc_derivation_info::write_vc_emulation_binding_with_pruning(
898 provider.storage(),
899 &group_id,
900 mls_group.epoch(),
901 epoch_id,
902 max_entries,
903 )
904 .await
905 .map_err(NewGroupError::StorageError)?;
906
907 mls_group
908 .store(provider.storage())
909 .await
910 .map_err(NewGroupError::StorageError)?;
911 mls_group
912 .store_epoch_keypairs(provider.storage(), &[leaf_keypair])
913 .await
914 .map_err(NewGroupError::StorageError)?;
915
916 Ok(mls_group)
917}