Skip to main content

openmls/group/mls_group/
creation.rs

1use errors::NewGroupError;
2use openmls_traits::storage::StorageProvider as StorageProviderTrait;
3
4use super::{builder::MlsGroupBuilder, *};
5use crate::{
6    credentials::CredentialWithKey,
7    extensions::Extensions,
8    group::{
9        commit_builder::external_commits::ExternalCommitBuilder,
10        errors::{ExternalCommitError, WelcomeError},
11    },
12    messages::{
13        group_info::{GroupInfo, VerifiableGroupInfo},
14        Welcome,
15    },
16    schedule::{
17        psk::{store::ResumptionPskStore, PreSharedKeyId},
18        EpochSecretsResult,
19    },
20    storage::OpenMlsProvider,
21    treesync::{
22        errors::{DerivePathError, PublicTreeError},
23        node::leaf_node::{Capabilities, LeafNodeParameters},
24        RatchetTreeIn,
25    },
26};
27
28#[cfg(doc)]
29use crate::key_packages::KeyPackage;
30
31impl MlsGroup {
32    // === Group creation ===
33
34    /// Creates a builder which can be used to configure and build
35    /// a new [`MlsGroup`].
36    pub fn builder() -> MlsGroupBuilder {
37        MlsGroupBuilder::new()
38    }
39
40    /// Creates a new group with the creator as the only member (and a random
41    /// group ID).
42    ///
43    /// This function removes the private key corresponding to the
44    /// `key_package` from the key store.
45    pub fn new<Provider: OpenMlsProvider>(
46        provider: &Provider,
47        signer: &impl Signer,
48        mls_group_create_config: &MlsGroupCreateConfig,
49        credential_with_key: CredentialWithKey,
50    ) -> Result<Self, NewGroupError<Provider::StorageError>> {
51        MlsGroupBuilder::new().build_internal(
52            provider,
53            signer,
54            credential_with_key,
55            Some(mls_group_create_config.clone()),
56        )
57    }
58
59    /// Creates a new group with a given group ID with the creator as the only
60    /// member.
61    pub fn new_with_group_id<Provider: OpenMlsProvider>(
62        provider: &Provider,
63        signer: &impl Signer,
64        mls_group_create_config: &MlsGroupCreateConfig,
65        group_id: GroupId,
66        credential_with_key: CredentialWithKey,
67    ) -> Result<Self, NewGroupError<Provider::StorageError>> {
68        MlsGroupBuilder::new()
69            .with_group_id(group_id)
70            .build_internal(
71                provider,
72                signer,
73                credential_with_key,
74                Some(mls_group_create_config.clone()),
75            )
76    }
77
78    /// Join an existing group through an External Commit.
79    /// The resulting [`MlsGroup`] instance starts off with a pending
80    /// commit (the external commit, which adds this client to the group).
81    /// Merging this commit is necessary for this [`MlsGroup`] instance to
82    /// function properly, as, for example, this client is not yet part of the
83    /// tree. As a result, it is not possible to clear the pending commit. If
84    /// the external commit was rejected due to an epoch change, the
85    /// [`MlsGroup`] instance has to be discarded and a new one has to be
86    /// created using this function based on the latest `ratchet_tree` and
87    /// group info. For more information on the external init process,
88    /// please see Section 11.2.1 in the MLS specification.
89    ///
90    /// Note: If there is a group member in the group with the same identity as
91    /// us, this will create a remove proposal.
92    #[allow(clippy::too_many_arguments)]
93    #[deprecated(
94        since = "0.7.1",
95        note = "Use the `MlsGroup::external_commit_builder` instead."
96    )]
97    pub fn join_by_external_commit<Provider: OpenMlsProvider>(
98        provider: &Provider,
99        signer: &impl Signer,
100        ratchet_tree: Option<RatchetTreeIn>,
101        verifiable_group_info: VerifiableGroupInfo,
102        mls_group_config: &MlsGroupJoinConfig,
103        capabilities: Option<Capabilities>,
104        extensions: Option<Extensions<LeafNode>>,
105        aad: &[u8],
106        credential_with_key: CredentialWithKey,
107    ) -> Result<(Self, MlsMessageOut, Option<GroupInfo>), ExternalCommitError<Provider::StorageError>>
108    {
109        let leaf_node_parameters = LeafNodeParameters::builder()
110            .with_capabilities(capabilities.unwrap_or_default())
111            .with_extensions(extensions.unwrap_or_default())
112            .build();
113
114        let mut external_commit_builder = ExternalCommitBuilder::new()
115            .with_aad(aad.to_vec())
116            .with_config(mls_group_config.clone());
117
118        if let Some(ratchet_tree) = ratchet_tree {
119            external_commit_builder = external_commit_builder.with_ratchet_tree(ratchet_tree)
120        }
121
122        let (mls_group, commit_message_bundle) = external_commit_builder
123            .build_group(provider, verifiable_group_info, credential_with_key)?
124            .leaf_node_parameters(leaf_node_parameters)
125            .load_psks(provider.storage())
126            .map_err(|e| {
127                log::error!("Error loading PSKs for external commit: {e:?}");
128                LibraryError::custom("Error loading PSKs for external commit")
129            })?
130            .build(provider.rand(), provider.crypto(), signer, |_| true)?
131            .finalize(provider)?;
132
133        let (commit, _, group_info) = commit_message_bundle.into_contents();
134
135        Ok((mls_group, commit, group_info))
136    }
137}
138
139impl ProcessedWelcome {
140    /// Creates a new processed [`Welcome`] message , which can be
141    /// inspected before creating a [`StagedWelcome`].
142    ///
143    /// This does not require a ratchet tree yet.
144    ///
145    /// [`Welcome`]: crate::messages::Welcome
146    pub fn new_from_welcome<Provider: OpenMlsProvider>(
147        provider: &Provider,
148        mls_group_config: &MlsGroupJoinConfig,
149        welcome: Welcome,
150    ) -> Result<Self, WelcomeError<Provider::StorageError>> {
151        let (resumption_psk_store, key_package_bundle) =
152            keys_for_welcome(mls_group_config, &welcome, provider)?;
153
154        let ciphersuite = welcome.ciphersuite();
155        let Some(egs) = welcome.find_encrypted_group_secret(
156            key_package_bundle
157                .key_package()
158                .hash_ref(provider.crypto())?,
159        ) else {
160            return Err(WelcomeError::JoinerSecretNotFound);
161        };
162
163        // This check seems to be superfluous from the perspective of the RFC, but still doesn't
164        // seem like a bad idea.
165        if welcome.ciphersuite() != key_package_bundle.key_package().ciphersuite() {
166            let e = WelcomeError::CiphersuiteMismatch;
167            log::debug!("new_from_welcome {e:?}");
168            return Err(e);
169        }
170
171        let group_secrets = GroupSecrets::try_from_ciphertext(
172            key_package_bundle.init_private_key(),
173            egs.encrypted_group_secrets(),
174            welcome.encrypted_group_info(),
175            ciphersuite,
176            provider.crypto(),
177        )?;
178
179        // Validate PSKs
180        PreSharedKeyId::validate_in_welcome(&group_secrets.psks, ciphersuite)?;
181
182        let psk_secret = {
183            let psks = load_psks(
184                provider.storage(),
185                &resumption_psk_store,
186                &group_secrets.psks,
187            )?;
188
189            PskSecret::new(provider.crypto(), ciphersuite, psks)?
190        };
191        let key_schedule = KeySchedule::init(
192            ciphersuite,
193            provider.crypto(),
194            &group_secrets.joiner_secret,
195            psk_secret,
196        )?;
197        let (welcome_key, welcome_nonce) = key_schedule
198            .welcome(provider.crypto(), ciphersuite)
199            .map_err(|_| LibraryError::custom("Using the key schedule in the wrong state"))?
200            .derive_welcome_key_nonce(provider.crypto(), ciphersuite)
201            .map_err(LibraryError::unexpected_crypto_error)?;
202        let verifiable_group_info = VerifiableGroupInfo::try_from_ciphertext(
203            &welcome_key,
204            &welcome_nonce,
205            welcome.encrypted_group_info(),
206            &[],
207            provider.crypto(),
208        )?;
209
210        if let Some(required_capabilities) =
211            verifiable_group_info.extensions().required_capabilities()
212        {
213            // Also check that our key package actually supports the extensions.
214            // As per the spec, the sender must have checked this. But you never know.
215            key_package_bundle
216                .key_package()
217                .leaf_node()
218                .capabilities()
219                .supports_required_capabilities(required_capabilities)?;
220        }
221
222        // https://validation.openmls.tech/#valn1404
223        // Verify that the cipher_suite in the GroupInfo matches the cipher_suite in the
224        // KeyPackage.
225        if verifiable_group_info.ciphersuite() != key_package_bundle.key_package().ciphersuite() {
226            let e = WelcomeError::CiphersuiteMismatch;
227            log::debug!("new_from_welcome {e:?}");
228            return Err(e);
229        }
230
231        Ok(Self {
232            mls_group_config: mls_group_config.clone(),
233            ciphersuite,
234            group_secrets,
235            key_schedule,
236            verifiable_group_info,
237            resumption_psk_store,
238            key_package_bundle,
239        })
240    }
241
242    /// Get a reference to the GroupInfo in this Welcome message.
243    ///
244    /// **NOTE:** The group info contains **unverified** values. Use with caution.
245    pub fn unverified_group_info(&self) -> &VerifiableGroupInfo {
246        &self.verifiable_group_info
247    }
248
249    /// Get a reference to the PSKs in this Welcome message.
250    ///
251    /// **NOTE:** The group info contains **unverified** values. Use with caution.
252    pub fn psks(&self) -> &[PreSharedKeyId] {
253        &self.group_secrets.psks
254    }
255
256    /// Consume the `ProcessedWelcome` and combine it with the ratchet tree into
257    /// a `StagedWelcome`.
258    pub fn into_staged_welcome<Provider: OpenMlsProvider>(
259        self,
260        provider: &Provider,
261        ratchet_tree: Option<RatchetTreeIn>,
262    ) -> Result<StagedWelcome, WelcomeError<Provider::StorageError>> {
263        self.into_staged_welcome_inner(
264            provider,
265            ratchet_tree,
266            LeafNodeLifetimePolicy::Verify,
267            false,
268        )
269    }
270
271    /// Consume the `ProcessedWelcome` and combine it with the ratchet tree into
272    /// a `StagedWelcome`.
273    pub(crate) fn into_staged_welcome_inner<Provider: OpenMlsProvider>(
274        mut self,
275        provider: &Provider,
276        ratchet_tree: Option<RatchetTreeIn>,
277        validate_lifetimes: LeafNodeLifetimePolicy,
278        replace_old_group: bool,
279    ) -> Result<StagedWelcome, WelcomeError<Provider::StorageError>> {
280        // Check if we need to replace an old group
281        if !replace_old_group
282            && MlsGroup::load(provider.storage(), self.verifiable_group_info.group_id())
283                .map_err(WelcomeError::StorageError)?
284                .is_some()
285        {
286            return Err(WelcomeError::GroupAlreadyExists);
287        }
288
289        // Build the ratchet tree and group
290
291        // Set nodes either from the extension or from the `nodes_option`.
292        // If we got a ratchet tree extension in the welcome, we enable it for
293        // this group. Note that this is not strictly necessary. But there's
294        // currently no other mechanism to enable the extension.
295        let ratchet_tree = match self.verifiable_group_info.extensions().ratchet_tree() {
296            Some(extension) => extension.ratchet_tree().clone(),
297            None => match ratchet_tree {
298                Some(ratchet_tree) => ratchet_tree,
299                None => return Err(WelcomeError::MissingRatchetTree),
300            },
301        };
302
303        // Since there is currently only the external pub extension, there is no
304        // group info extension of interest here.
305        let (public_group, _group_info_extensions) = PublicGroup::from_ratchet_tree(
306            provider.crypto(),
307            ratchet_tree,
308            self.verifiable_group_info.clone(),
309            ProposalStore::new(),
310            validate_lifetimes,
311        )?;
312
313        // Check that the leaf node of the added key package supports all extensions in the group
314        // context.
315        // https://validation.openmls.tech/#valn1415
316        let added_leaf_supports_all_group_context_extensions = public_group
317            .group_context()
318            .extensions()
319            .iter()
320            .all(|extension| {
321                self.key_package_bundle
322                    .key_package
323                    .leaf_node()
324                    .supports_extension(&extension.extension_type())
325            });
326        if !added_leaf_supports_all_group_context_extensions {
327            return Err(WelcomeError::UnsupportedExtensions);
328        }
329
330        // Find our own leaf in the tree.
331        let own_leaf_index = public_group
332            .members()
333            .find_map(|m| {
334                if m.signature_key
335                    == self
336                        .key_package_bundle
337                        .key_package()
338                        .leaf_node()
339                        .signature_key()
340                        .as_slice()
341                {
342                    Some(m.index)
343                } else {
344                    None
345                }
346            })
347            .ok_or(WelcomeError::PublicTreeError(
348                PublicTreeError::MalformedTree,
349            ))?;
350
351        struct KeyScheduleResult {
352            group_epoch_secrets: GroupEpochSecrets,
353            message_secrets: MessageSecrets,
354            #[cfg(feature = "extensions-draft-08")]
355            application_exporter: ApplicationExportSecret,
356        }
357        let KeyScheduleResult {
358            group_epoch_secrets,
359            message_secrets,
360            #[cfg(feature = "extensions-draft-08")]
361                application_exporter: application_export_secret,
362        } = {
363            let serialized_group_context = public_group
364                .group_context()
365                .tls_serialize_detached()
366                .map_err(LibraryError::missing_bound_check)?;
367
368            // TODO #751: Implement PSK
369            self.key_schedule
370                .add_context(provider.crypto(), &serialized_group_context)
371                .map_err(|_| LibraryError::custom("Using the key schedule in the wrong state"))?;
372
373            let EpochSecretsResult {
374                epoch_secrets,
375                #[cfg(feature = "extensions-draft-08")]
376                application_exporter,
377            } = self
378                .key_schedule
379                .epoch_secrets(provider.crypto(), self.ciphersuite)
380                .map_err(|_| LibraryError::custom("Using the key schedule in the wrong state"))?;
381
382            let (group_epoch_secrets, message_secrets) = epoch_secrets.split_secrets(
383                serialized_group_context,
384                public_group.tree_size(),
385                own_leaf_index,
386            );
387
388            KeyScheduleResult {
389                group_epoch_secrets,
390                message_secrets,
391                #[cfg(feature = "extensions-draft-08")]
392                application_exporter,
393            }
394        };
395
396        let confirmation_tag = message_secrets
397            .confirmation_key()
398            .tag(
399                provider.crypto(),
400                self.ciphersuite,
401                public_group.group_context().confirmed_transcript_hash(),
402            )
403            .map_err(LibraryError::unexpected_crypto_error)?;
404
405        // Verify confirmation tag
406        // https://validation.openmls.tech/#valn1411
407        if &confirmation_tag != public_group.confirmation_tag() {
408            log::error!("Confirmation tag mismatch");
409            log_crypto!(trace, "  Got:      {:x?}", confirmation_tag);
410            log_crypto!(trace, "  Expected: {:x?}", public_group.confirmation_tag());
411            debug_assert!(false, "Confirmation tag mismatch");
412
413            // in some tests we need to be able to proceed despite the tag being wrong,
414            // e.g. to test whether a later validation check is performed correctly.
415            if !crate::skip_validation::is_disabled::confirmation_tag() {
416                return Err(WelcomeError::ConfirmationTagMismatch);
417            }
418        }
419
420        let message_secrets_store = MessageSecretsStore::new_with_secret(0, message_secrets);
421
422        // Extract and store the resumption PSK for the current epoch.
423        let resumption_psk = group_epoch_secrets.resumption_psk();
424        self.resumption_psk_store
425            .add(public_group.group_context().epoch(), resumption_psk.clone());
426
427        let welcome_sender_index = self.verifiable_group_info.signer();
428        let path_keypairs = if let Some(path_secret) = self.group_secrets.path_secret {
429            let (path_keypairs, _commit_secret) = public_group
430                .derive_path_secrets(
431                    provider.crypto(),
432                    self.ciphersuite,
433                    path_secret,
434                    welcome_sender_index,
435                    own_leaf_index,
436                )
437                .map_err(|e| match e {
438                    DerivePathError::LibraryError(e) => e.into(),
439                    DerivePathError::PublicKeyMismatch => {
440                        WelcomeError::PublicTreeError(PublicTreeError::PublicKeyMismatch)
441                    }
442                })?;
443            Some(path_keypairs)
444        } else {
445            None
446        };
447
448        let staged_welcome = StagedWelcome {
449            mls_group_config: self.mls_group_config,
450            public_group,
451            group_epoch_secrets,
452            own_leaf_index,
453            message_secrets_store,
454            #[cfg(feature = "extensions-draft-08")]
455            application_export_secret,
456            resumption_psk_store: self.resumption_psk_store,
457            verifiable_group_info: self.verifiable_group_info,
458            key_package_bundle: self.key_package_bundle,
459            path_keypairs,
460        };
461
462        Ok(staged_welcome)
463    }
464}
465
466impl StagedWelcome {
467    /// Creates a new staged welcome from a [`Welcome`] message. Returns an error
468    /// ([`WelcomeError::NoMatchingKeyPackage`]) if no [`KeyPackage`]
469    /// can be found.
470    /// Note: calling this function will consume the key material for decrypting the [`Welcome`]
471    /// message, even if the caller does not turn the [`StagedWelcome`] into an [`MlsGroup`].
472    ///
473    /// [`Welcome`]: crate::messages::Welcome
474    pub fn new_from_welcome<Provider: OpenMlsProvider>(
475        provider: &Provider,
476        mls_group_config: &MlsGroupJoinConfig,
477        welcome: Welcome,
478        ratchet_tree: Option<RatchetTreeIn>,
479    ) -> Result<Self, WelcomeError<Provider::StorageError>> {
480        let processed_welcome =
481            ProcessedWelcome::new_from_welcome(provider, mls_group_config, welcome)?;
482
483        processed_welcome.into_staged_welcome(provider, ratchet_tree)
484    }
485
486    /// Similar to [`StagedWelcome::new_from_welcome`] but as a builder.
487    ///
488    /// The builder allows to set the ratchet tree, skip leaf node lifetime
489    /// validation, and get the [`ProcessedWelcome`] for inspection before staging.
490    pub fn build_from_welcome<'a, Provider: OpenMlsProvider>(
491        provider: &'a Provider,
492        mls_group_config: &MlsGroupJoinConfig,
493        welcome: Welcome,
494        // ratchet_tree: Option<RatchetTreeIn>,
495    ) -> Result<JoinBuilder<'a, Provider>, WelcomeError<Provider::StorageError>> {
496        let processed_welcome =
497            ProcessedWelcome::new_from_welcome(provider, mls_group_config, welcome)?;
498
499        // processed_welcome.into_staged_welcome(provider, ratchet_tree)
500        Ok(JoinBuilder::new(provider, processed_welcome))
501    }
502
503    /// Returns the [`LeafNodeIndex`] of the group member that authored the [`Welcome`] message.
504    ///
505    /// [`Welcome`]: crate::messages::Welcome
506    pub fn welcome_sender_index(&self) -> LeafNodeIndex {
507        self.verifiable_group_info.signer()
508    }
509
510    /// Returns the [`LeafNode`] of the group member that authored the [`Welcome`] message.
511    ///
512    /// [`Welcome`]: crate::messages::Welcome
513    pub fn welcome_sender(&self) -> Result<&LeafNode, LibraryError> {
514        let sender_index = self.welcome_sender_index();
515        self.public_group
516            .leaf(sender_index)
517            .ok_or(LibraryError::custom(
518                "no leaf with given welcome sender index exists",
519            ))
520    }
521
522    /// Get the [`GroupContext`] of this welcome's [`PublicGroup`].
523    pub fn group_context(&self) -> &GroupContext {
524        self.public_group.group_context()
525    }
526
527    /// Get an iterator over all [`Member`]s of this welcome's [`PublicGroup`].
528    pub fn members(&self) -> impl Iterator<Item = Member> + '_ {
529        self.public_group.members()
530    }
531
532    /// Get the [`ApplicationExportSecret`] of this welcome.
533    #[cfg(feature = "extensions-draft-08")]
534    pub fn application_export_secret(&self) -> &ApplicationExportSecret {
535        &self.application_export_secret
536    }
537
538    /// Consumes the [`StagedWelcome`] and returns the respective [`MlsGroup`].
539    pub fn into_group<Provider: OpenMlsProvider>(
540        self,
541        provider: &Provider,
542    ) -> Result<MlsGroup, WelcomeError<Provider::StorageError>> {
543        // If we got a path secret, derive the path (which also checks if the
544        // public keys match) and store the derived keys in the key store.
545        let group_keypairs = if let Some(path_keypairs) = self.path_keypairs {
546            let mut keypairs = vec![self.key_package_bundle.encryption_key_pair()];
547            keypairs.extend_from_slice(&path_keypairs);
548            keypairs
549        } else {
550            vec![self.key_package_bundle.encryption_key_pair()]
551        };
552
553        #[cfg(feature = "extensions-draft-08")]
554        let application_export_tree = ApplicationExportTree::new(self.application_export_secret);
555
556        let mut mls_group = MlsGroup {
557            mls_group_config: self.mls_group_config,
558            own_leaf_nodes: vec![],
559            aad: vec![],
560            group_state: MlsGroupState::Operational,
561            public_group: self.public_group,
562            group_epoch_secrets: self.group_epoch_secrets,
563            own_leaf_index: self.own_leaf_index,
564            message_secrets_store: self.message_secrets_store,
565            resumption_psk_store: self.resumption_psk_store,
566            #[cfg(feature = "extensions-draft-08")]
567            application_export_tree: Some(application_export_tree),
568        };
569
570        mls_group
571            .store_epoch_keypairs(provider.storage(), group_keypairs.as_slice())
572            .map_err(WelcomeError::StorageError)?;
573        mls_group.set_max_past_epochs(mls_group.mls_group_config.max_past_epochs);
574
575        mls_group
576            .store(provider.storage())
577            .map_err(WelcomeError::StorageError)?;
578
579        Ok(mls_group)
580    }
581}
582
583fn keys_for_welcome<Provider: OpenMlsProvider>(
584    mls_group_config: &MlsGroupJoinConfig,
585    welcome: &Welcome,
586    provider: &Provider,
587) -> Result<
588    (ResumptionPskStore, KeyPackageBundle),
589    WelcomeError<<Provider as OpenMlsProvider>::StorageError>,
590> {
591    let resumption_psk_store = ResumptionPskStore::new(mls_group_config.number_of_resumption_psks);
592    let key_package_bundle: KeyPackageBundle = welcome
593        .secrets()
594        .iter()
595        .find_map(|egs| {
596            let hash_ref = egs.new_member();
597
598            provider
599                .storage()
600                .key_package(&hash_ref)
601                .map_err(WelcomeError::StorageError)
602                .transpose()
603        })
604        .ok_or(WelcomeError::NoMatchingKeyPackage)??;
605    if !key_package_bundle.key_package().last_resort() {
606        provider
607            .storage()
608            .delete_key_package(&key_package_bundle.key_package.hash_ref(provider.crypto())?)
609            .map_err(WelcomeError::StorageError)?;
610    } else {
611        log::debug!("Key package has last resort extension, not deleting");
612    }
613    Ok((resumption_psk_store, key_package_bundle))
614}
615
616/// Verify or skip the validation of leaf node lifetimes in the ratchet tree
617/// when joining a group.
618#[derive(Default, Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
619pub enum LeafNodeLifetimePolicy {
620    /// Verify the lifetime of leaf nodes in the ratchet tree.
621    ///
622    /// **NOTE:** Only leaf nodes that have never been updated have a lifetime.
623    #[default]
624    Verify,
625
626    /// Skip the verification of the lifeimte in leaf nodes in the ratchet tree.
627    Skip,
628}
629
630/// Builder for joining a group.
631///
632/// Create this with [`StagedWelcome::build_from_welcome`].
633pub struct JoinBuilder<'a, Provider: OpenMlsProvider> {
634    provider: &'a Provider,
635    processed_welcome: ProcessedWelcome,
636    ratchet_tree: Option<RatchetTreeIn>,
637    validate_lifetimes: LeafNodeLifetimePolicy,
638    replace_old_group: bool,
639}
640
641impl<'a, Provider: OpenMlsProvider> JoinBuilder<'a, Provider> {
642    /// Create a new builder for the [`JoinBuilder`].
643    pub fn new(provider: &'a Provider, processed_welcome: ProcessedWelcome) -> Self {
644        Self {
645            provider,
646            processed_welcome,
647            ratchet_tree: None,
648            replace_old_group: false,
649            validate_lifetimes: LeafNodeLifetimePolicy::Verify,
650        }
651    }
652
653    /// The ratchet tree to use for the new group.
654    pub fn with_ratchet_tree(mut self, ratchet_tree: RatchetTreeIn) -> Self {
655        self.ratchet_tree = Some(ratchet_tree);
656        self
657    }
658
659    /// Instruct the builder to replace any existing group with the same ID.
660    pub fn replace_old_group(mut self) -> Self {
661        self.replace_old_group = true;
662        self
663    }
664
665    /// Skip the validation of lifetimes in leaf nodes in the ratchet tree.
666    /// Note that only the leaf nodes are checked that were never updated.
667    ///
668    /// By default they are validated.
669    pub fn skip_lifetime_validation(mut self) -> Self {
670        self.validate_lifetimes = LeafNodeLifetimePolicy::Skip;
671        self
672    }
673
674    /// Get a reference to the [`ProcessedWelcome`].
675    ///
676    /// Use this to inspect the [`Welcome`] message before validation.
677    pub fn processed_welcome(&self) -> &ProcessedWelcome {
678        &self.processed_welcome
679    }
680
681    /// Build the [`StagedWelcome`].
682    pub fn build(self) -> Result<StagedWelcome, WelcomeError<Provider::StorageError>> {
683        self.processed_welcome.into_staged_welcome_inner(
684            self.provider,
685            self.ratchet_tree,
686            self.validate_lifetimes,
687            self.replace_old_group,
688        )
689    }
690}