This example demonstrates how to set up a secure two-party chat using OpenMLS. Two users will create credentials, exchange key packages, and establish an encrypted group conversation.Documentation Index
Fetch the complete documentation index at: https://mintlify.com/openmls/openmls/llms.txt
Use this file to discover all available pages before exploring further.
Overview
In this example, we’ll create:- Two participants (Sasha and Maxim)
- Credentials and key packages for each participant
- A group with both members
- Encrypted message exchange
use openmls::prelude::{*, tls_codec::*};
use openmls_rust_crypto::OpenMlsRustCrypto;
use openmls_basic_credential::SignatureKeyPair;
// Define ciphersuite
let ciphersuite = Ciphersuite::MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519;
// Create providers for both parties
let sasha_provider = &OpenMlsRustCrypto::default();
let maxim_provider = &OpenMlsRustCrypto::default();
// Helper to create and store credentials
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,
)
}
// Helper to create key package bundles
fn generate_key_package(
ciphersuite: Ciphersuite,
provider: &impl OpenMlsProvider,
signer: &SignatureKeyPair,
credential_with_key: CredentialWithKey,
) -> KeyPackageBundle {
KeyPackage::builder()
.build(
ciphersuite,
provider,
signer,
credential_with_key,
)
.unwrap()
}
let (sasha_credential_with_key, sasha_signer) = generate_credential_with_key(
"Sasha".into(),
CredentialType::Basic,
ciphersuite.signature_algorithm(),
sasha_provider,
);
let (maxim_credential_with_key, maxim_signer) = generate_credential_with_key(
"Maxim".into(),
CredentialType::Basic,
ciphersuite.signature_algorithm(),
maxim_provider,
);
let maxim_key_package = generate_key_package(
ciphersuite,
maxim_provider,
&maxim_signer,
maxim_credential_with_key
);
// Sasha creates a new group
let mut sasha_group = MlsGroup::new(
sasha_provider,
&sasha_signer,
&MlsGroupCreateConfig::default(),
sasha_credential_with_key,
)
.expect("An unexpected error occurred.");
// Sasha invites Maxim to the group
let (mls_message_out, welcome_out, group_info) = sasha_group
.add_members(
sasha_provider,
&sasha_signer,
core::slice::from_ref(maxim_key_package.key_package())
)
.expect("Could not add members.");
// Sasha merges the pending commit that adds Maxim
sasha_group
.merge_pending_commit(sasha_provider)
.expect("error merging pending commit");
// Sasha serializes the Welcome message
let serialized_welcome = welcome_out
.tls_serialize_detached()
.expect("Error serializing welcome");
// Maxim deserializes the message
let mls_message_in = MlsMessageIn::tls_deserialize(&mut serialized_welcome.as_slice())
.expect("An unexpected error occurred.");
// Extract the Welcome
let welcome = match mls_message_in.extract() {
MlsMessageBodyIn::Welcome(welcome) => welcome,
_ => unreachable!("Unexpected message type."),
};
// Maxim creates a staged join to inspect the welcome
let maxim_staged_join = StagedWelcome::new_from_welcome(
maxim_provider,
&MlsGroupJoinConfig::default(),
welcome,
// The public tree is needed and transferred out of band
Some(sasha_group.export_ratchet_tree().into()),
)
.expect("Error creating a staged join from Welcome");
// Maxim creates the group
let mut maxim_group = maxim_staged_join
.into_group(maxim_provider)
.expect("Error creating the group from the staged join");
// Maxim sends a message
let message_maxim = maxim_group
.create_message(maxim_provider, &maxim_signer, b"Hello Sasha!")
.expect("Error creating application message");
// Serialize the message for transmission
let serialized_message = message_maxim
.tls_serialize_detached()
.expect("Error serializing message");
// Sasha receives and processes the message
let protocol_message = MlsMessageIn::tls_deserialize(&mut serialized_message.as_slice())
.expect("Error deserializing message")
.try_into_protocol_message()
.expect("Expected protocol message");
let processed_message = sasha_group
.process_message(sasha_provider, protocol_message)
.expect("Error processing message");
if let ProcessedMessageContent::ApplicationMessage(app_message) =
processed_message.into_content()
{
let message_text = String::from_utf8(
app_message.into_bytes()
).unwrap();
println!("Sasha received: {}", message_text); // "Hello Sasha!"
}
Complete Example
Key Concepts
- Credentials: Identity information for participants
- Key Packages: Pre-generated cryptographic material for asynchronous joins
- Welcome Message: Invitation to join an existing group
- Ratchet Tree: The group’s public key structure (exported and shared out-of-band)
- Application Messages: Encrypted content messages
Next Steps
- Learn about group chat with multiple participants
- Explore server integration for message delivery
- Implement key rotation for forward secrecy