Skip to main content
MLS conversations (groups) are the core communication entities in Wire CoreCrypto. A conversation holds the group state — epoch, member list, pending proposals and commits — and provides operations for messaging and membership management. All mutating conversation operations go through ConversationGuard, obtained via TransactionContext::conversation.

Creating conversations

new_conversation

pub async fn new_conversation(
    &self,
    id: &ConversationIdRef,
    credential_ref: &CredentialRef,
    config: MlsConversationConfiguration,
) -> Result<()>
Creates a new empty MLS group locally. The creator is the sole initial member. Fails if a conversation (or pending conversation) with the same ID already exists.
id
&ConversationIdRef
required
Unique group identifier bytes. Wire typically uses a UUID encoded as bytes.
credential_ref
&CredentialRef
required
Reference to the credential the creator uses for this group. See Credentials.
config
MlsConversationConfiguration
required
Group configuration. See MlsConversationConfiguration below.
Example
let id: Vec<u8> = uuid::Uuid::new_v4().as_bytes().to_vec();
tx.new_conversation(&id, &credential_ref, MlsConversationConfiguration {
    ciphersuite: Ciphersuite::MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519,
    ..Default::default()
}).await?;
tx.finish().await?;

MlsConversationConfiguration

pub struct MlsConversationConfiguration {
    pub ciphersuite: Ciphersuite,
    pub external_senders: Vec<ExternalSender>,
    pub custom: MlsCustomConfiguration,
}
ciphersuite
Ciphersuite
The MLS ciphersuite for the group. Defaults to MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519.
external_senders
Vec<ExternalSender>
Delivery service public signature keys used for external proposals. Provide via set_raw_external_senders to accept both JWK and raw key formats.
custom
MlsCustomConfiguration
Additional Wire-specific configuration. See MlsCustomConfiguration.

MlsCustomConfiguration

pub struct MlsCustomConfiguration {
    pub key_rotation_span: Option<Duration>,
    pub wire_policy: MlsWirePolicy,
    pub out_of_order_tolerance: u32,
    pub maximum_forward_distance: u32,
}
key_rotation_span
Option<Duration>
Planned: automatic key rotation interval. Not yet implemented.
wire_policy
MlsWirePolicy
Controls whether handshake messages are encrypted. Plaintext (default) or Ciphertext.
out_of_order_tolerance
u32
How many application messages within an epoch can arrive out of order before they are rejected. Default: 2.
maximum_forward_distance
u32
Maximum number of application messages that may be skipped (dropped by the DS) within an epoch. Default: 1000.

Querying conversations

conversation_exists

pub async fn conversation_exists(
    &self,
    id: &ConversationIdRef,
) -> Result<bool>
Returns true if a fully-merged conversation with the given ID exists locally.

get_client_ids

// on ConversationGuard (via the Conversation trait)
pub async fn get_client_ids(&self) -> Vec<ClientId>
Returns the ClientId of every current group member, derived from their MLS credential identity.

mark_as_child_of

// on ConversationGuard
pub async fn mark_as_child_of(
    &mut self,
    parent_id: &ConversationIdRef,
) -> Result<()>
Marks this conversation as a child of another. The parent conversation must already exist in the keystore. This is used for sub-conversations.
parent_id
&ConversationIdRef
required
The ID of the parent conversation.

Membership management

add_members

// on ConversationGuard
pub async fn add_members(
    &mut self,
    key_packages: Vec<KeyPackageIn>,
) -> Result<NewCrlDistributionPoints>
Adds one or more members to the group by committing their key packages. The commit is automatically sent to the Delivery Service via MlsTransport::send_commit_bundle and merged locally on success.
key_packages
Vec<KeyPackageIn>
required
Deserialised KeyPackageIn objects for each new member.
NewCrlDistributionPoints
NewCrlDistributionPoints
Any new CRL distribution points found in the incoming X.509 credentials. Your application should fetch these CRLs and register them with e2ei_register_crl.
Example
let tx = cc.new_transaction().await?;
let mut guard = tx.conversation(&conv_id).await?;
guard.add_members(key_packages).await?;
tx.finish().await?;

remove_members

// on ConversationGuard
pub async fn remove_members(
    &mut self,
    clients: &[impl Borrow<ClientIdRef>],
) -> Result<()>
Removes one or more members from the group. Creates, sends, and merges a remove commit automatically.
clients
&[ClientIdRef]
required
The client IDs to remove.

update_key_material

// on ConversationGuard
pub async fn update_key_material(&mut self) -> Result<()>
Rotates the own leaf node keys and commits. Any pending proposals are included in the commit.

commit_pending_proposals

// on ConversationGuard
pub async fn commit_pending_proposals(&mut self) -> Result<()>
Commits all pending proposals in the group. Is a no-op if there are no pending proposals.

Proposals

Proposals are pre-staged changes that a member can create without yet committing them. Another member (or the same one) then commits them.

new_add_proposal

pub async fn new_add_proposal(
    &self,
    id: &ConversationId,
    key_package: KeyPackage,
) -> Result<MlsProposalBundle>
Creates an Add proposal for the given key package.

new_remove_proposal

pub async fn new_remove_proposal(
    &self,
    id: &ConversationId,
    client_id: ClientId,
) -> Result<MlsProposalBundle>
Creates a Remove proposal for the given client.

new_update_proposal

pub async fn new_update_proposal(
    &self,
    id: &ConversationId,
) -> Result<MlsProposalBundle>
Creates a self-Update proposal to rotate the own leaf node keys.

