Skip to content

SystemOperation

SystemOperation is the protocol/MLS operations envelope in Cryptid. It carries messages that change group state, cryptographic operations, and protocol-level events. NOT user-facing chat content.

We separate SystemOperation from CryptidMessage for:

  • Clarity of semantics:
    • Clients can treat CryptidMessage as “chat stuff” and SystemOperation as “protocol stuff”
    • Keeps UI and state machines cleaner
  • Different handling paths:
    • CryptidMessage goes to: decrypt -> store in message DB -> render in UI -> mabye sync reactions/edits
    • SystemOperation goes to: feed into MLS/group engine -> update permissions/metadata/address mappings -> maybe emit internal events
  • Security and robustness:
    • By having a distinct type, it’s easier to enforce “only MLS-validated, permission-checked commits can change group state”
    • Reject or quarantine bogus SystemOperation payloads without risking the chat message pipeline
  • UX isolation:
    • SystemOperation events are mostly invisible or minimally surfaced
    • Keeping them distinct avoids accidental exposing low-level events as messages
SystemOperation Structure
/// Protocol operation identifier (UUIDv7)
pub struct OperationId(Uuidv7);
/// Protocol/MLS operations envelope
struct SystemOperation {
// UUIDv7 for ordering/sequencing
operation_id: OperationId,
// Device that initiated this operation
sender: DeviceId,
// MLS group this applies to
group_id: GroupId,
// The actual operation
inner: SystemOperationInner,
}
  • operation_id
    • OperationId (wrapper around Uuidv7)
    • Unique identifier for this operation
    • Used for ordering and deduplication
  • sender
    • DeviceId of the device that created this operation
    • Authenticated by MLS group signature
    • Cannot be spoofed
  • group_id
    • GroupId this operation applies to
    • MUST be a valid MLS group
  • inner
    • SystemOperationInner enum variant
    • Determines the type of operation
SystemOperationInner Enum
enum SystemOperationInner {
// MLS commits and welcomes
MLSCommit {
proposals: Vec<Proposal>,
path: Vec<u8>,
},
MLSWelcome { welcome: Vec<u8> },
// Group context extension updates
GroupContextExtension {
permissions: Option<PermissionExtension>,
metadata: Option<GroupMetadataExtension>,
custom_media: Option<CustomMediaExtension>,
thread_metadata: Option<ThreadMetadataExtension>,
},
// Sent by the adding member to a joiner immediately after Welcome.
// MLS carries creds but not addresses, so we need a way for someone
// to supply an initial set of delivery addresses of group members
// to new joiners.
AddressBookSnapshot {
addresses: Vec<(DeviceId, DeliveryAddress)>,
},
// Distributes mailbox public keys, not just routing hints. A member
// who misses this cannot reach the sender at all, which is what the
// rotation overlap window covers. None means first announcement on join.
AddressRotation {
old_addresses: Option<Vec<DeliveryAddress>>,
new_addresses: Vec<DeliveryAddress>,
},
// Epoch change notification
EpochChange { new_epoch: u64 },
}

Carries MLS commit messages that add or remove members from the group.

SystemOperationInner::MLSCommit {
proposals: Vec<Proposal>,
path: Vec<u8>,
}
  • proposals: List of proposals (Add, Remove, Update)
  • path: Commit path for ratchet tree updates
  • All commits MUST be validated against group permissions before processing
  • See MLS Integration for MLS details

Carries welcome messages for newly added members.

SystemOperationInner::MLSWelcome { welcome: Vec<u8> }
  • welcome: MLS welcome message ciphertext
  • Used to add new members to the group
  • Contains encrypted group state for the new member

Carries updates to group context extensions.

