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