ContactRequest
ContactRequest is the message type used for initial contact establishment between devices that are not yet in any shared MLS group. It handles the bootstrapping of new contacts and direct message groups.
Purpose
Section titled “Purpose”When two devices want to establish their first contact (e.g., via a QR code scan), they need to:
- Create a new MLS group for 1:1 chat
- Exchange identity information
- Store each other as contacts
ContactRequest handles this entire flow in a single message type.
ContactRequest Structure
Section titled “ContactRequest Structure”/// Contact request identifier (UUIDv7)pub struct RequestId(Uuidv7);
/// Initial ContactRequeststruct ContactRequest { // UUIDv7 for ordering request_id: RequestId,
// Device sending the request sender: DeviceId,
// Their UserIdentity (for contact store) user_identity: UserIdentity,
// Their devices (each contains delivery address and keypackage server) // Recipient fetches KeyPackages from each device's keypackage_server devices: Vec<DevicePublicInfo>,}Field Specification
Section titled “Field Specification”request_id- Wrapper around Uuidv7
- Unique identifier for this contact request
- Used for deduplication and ordering
sender- DeviceId of the device sending the request
- Authenticated by the sender’s device key
user_identity- UserIdentity of the requester
- Stored in the recipient’s contact store
- See UserIdentity
devices- All devices controlled by this user
- Used to create the DirectMessageGroup with all their devices
- See DevicePublicInfo
Contact Establishment Flow
Section titled “Contact Establishment Flow”Overview
Section titled “Overview”sequenceDiagram Alice ->> Relay Server: Upload KeyPackages to Server Alice ->> Bob: Scan QR / Exchange InfoPackage Alice ->> Bob: Create DirectMessageGroup and Send ContactRequest Bob ->> Relay Server: Fetch KeyPackage from server for each device in devices Bob ->> Alice: Receive MLSWelcome Alice --> Bob: Both in DM group, ready to chat
Detailed Steps
Section titled “Detailed Steps”-
KeyPackage Upload
Alice’s devices upload KeyPackages to their respective
keypackage_server:- Each device uploads 50-100 KeyPackages
- KeyPackages are stored by device_id on the server
- The server URL for each device is stored in DevicePublicInfo
- See KeyPackages
-
InfoPackage Exchange
Both devices exchange InfoPackages (via QR code, NFC, or link):
- Contains: UserIdentity, DevicePublicInfo array
- Does NOT contain KeyPackages (they’re on the respective servers)
- See InfoPackages
-
Create DirectMessageGroup
The requester creates a new MLS group:
- Group ID is deterministic: Blake3(sorted(user1_id, user2_id))
- Both users are founders
-
Send ContactRequest
Requester sends ContactRequest with:
- Their UserIdentity (for contact store)
- Their DevicePublicInfo array
-
Fetch KeyPackages
Recipient receives ContactRequest and:
- Extracts device IDs from the
devicesfield - For each device, fetch a KeyPackage from that device’s
keypackage_server - Validates KeyPackage signatures and credentials
- Extracts device IDs from the
-
Add Members and Send Welcome
Recipient:
- Creates the same DirectMessageGroup (same group ID)
- Stores the sender’s UserIdentity in contact store
- Adds sender’s devices to the group using the fetched KeyPackages
- Sends MLSWelcome to the new devices
-
Group Ready!
Both devices now have:
- A shared DirectMessageGroup
- Each other in their contact store
- Full encryption for 1:1 chat
Requirements
Section titled “Requirements”Security Requirements
Section titled “Security Requirements”-
Identity Verification:
- Clients SHOULD verify the UserIdentity matches what was received via InfoPackage
- Device signatures MUST be validated
- Device IDs in ContactRequest MUST match UserIdentity’s device list
-
KeyPackage Validation:
- Fetched KeyPackages MUST be valid (not expired, not used)
- KeyPackage credential MUST match device_id
- Clients MUST remove used KeyPackages from storage
Privacy Requirements
Section titled “Privacy Requirements”- Contact Store:
- Contact information is stored locally only
- Not shared with servers
- Users can delete contacts at any time
NOT Protocol Responsibilities
Section titled “NOT Protocol Responsibilities”- How to exchange InfoPackages (QR, NFC, link, etc.)
- When to show contact requests to users
- How to display contacts in UI
- Contact list management
- Blocking/muting new contacts
Example ContactRequest
Section titled “Example ContactRequest”{ "request_id": "019e607a-9846-7483-8698-83fbc5b8130a", "sender": "device-abc123", "user_identity": { "user_id": "a1b2c3d4e5f6...", "created_at": 1779735133, "default_persona": { "display_name": "Alice", "profile_picture": null, "bio": "Hello!", "pronouns": null }, "personas": {} }, "devices": [ { "device_id": "device-abc123", "public_key": "base64encoded...", "initial_delivery_address": { "prefix": "alice-device-abc123", "server": "chat.example.com" }, "keypackage_server": "chat.example.com", "linked_at": 1779734100 }, { "device_id": "device-def456", "public_key": "base64encoded...", "initial_delivery_address": { "prefix": "alice-device-def456", "server": "chat.example.com" }, "keypackage_server": "chat.example.com", "linked_at": 1779734100 } ]}DirectMessageGroup ID Derivation
Section titled “DirectMessageGroup ID Derivation”Group ID is deterministic to ensure both parties create the same group:
fn create_dm_group_id(user1: &UserId, user2: &UserId) -> GroupId { let mut sorted = [*user1, *user2]; sorted.sort(); // Ensures same ID regardless of who creates group blake3(&sorted.concat())}