Join a group from a Welcome message

To join a group from a Welcome message, a new MlsGroup can be instantiated from the MlsMessageIn message containing the Welcome and an MlsGroupJoinConfig (see Group configuration for more details). This is a two-step process: a StagedWelcome is constructed from the Welcome and can then be turned into an MlsGroup. If the group configuration does not use the ratchet tree extension, the ratchet tree needs to be provided.

    let staged_join = StagedWelcome::new_from_welcome(provider, &mls_group_config, welcome, None)
        .expect("Error constructing staged join");
    let mut bob_group = staged_join
        .into_group(provider)
        .expect("Error joining group from StagedWelcome");

The reason for this two-phase process is to allow the recipient of a Welcome to inspect the message, e.g. to determine the identity of the sender.

Pay attention not to forward a Welcome message to a client before its associated commit has been accepted by the Delivery Service. Otherwise, you would end up with an invalid MLS group instance.

Examining a welcome message

When a client is invited to join a group, the application can allow the client to decide whether or not to join the group. In order to determine whether to join the group, the application can inspect information provided in the welcome message, such as who invited them, who else is in the group, what extensions are available, and more. If the application decides not to join the group, the welcome must be discarded to ensure that the local state is cleaned up.

After receiving a MlsMessageIn from the delivery service, the first step is to extract the MlsMessageBodyIn, and determine whether it is a welcome message.

    let welcome = match welcome.extract() {
        MlsMessageBodyIn::Welcome(welcome) => welcome,
        _ => unimplemented!("Handle other message types"),
    };

The next step is to process the Welcome. This removes the consumed KeyPackage from the StorageProvider, unless it is a last resort KeyPackage.

    let join_config = MlsGroupJoinConfig::default();
    // This deletes the keys used to decrypt the welcome, except if it is a last resort key
    // package.
    let processed_welcome = ProcessedWelcome::new_from_welcome(bob_provider, &join_config, welcome)
        .expect("Error constructing processed welcome");

At this stage, there are some more pieces of information in the ProcessedWelcome that could be useful to the application. For example, it can be useful to check which extensions are available. However, the pieces of information that are retrieved from the ProcessedWelcome are unverified, and verified values are only available from the StagedWelcome that is produced in the next step.

    // unverified pre-shared keys (`&[PreSharedKeyId]`)
    let _unverified_psks = processed_welcome.psks();

    // unverified group info (`VerifiableGroupInfo`)
    let unverified_group_info = processed_welcome.unverified_group_info();

    // From the unverified group info, the ciphersuite, group_id, and other information
    // can be retrieved.
    let _ciphersuite = unverified_group_info.ciphersuite();
    let _group_id = unverified_group_info.group_id();
    let _epoch = unverified_group_info.epoch();

    // Can also retrieve any available extensions
    let extensions = unverified_group_info.extensions();

    // Retrieving the ratchet tree extension
    let ratchet_tree_extension = extensions
        .ratchet_tree()
        .expect("No ratchet tree extension");
    // The (unverified) ratchet tree itself can also be inspected
    let _ratchet_tree = ratchet_tree_extension.ratchet_tree();

The next step is to stage the ProcessedWelcome.

    let staged_welcome: StagedWelcome = processed_welcome
        .into_staged_welcome(bob_provider, None)
        .expect("Error constructing staged welcome");

Then, more information about the welcome message's sender, such as the credential, signature public key, and encryption public key can also be individually inspected. The welcome message sender's credential can be validated at this stage.

    let welcome_sender: &LeafNode = staged_welcome
        .welcome_sender()
        .expect("Welcome sender could not be retrieved");

    // Inspect sender's credential...
    let _credential = welcome_sender.credential();
    // Inspect sender's signature public key...
    let _signature_key = welcome_sender.signature_key();

Additionally, some information about the other group members is made available, e.g. credentials and signature public keys for credential validation.

    // Inspect the group members
    for member in staged_welcome.members() {
        // leaf node index
        let _leaf_node_index = member.index;
        // credential
        let _credential = member.credential;
        // encryption public key
        let _encryption_key = member.encryption_key;
        // signature public key
        let _signature_key = member.signature_key;
    }

Lastly, the GroupContext contains several other useful pieces of information, including the protocol version, the extensions enabled on the group, and the required extension, proposal, and credential types.

    // Inspect group context...
    let group_context = staged_welcome.group_context();

    // inspect protocol version...
    let _protocol_version = group_context.protocol_version();
    // Inspect ciphersuite...
    let _ciphersuite = group_context.ciphersuite();
    // Inspect extensions...
    let extensions: &Extensions = group_context.extensions();

    // Can check which extensions are enabled
    let _has_ratchet_extension = extensions.ratchet_tree().is_some();

    // Inspect required capabilities...
    if let Some(capabilities) = group_context.required_capabilities() {
        // Inspect required extension types...
        let _extension_types: &[ExtensionType] = capabilities.extension_types();
        // Inspect required proposal types...
        let _proposal_types: &[ProposalType] = capabilities.proposal_types();
        // Inspect required credential types...
        let _credential_types: &[CredentialType] = capabilities.credential_types();
    }
    // Additional information from the `GroupContext`
    let _group_id = group_context.group_id();
    let _epoch = group_context.epoch();
    let _tree_hash = group_context.tree_hash();
    let _confirmed_transcript_hash = group_context.confirmed_transcript_hash();