First, import the necessary modules and set up the ciphersuite and crypto providers:
use openmls::prelude::{*, tls_codec::*};use openmls_rust_crypto::OpenMlsRustCrypto;use openmls_basic_credential::SignatureKeyPair;// Define the ciphersuite (using the mandatory-to-implement suite)let ciphersuite = Ciphersuite::MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519;// Create providers for both partieslet sasha_provider = &OpenMlsRustCrypto::default();let maxim_provider = &OpenMlsRustCrypto::default();
In this example, we use separate provider instances for each participant to simulate two independent clients. In a real application, each client would run in its own process or on different devices.
fn generate_credential_with_key( identity: Vec<u8>, credential_type: CredentialType, signature_algorithm: SignatureScheme, provider: &impl OpenMlsProvider,) -> (CredentialWithKey, SignatureKeyPair) { let credential = BasicCredential::new(identity); let signature_keys = SignatureKeyPair::new(signature_algorithm) .expect("Error generating a signature key pair."); // Store the signature key into the key store so OpenMLS has access to it signature_keys .store(provider.storage()) .expect("Error storing signature keys in key store."); ( CredentialWithKey { credential: credential.into(), signature_key: signature_keys.public().into(), }, signature_keys, )}
Credentials identify participants in an MLS group. OpenMLS uses BasicCredential, which contains an identity as a byte vector.Key packages contain the public key material needed to add a client to a group asynchronously. They include:
A public HPKE encryption key
The client’s credential and signature
Information about supported features and extensions
In a real application, Maxim would upload their key package to a server, and Sasha would retrieve it from there. This enables asynchronous group creation.
Maxim deserializes the Welcome message and joins the group:
// Maxim deserializes the messagelet mls_message_in = MlsMessageIn::tls_deserialize(&mut serialized_welcome.as_slice()) .expect("An unexpected error occurred.");// Extract the Welcome messagelet welcome = match mls_message_in.extract() { MlsMessageBodyIn::Welcome(welcome) => welcome, _ => unreachable!("Unexpected message type."),};// Create a staged join to inspect the Welcomelet maxim_staged_join = StagedWelcome::new_from_welcome( maxim_provider, &MlsGroupJoinConfig::default(), welcome, // The public tree is transferred out of band Some(sasha_group.export_ratchet_tree().into()),).expect("Error creating a staged join from Welcome");// Finally, Maxim creates the grouplet mut maxim_group = maxim_staged_join .into_group(maxim_provider) .expect("Error creating the group from the staged join");
Why stage the Welcome?
Staging allows you to inspect the Welcome message before committing to join the group. This enables validation of:
Group membership
Group parameters and policies
Extensions and capabilities
Once validated, you can commit by calling into_group().