Group Management and Federation
Group Creation Process
Section titled “Group Creation Process”fn create_group( founder: &DeviceIdentity, initial_members: &[LocalContact], provider: &impl OpenMlsProvider,) -> Result<MultiUserGroup> {
// 1. Generate random group ID let group_id = generate_random_bytes(32);
// 2. Create MLS group with founder's credential and RatchetTreeExtension let config = MlsGroupCreateConfig::builder() .with_ratchet_tree_extension(true) // Include tree in Welcome .build();
let mut mls_group = MlsGroup::new( provider, &founder.keypair, &config, founder.create_mls_credential(), )?;
// 3. Fetch KeyPackages and add initial members to MLS group let mut key_packages = Vec::new(); for contact in initial_members { // Fetch fresh KeyPackage from contact's server let key_package = fetch_keypackage( &contact.keypackage_server, &contact.device_id, )?; key_packages.push(key_package); }
// Add all members in one operation (OpenMLS API) let (commit, welcome, _) = mls_group.add_members( provider, &founder.keypair, &key_packages, )?;
// Commit the changes mls_group.merge_pending_commit(provider)?;
// 4. Send welcome messages to all added members for contact in initial_members { // Welcome includes ratchet tree (RatchetTreeExtension) send_federated_message( welcome.clone(), &contact.delivery_addresses[0], // Use first delivery address )?; }
// 5. Send commit to existing members (none in this case, group just created) Ok(MultiUserGroup { group_id, founder_device: founder.device_id, members: build_member_list_from_mls(&mls_group), mls_state: mls_group, admin_devices: vec![founder.device_id], created_at: current_unix_timestamp(), })
}
Adding Members to Groups
Section titled “Adding Members to Groups”Members can add people they have as contacts. When adding a member, their KeyPackage must be fetched from their designated server.
KeyPackage Requirement:
Each contact stores a keypackage_server
field (from their ShareableIdentityBundle) that specifies where to fetch KeyPackages. The adding member:
- Fetches a fresh KeyPackage from the contact’s server
- Uses it to add the contact to the MLS group
- Sends the Welcome message to the contact’s delivery address
See Contact Exchange and Trust for details on KeyPackage management.
fn add_member_to_group( group: &mut MultiUserGroup, new_member_contact: &LocalContact, adding_member: &DeviceIdentity, provider: &impl OpenMlsProvider,) -> Result<()> {
// 1. Check permissions if !group.admin_devices.contains(&adding_member.device_id) { return Err("Only admins can add members"); }
// 2. Fetch fresh KeyPackage from new member's server let key_package = fetch_keypackage( &new_member_contact.keypackage_server, &new_member_contact.device_id, )?;
// 3. Add member to MLS group (creates commit + welcome) let (commit, welcome, _) = group.mls_state.add_members( provider, &adding_member.keypair, &[key_package], )?;
// 4. Merge pending commit to update local group state group.mls_state.merge_pending_commit(provider)?;
// 5. Serialize commit for distribution let commit_message = commit.tls_serialize_detached()?;
// 6. Send commit to all current members (updates their group state) for member in &group.members { let secure_message = SecureMessage { message_id: generate_random_bytes(32), group_id: group.group_id, mls_ciphertext: commit_message.clone(), sender_signature: adding_member.sign(&commit_message), timestamp: current_unix_timestamp(), message_type: MessageType::SystemOperation, }; send_federated_message( secure_message, &member.delivery_addresses[0], // Use first delivery address )?; }
// 7. Serialize and send Welcome to new member let welcome_message = welcome.tls_serialize_detached()?; send_federated_message( welcome_message, &new_member_contact.delivery_addresses[0], )?;
// 8. Update local group state group.members.push(GroupMember { device_id: new_member_contact.device_id, public_key: new_member_contact.public_key, delivery_addresses: new_member_contact.delivery_addresses.clone(), added_by: adding_member.device_id, added_at: current_unix_timestamp(), participation_status: ParticipationStatus::PendingWelcome, });
Ok(())
}
Address Rotation in Groups
Section titled “Address Rotation in Groups”When a group member rotates their delivery addresses, they notify the group via a system message.
Address Rotation Protocol
Section titled “Address Rotation Protocol”fn rotate_address_in_group( group: &mut MultiUserGroup, device: &DeviceIdentity, new_address: String, provider: &impl OpenMlsProvider,) -> Result<()> { // 1. Create system message announcing address change let address_rotation = SystemOperation::AddressRotation { device_id: device.device_id, new_delivery_address: new_address.clone(), timestamp: current_unix_timestamp(), };
// 2. Serialize and sign the message let message_bytes = serialize(&address_rotation)?; let signature = device.sign(&message_bytes);
// 3. Encrypt with MLS let encrypted = group.mls_state.encrypt_application_message( provider, &device.keypair, &message_bytes, )?;
// 4. Send to all group members for member in &group.members { let secure_message = SecureMessage { message_id: generate_random_bytes(32), group_id: group.group_id, mls_ciphertext: encrypted.clone(), sender_signature: signature.clone(), timestamp: current_unix_timestamp(), message_type: MessageType::SystemOperation, }; send_federated_message( secure_message, &member.delivery_addresses, )?; }
// 5. Update local group state if let Some(member) = group.members.iter_mut() .find(|m| m.device_id == device.device_id) { member.delivery_addresses.push(new_address); }
Ok(())}
Receiving Address Rotation
Section titled “Receiving Address Rotation”When members receive an address rotation message:
fn handle_address_rotation( group: &mut MultiUserGroup, rotation: &SystemOperation::AddressRotation,) -> Result<()> { // 1. Find the member let member = group.members.iter_mut() .find(|m| m.device_id == rotation.device_id) .ok_or("Unknown member")?;
// 2. Add new address to their list if !member.delivery_addresses.contains(&rotation.new_delivery_address) { member.delivery_addresses.push(rotation.new_delivery_address.clone()); }
// 3. Optional: Remove old addresses after grace period // (Keep old addresses for a few days to handle in-flight messages)
Ok(())}
Address rotation properties:
- Members maintain multiple addresses during transition
- Old addresses remain valid for a grace period (e.g., 7 days)
- Group members automatically learn new addresses
- No manual contact updates needed
Federation Protocol
Section titled “Federation Protocol”Server Discovery
Section titled “Server Discovery”GET /.well-known/cryptidHost: server.example.com
{ "protocol_version": "1.0", "server_name": "server.example.com", "federation_enabled": true, "supported_features": [ "mls_messaging" ], "rate_limits": { "federated_messages_per_minute": 1000, "max_message_size": 10485760 }, "certificate_fingerprint": "sha256:a1b2c3d4..."}
Cross-Server Message Delivery
Section titled “Cross-Server Message Delivery”PUT /federation/v1/deliver/{transaction_id}Content-Type: application/jsonAuthorization: Bearer <federation_token>
{ "origin_server": "alice.server1.com", "transaction_id": "unique_transaction_id", "messages": [ { "message_id": "message_id_32_bytes", "recipient_addresses": ["bob@server2.com"], "group_id": "group_id_32_bytes", "mls_ciphertext": "base64_of_encrypted_content", "sender_signature": "ed25519_signature", "timestamp": 1758999498 } ]}
Recipient Address Handling
Section titled “Recipient Address Handling”The recipient_addresses
field contains delivery addresses (not device_ids). If a device has multiple delivery addresses on different servers, the message may be sent to any active address. The receiving server routes to the device regardless of which address was used.
Example:
{ "origin_server": "server1.com", "messages": [ { "recipient_addresses": [ "bob-work@server2.com", // Bob's work address "bob-personal@server2.com" // Bob's personal address (same device) ], // Server2 delivers to Bob's device via either address } ]}