Skip to main content

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.

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.

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
1
Set up dependencies
2
Add OpenMLS and the Rust crypto provider to your Cargo.toml:
3
[dependencies]
openmls = "1.0"
openmls_rust_crypto = "0.5"
openmls_basic_credential = "0.5"
4
Define the ciphersuite and providers
5
First, choose a ciphersuite and create providers for both parties:
6
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();
7
Create helper functions
8
Define helper functions to generate credentials and key packages:
9
// 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()
}
10
Generate credentials for both parties
11
Create credentials for Sasha and Maxim:
12
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,
);
13
Generate key packages
14
Generate a key package for Maxim to facilitate the asynchronous handshake:
15
let maxim_key_package = generate_key_package(
    ciphersuite,
    maxim_provider,
    &maxim_signer,
    maxim_credential_with_key
);
16
Create a group and add member
17
Sasha creates a new group and invites Maxim:
18
// 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");
19
Process the Welcome message
20
Maxim receives and processes the Welcome message to join the group:
21
// 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");
22
Send encrypted messages
23
Now both parties can exchange encrypted messages:
24
// 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

use openmls::prelude::{*, tls_codec::*};
use openmls_rust_crypto::OpenMlsRustCrypto;
use openmls_basic_credential::SignatureKeyPair;

fn main() {
    // Define ciphersuite
    let ciphersuite = Ciphersuite::MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519;
    let sasha_provider = &OpenMlsRustCrypto::default();
    let maxim_provider = &OpenMlsRustCrypto::default();

    // Create credentials
    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,
    );

    // Generate key package for Maxim
    let maxim_key_package = generate_key_package(
        ciphersuite,
        maxim_provider,
        &maxim_signer,
        maxim_credential_with_key,
    );

    // Sasha creates a group and adds Maxim
    let mut sasha_group = MlsGroup::new(
        sasha_provider,
        &sasha_signer,
        &MlsGroupCreateConfig::default(),
        sasha_credential_with_key,
    )
    .expect("An unexpected error occurred.");

    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_group
        .merge_pending_commit(sasha_provider)
        .expect("error merging pending commit");

    // Maxim joins the group
    let serialized_welcome = welcome_out
        .tls_serialize_detached()
        .expect("Error serializing welcome");

    let mls_message_in = MlsMessageIn::tls_deserialize(&mut serialized_welcome.as_slice())
        .expect("An unexpected error occurred.");

    let welcome = match mls_message_in.extract() {
        MlsMessageBodyIn::Welcome(welcome) => welcome,
        _ => unreachable!("Unexpected message type."),
    };

    let maxim_staged_join = StagedWelcome::new_from_welcome(
        maxim_provider,
        &MlsGroupJoinConfig::default(),
        welcome,
        Some(sasha_group.export_ratchet_tree().into()),
    )
    .expect("Error creating a staged join from Welcome");

    let mut maxim_group = maxim_staged_join
        .into_group(maxim_provider)
        .expect("Error creating the group from the staged join");

    // Exchange messages
    let message = maxim_group
        .create_message(maxim_provider, &maxim_signer, b"Hello Sasha!")
        .expect("Error creating application message");

    println!("Two-party chat established successfully!");
}

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.");
    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,
    )
}

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()
}

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