SystemOperationInner::GroupContextExtension {
permissions: Option<PermissionExtension>,
metadata: Option<GroupMetadataExtension>,
custom_media: Option<CustomMediaExtension>,
thread_metadata: Option<ThreadMetadataExtension>,
}
  • PermissionExtension:
    • Updates to group permissions (see Moderation Architecture)
    • Contains device_id -> Permission mappings
    • Cryptographically enforced via MLS group extensions
  • GroupMetadataExtension:
    • Updates to group name, description, picture
    • Contains group display information
  • CustomMediaExtension:
    • Updates to group’s custom emoji and sticker packs
    • Contains emoji definitions, sticker references
  • ThreadMetadataExtension:
    • Updates to thread metadata
    • Contains thread titles, settings

Sent by the adding member to a joiner immediately after Welcome. MLS carries credentials but not addresses, so a new member otherwise knows every device_id in the group and no addresses at all.

SystemOperationInner::AddressBookSnapshot {
addresses: Vec<(DeviceId, DeliveryAddress)>,
}
  • addresses: every current group member’s delivery address for this group
  • MUST be sent before the joiner is expected to participate
  • The joiner replies with an AddressRotation carrying old_addresses: None

Distributes a device’s mailbox public keys to the group. This is key distribution, not merely a routing hint. A member who misses it cannot reach the sender at all, which is what the overlap window covers.

SystemOperationInner::AddressRotation {
old_addresses: Option<Vec<DeliveryAddress>>,
new_addresses: Vec<DeliveryAddress>,
}
  • old_addresses: previous addresses being replaced, for cleanup. None on first announcement after joining a group
  • new_addresses: new addresses, including the mailbox_public senders encrypt to
  • Members update their address book, which is keyed by (DeviceId, GroupId)
  • Old addresses remain deliverable until the sender deregisters them
  • See Mailbox Keypairs for derivation and rotation

Notifies that the MLS epoch has changed.

SystemOperationInner::EpochChange { new_epoch: u64 }
  • new_epoch: New MLS epoch number
  • Clients update their local group state
  • Triggers key derivation for new epoch

  • Permission Enforcement:

    • Clients MUST verify sender has required permissions before accepting commits
    • Permission checks MUST use the MLS group extension (not local state)
    • Modified clients that skip verification are isolated from honest clients because they’ll brick their own state
  • Commit Validation:

    • All MLSCommit operations MUST be validated against group permissions
    • Unauthorized commits MUST be rejected
    • See Moderation Architecture
  • Message Handling:
    • SystemOperation messages MUST be processed before CryptidMessage messages
    • Group state changse affect message decryption
    • Clients MUST apply SystemOperations before processing messages in the same epoch
  • Ordering:
    • SystemOperation messages provide causal ordering within an epoch
    • Clients MUST process in MLS epoch order
    • Within same epoch, use operation_id for ordering
  • Enforcing UI display of system operations
  • Determining when to show “X joined the group” messages
  • Storing operation history
  • Reverting failed operations (MLS handles this)

{
"operation_id": "019e3eb4-2fbf-7863-9118-8c80709fdce1",
"sender": "admin-device-id",
"group_id": "group-uuid-v4",
"inner": {
"type": "MLSCommit",
"proposals": [
{
"type": "Add",
"new_member": "device-xyz"
}
],
"path": "base64encodedpath..."
}
}
{
"operation_id": "019e3eb4-2fbf-7863-9118-8c80709fdce1",
"sender": "admin-device-id",
"group_id": "group-uuid-v4",
"inner": {
"type": "GroupContextExtension",
"permissions": {
"device_permissions": {
"device-abc": 1,
"device-xyz": 2
},
"founder": "device-abc",
"version": 5
},
"metadata": null,
"custom_media": null,
"thread_metadata": null
}
}
{
"operation_id": "019e3eb4-2fbf-7863-9118-8c80709fdce1",
"sender": "device-xyz",
"group_id": "group-uuid-v4",
"inner": {
"type": "AddressRotation",
"old_addresses": [
{
"prefix": "oldaddress123",
"server": "chat.example.com",
"mailbox_public": "base64_x25519_public_key",
"created_at": 1787798678,
"active": true
}
],
"new_addresses": [
{
"prefix": "newaddress456",
"server": "chat.example.com",
"mailbox_public": "base64_x25519_public_key",
"created_at": 1787798706,
"active": true
}
]
}
}