Messaging

encrypt_message

// on ConversationGuard
pub async fn encrypt_message(
    &mut self,
    message: impl AsRef<[u8]>,
) -> Result<Vec<u8>>
Encrypts an application message for all current group members. Returns the TLS-serialised MlsCiphertext.
Encryption is not available when there is a pending commit or pending proposals. Commit or clear them first.
message
&[u8]
required
The raw plaintext bytes to encrypt.
Vec<u8>
Vec<u8>
TLS-serialised MlsCiphertext ready to be forwarded to the Delivery Service.
Example
let tx = cc.new_transaction().await?;
let mut guard = tx.conversation(&conv_id).await?;
let ciphertext = guard.encrypt_message(b"Hello, world!").await?;
tx.finish().await?;

decrypt_message

// on ConversationGuard
pub async fn decrypt_message(
    &mut self,
    message: impl AsRef<[u8]>,
) -> Result<MlsConversationDecryptMessage>
Deserialises and processes an incoming MLS message. Handles application messages, proposals, commits, and external join proposals. Future-epoch messages and commits with missing proposals are automatically buffered and replayed.
message
&[u8]
required
TLS-serialised MlsMessage bytes received from the Delivery Service.
MlsConversationDecryptMessage
MlsConversationDecryptMessage

MlsConversationDecryptMessage

pub struct MlsConversationDecryptMessage {
    pub app_msg: Option<Vec<u8>>,
    pub proposals: Vec<MlsProposalBundle>,
    pub is_active: bool,
    pub delay: Option<u64>,
    pub sender_client_id: Option<ClientId>,
    /// Deprecated: prefer the EpochObserver interface
    #[deprecated]
    pub has_epoch_changed: bool,
    pub identity: WireIdentity,
    pub buffered_messages: Option<Vec<MlsBufferedConversationDecryptMessage>>,
    pub crl_new_distribution_points: NewCrlDistributionPoints,
}
app_msg
Option<Vec<u8>>
The decrypted application message payload. None for commit and proposal messages.
proposals
Vec<MlsProposalBundle>
Renewed proposals that could not be included in a received commit. Your application should re-fan these out to other members.
is_active
bool
false when the current client was removed from the group by the commit just processed. The conversation is wiped locally in that case.
delay
Option<u64>
Suggested delay in seconds before the client should commit pending proposals. Provided to pace commit storms.
sender_client_id
Option<ClientId>
The ClientId of the sender. Present only for application messages.
has_epoch_changed
bool
true if processing this message caused an epoch change. Deprecated — prefer implementing the EpochObserver interface instead, which provides richer epoch-change notifications.
identity
WireIdentity
Identity claims from the sender’s credential. See WireIdentity.
buffered_messages
Option<Vec<MlsBufferedConversationDecryptMessage>>
Messages that were received before their epoch’s commit and buffered. Replayed automatically once the commit arrives. Present only on commit decryption.
crl_new_distribution_points
NewCrlDistributionPoints
New CRL distribution points introduced by credentials in this message. Fetch and register them with e2ei_register_crl.

External commits

join_by_external_commit

pub async fn join_by_external_commit(
    &self,
    group_info: VerifiableGroupInfo,
    credential_ref: &CredentialRef,
) -> Result<WelcomeBundle>
Joins an existing MLS group via an external commit. Creates the commit, sends it via MlsTransport::send_commit_bundle, and merges locally on acceptance. If the Delivery Service rejects the commit, clear the pending state and retry by calling this method again with a fresh VerifiableGroupInfo.
group_info
VerifiableGroupInfo
required
A VerifiableGroupInfo (TLS-deserialised from the DS). Must include the ratchet tree extension.
credential_ref
&CredentialRef
required
Credential to use when joining the group.
WelcomeBundle
WelcomeBundle
Contains the new ConversationId and any CRL distribution points from the group.

Secret export and sub-conversations

export_secret_key

// on ConversationGuard (via Conversation trait)
pub async fn export_secret_key(
    &self,
    key_length: usize,
) -> Result<Vec<u8>>
Derives a key of key_length bytes from the current group epoch using the MLS exporter. Used to bootstrap sub-protocols.
key_length
usize
required
Desired output length in bytes. Must not exceed u16::MAX or context_hash * 255.
Vec<u8>
Vec<u8>
The derived key bytes.

get_external_sender

// on ConversationGuard (via Conversation trait)
pub async fn get_external_sender(&self) -> Result<Vec<u8>>
Returns the raw public key of the first external sender configured for this group. Used when initialising sub-conversations.

wipe (wipe_conversation)

// on ConversationGuard
pub async fn wipe(&mut self) -> Result<()>
Destroys the conversation locally — removes the group from the keystore and the in-memory group store. Does not notify other members.

MlsCommitBundle

Returned internally when a commit is produced. Passed to MlsTransport::send_commit_bundle.
pub struct MlsCommitBundle {
    pub welcome: Option<MlsMessageOut>,
    pub commit: MlsMessageOut,
    pub group_info: MlsGroupInfoBundle,
    pub encrypted_message: Option<Vec<u8>>,
}
welcome
Option<MlsMessageOut>
Welcome message for newly added members. None if no members were added.
commit
MlsMessageOut
The commit message to be fanned out to existing group members.
group_info
MlsGroupInfoBundle
Updated GroupInfo for clients that want to join via external commit after this epoch.
encrypted_message
Option<Vec<u8>>
An optional encrypted message to fan out to the new epoch. Reserved for history sharing.

Build docs developers (and LLMs) love