01Protocol Overview

Loqa federation enables independently operated instances to exchange messages, membership changes, reactions, and typing indicators in real time — without a central coordinator. The protocol is designed around four core principles.

Decentralized

No single server is authoritative. Each instance manages its own users, guilds, and keys. Peers discover and authenticate each other directly — there is no federation "hub" or registry.

E2EE-Preserving

When a payload is already protected end to end, the federation layer can relay its opaque encrypted fields without converting them to plaintext. This does not make non-E2EE content encrypted.

Cryptographically Authenticated

Every request is signed with Ed25519. Receiving servers verify the signature against the sender's public key before processing. Replay attacks are blocked by a ±5 minute timestamp window.

Reliable Delivery

Events are persisted in a transactional outbox and delivered with exponential backoff retry (up to 10 attempts). The inbox deduplicates events so processing is idempotent.

Federation Architecture
Instance A
loqa.chat
API Layer
Hooks
Outbox
Signed Events
Signed Events
/_federation/v1/events
TLS 1.3 + Ed25519
Instance B
community.org
Inbox Dedup
Processor
Database

02Server Identity & Discovery

Every federation-enabled Loqa instance publishes a discovery document at a well-known URL. This document is the root of trust — it tells other servers how to reach us, which public key to verify our signatures against, and what capabilities we support.

Discovery Document

GET https://loqa.chat/.well-known/loqa/federation
{
  "server_name": "loqa.chat",
  "federation_url": "https://loqa.chat/_federation",
  "public_key": "ed25519:aB3dEf7G:dGhlIHB1YmxpYyBrZXkgYmFzZTY0...",
  "version": "1.0",
  "capabilities": [
    "messages",
    "members",
    "reactions",
    "typing",
    "mls_e2ee",
    "guild_transfer"
  ]
}

Ed25519 Keypair

Each server generates an Ed25519 signing keypair on first boot. The private key is encrypted with AES-256-GCM using the server's master encryption key before storage. The public key and a key_id fingerprint (first 6 bytes, base64) are published in the discovery document.

Key Storage

Private keys are stored in the federation_keys table as hex(nonce):base64(ciphertext). The 12-byte AES-GCM nonce is unique per key. Keys can be rotated — old keys are marked expired but retained for signature verification during the transition window.

Capability Negotiation

The capabilities array declares which federation features this instance supports. Peers check capabilities before sending events — e.g., a server without guild_transfer will never receive transfer offers.

Server Name Validation

When fetching a remote discovery document, the server_name field is validated against the requested domain. A mismatch generates a warning — preventing DNS hijacking from silently redirecting federation traffic.

03HTTP Signature Authentication

Every federation request carries three custom HTTP headers that cryptographically bind the request to the sending server. The receiving server verifies the signature before processing any event.

Signature Construction
1
Build signature payload
METHOD\nPATH\nTIMESTAMP\nSHA256(body)
2
Sign with Ed25519
Server's private signing key
3
Attach headers
Origin + Timestamp + Signature

Request Headers

HeaderFormatPurpose
X-Loqa-Originloqa.chatIdentifies the sending server (used to look up its public key)
X-Loqa-TimestampUnix epoch secondsBinds signature to a point in time — prevents replay outside ±5 min window
X-Loqa-Signatureed25519:<fingerprint>:<base64>Ed25519 signature over the constructed payload

Verification Flow

Check timestamp freshness
|now − timestamp| ≤ 300 seconds
Look up peer's public key
FROM federation_peers WHERE status = 'active'
Rebuild payload + verify Ed25519 sig
METHOD + PATH + TIMESTAMP + SHA256(body)
Accept or reject request
401 Unauthorized on any failure

Replay Protection

The ±5 minute timestamp window ensures that intercepted requests cannot be replayed later. The body hash (SHA-256) is included in the signed payload, so modifying the request body invalidates the signature.

Key Rotation

The key_id in the signature header identifies which public key to verify against. When a server rotates its signing key, it publishes the new key in its discovery document. Old keys remain valid for signature verification until explicitly expired.

04Event System

The event system is the core of Loqa federation. Local actions (sending a message, adding a reaction, joining a guild) trigger hooks that fan out signed event envelopes to all peer servers via a persistent outbox.

Event Types

Event TypeTriggerDeliveryData Fields
MESSAGE_CREATEUser sends a messageOutboxid, channel, author, content, encrypted_content, nonce, attachments, embeds
MESSAGE_UPDATEUser edits a messageOutboxid, channel, updates
MESSAGE_DELETEUser deletes a messageOutboxid, channel
REACTION_ADDUser reacts to a messageOutboxmessage_id, channel_id, emoji, user_id
REACTION_REMOVEUser removes a reactionOutboxmessage_id, channel_id, emoji, user_id
MEMBER_JOINUser joins a guildOutboxguild_id, user_id
MEMBER_LEAVEUser leaves a guildOutboxguild_id, user_id
MEMBER_UPDATEProfile or role changeOutboxguild_id, user_id, updates
TYPING_STARTUser starts typingDirect (ephemeral)channel_id, user_id
GUILD_TRANSFERGuild migrated to new serverOutboxguild_id, new_authority, origin
CHANNEL_CREATEChannel added to guildOutboxguild_id, channel (id, name, type, position, topic)
CHANNEL_UPDATEChannel metadata changedOutboxguild_id, channel_id, updates
CHANNEL_DELETEChannel removedOutboxguild_id, channel_id
ROLE_CREATERole added to guildOutboxguild_id, role (id, name, color, permissions)
ROLE_UPDATERole properties changedOutboxguild_id, role_id, updates
ROLE_DELETERole removedOutboxguild_id, role_id
GUILD_UPDATEGuild metadata changedOutboxguild_id, updates (name, description, icon)
MEMBER_BANMember banned from guildOutboxguild_id, user_id, reason, delete_message_seconds
MEMBER_UNBANMember unbanned from guildOutboxguild_id, user_id
MEMBER_KICKMember kicked from guildOutboxguild_id, user_id
MESSAGE_BULK_DELETEMessages bulk-deleted from channelOutboxguild_id, channel_id, message_ids

Event Envelope

Events can be sent individually or in batch — the /_federation/v1/events endpoint accepts both a single JSON object and a JSON array of event envelopes. The response reports per-event processing counts.

Event envelope structure (single or as elements of a batch array)
{
  "event_id": "msg_create_abc123",
  "origin": "loqa.chat",
  "timestamp": 1739836800,
  "guild_id": "guild_456",
  "event_type": "MESSAGE_CREATE",
  "data": { ... },
  "signature": "ed25519:aB3dEf7G:base64_signature..."
}

Outbox & Inbox Architecture

API Route
Local DB write + Centrifugo push
Hook
Fire-and-forget, never blocks API
Outbox
Persistent queue in PostgreSQL
Drain Task
Every 5s, batch of 50
HTTPS + Ed25519
Receive Events
Verify signature + peer status
Inbox Dedup
ON CONFLICT (event_id) DO NOTHING
Processor
Dispatch by event type
Local DB
Federated message/member stored

Exponential Backoff

Failed deliveries are retried with exponential backoff: 2n seconds per attempt, capped at ~4 minutes (28 = 256s). After 10 failed attempts, the event is abandoned. Events for inactive peers are immediately dropped.

Idempotent Processing

Every event carries a unique event_id. The inbox table uses ON CONFLICT (event_id) DO NOTHING — if the same event arrives twice (e.g., due to retry), the duplicate is silently discarded.

Fan-Out

Each guild can be federated with multiple peers. The hook layer queries federation_guild_peers and enqueues one outbox entry per peer. Direction control (inbound, outbound, both) limits which peers receive which events.

Ephemeral Events

TYPING_START is the only ephemeral event — it bypasses the outbox entirely and is sent directly via HTTP. If delivery fails, it's silently dropped. Typing indicators are not persisted on either side.

Member Events

MEMBER_JOIN creates a federated_users entry for the remote user. MEMBER_UPDATE syncs profile changes (display name). MEMBER_LEAVE is currently log-only — the membership record is retained for message attribution.

05Peer Management

Federation peers are established through an invite → accept handshake. Each peer relationship is scoped to specific guilds with configurable direction control.

Peer Establishment Flow
1
Admin initiates federation
POST /api/admin/federation/peers
2
Fetch target's discovery document
GET https://target/.well-known/loqa/federation
3
Send signed invite
POST /_federation/v1/peers/invite
{ origin_server, guild_id, public_key, direction }
4
Target persists invite as "pending"
Pending admin approval — guild-peer link is pre-created with the requested direction
5
Target accepts → exchanges public key
POST /_federation/v1/peers/accept
Returns server_name, public_key, federation_url
Peer Active — events flow bidirectionally

Per-Guild Scoping

Peer relationships are linked to specific guilds via federation_guild_peers. A single peer connection can federate multiple guilds, each with independent direction settings.

Direction Control

Each guild-peer link has a direction: inbound (receive only), outbound (send only), or both. This allows read-only mirrors, one-way broadcasting, or full bidirectional federation.

Health Checks

GET /_federation/v1/peers/status returns the server's health, protocol version, and supported capabilities — enabling peers to verify connectivity and feature compatibility.

Public Key Exchange

On accept, both servers exchange their Ed25519 public keys. These keys are stored in federation_peers and used for all subsequent signature verification. Key rotation triggers a discovery document refresh.

Guild State Sync

Authenticated peers can request a full guild state snapshot via GET /_federation/v1/guilds/:id/state (returns guild metadata, channels, roles, members, emoji, and bans) or just the member list via GET /_federation/v1/guilds/:id/members. Both endpoints require Ed25519 peer authentication.

06Guild Transfer Protocol

Loqa supports seamless guild migration between instances — moving an entire community (channels, roles, members, emoji, bans) from one server to another without data loss.

Guild Transfer Sequence
1Admin Initiates
Server admin triggers transfer
POST /api/servers/:id/federation/transfer
2Offer Sent
Origin sends transfer offer to target
POST /_federation/v1/guilds/:id/transfer/offer
Includes guild preview (name, description, member count)
3Snapshot Streamed
Origin builds full state snapshot and streams it
POST /_federation/v1/guilds/:id/transfer/snapshot
Contains guild, channels, roles, members, emoji, bans
4Target Imports
Target imports guild metadata, channels, and roles — becomes the new authority
Guild is created with federation_enabled = true
Transfer progress is queryable via GET /_federation/v1/guilds/:id/transfer/status
5Tombstone & Notify
Origin sets tombstone redirect and broadcasts
GUILD_TRANSFER event to all peers
Clients are redirected to the new authority

Snapshot Contents

ObjectFieldsConflict Strategy
Guildname, owner_id, description, iconON CONFLICT (id) DO UPDATE
Channelsid, name, type, position, topicON CONFLICT (id) DO NOTHING
Rolesid, name, rank, color, permissionsON CONFLICT (id) DO NOTHING
Membersuser_id, username, display_name, avatar, nickname, rolesVia federated_users table
Emojiguild_id, name, animated, urlON CONFLICT DO NOTHING
Bansserver_id, user_id, reasonON CONFLICT DO NOTHING

Tombstone Redirect

After a successful transfer, the origin server stores a federation_transfers record with a redirect_until timestamp. Any API requests for the transferred guild return a redirect to the new authority, giving clients time to update.

Peer Notification

A GUILD_TRANSFER event is broadcast to all federation peers via the outbox. Peers update their federation_guild_peers records to point to the new authority — future events for this guild route to the new server.

07E2EE Compatibility

The federation protocol can preserve a correctly end-to-end encrypted payload by relaying its opaque ciphertext. Sending and receiving servers can still process non-E2EE messages and any content outside the covered encryption path.

Encrypted Message Federation
Client A
Instance A
MLS encrypt or
Double Ratchet encrypt
Federated Payload
encrypted_content
nonce
sender_key_id
content: null
For this covered payload, servers relay opaque bytes
Client B
Instance B
MLS decrypt or
Double Ratchet decrypt

Experimental MLS Federation

The experimental MLS path carries encrypted_content and nonce fields verbatim through federation for local clients to decrypt. Production membership, recovery, and multi-device lifecycle behavior must be validated before a federated channel is represented as E2EE.

Federated User IDs

Users from remote instances are identified as user_id@origin_server (e.g., alice@community.org). This namespacing prevents ID collisions and makes it clear which instance is authoritative for each user’s identity.

Media Proxy

When media_proxy is enabled (default: true), remote media URLs are proxied through the local server. This prevents IP leakage — clients never make direct requests to remote instances, preserving user privacy.

Origin Tracking

Every federated message is tagged with federation_origin in the database. Updates and deletes are scoped to matching origin servers — a remote server can only modify or delete its own messages, never local ones.

MLS Lifecycle Across Federation

The experimental cross-instance MLS path provides dedicated endpoints for KeyPackage distribution, Commit relay, and Welcome delivery. Those endpoints are protocol building blocks, not proof that the full production lifecycle is complete.

Cross-Server KeyPackage Claims

When adding a remote user to an MLS group, the local server proxies the GET /api/mls/key-packages/user@server request to the remote server’s /_federation/v1/keys/:user_id endpoint via a signed GET. The remote server claims and returns a KeyPackage from its local store.

Commit Relay

After a Commit is stored locally, it is relayed to all federated peers of the guild via direct HTTP POST to /_federation/v1/mls/commit. This bypasses the general event outbox for time-critical epoch ordering. The receiving server stores the Commit and advances the local group epoch.

Welcome Relay

When a Commit adds a remote user (identified by the @ separator in their ID), the corresponding Welcome message is sent directly to the user’s home server via /_federation/v1/mls/welcome. The home server stores it for the user to retrieve on their next connection.

Epoch Authority

The server that created the MLS group is authoritative for sequencing Commits. Stale epoch updates from remote peers are accepted best-effort — the Commit is stored but an outdated epoch update is logged and tolerated without failing the overall operation.

08Connecting Your Instance

This section walks through the practical steps for self-hosting operators who want to federate their Loqa instance with the main network or another operator.

Prerequisites

1. Running Loqa Instance

You need a running Loqa backend (Rust) with PostgreSQL. The instance must be reachable via HTTPS on a domain you control — federation endpoints are served under /_federation/v1/*.

2. TLS Certificate

A valid TLS certificate for your domain. Self-signed certs are rejected — use Let's Encrypt or similar. Federation traffic requires TLS 1.2+ (1.3 recommended).

Step-by-Step Setup

Federation Setup Checklist
1
Enable federation in your config
Set federation.enabled = true and federation.server_name to your domain
2
Restart the backend — keys auto-generate
Ed25519 keypair created on first boot, encrypted and stored in DB
3
Verify your discovery document
GET https://yourdomain/.well-known/loqa/federation should return JSON with your public key
4
Send a peer invite to the target instance
POST /api/admin/federation/peers with the target domain — your server will fetch their discovery doc and send a signed invite
5
Target admin accepts the invite
Peer status changes from 'pending' → 'active' and public keys are exchanged
6
Link guilds to the peer
POST /api/admin/federation/guilds to associate specific guilds with the peer, setting direction (inbound/outbound/both)
Federation active — events flow between linked guilds

Configuration Reference

SettingDefaultDescription
federation.enabledfalseMaster switch — enables the /_federation router and discovery endpoint
federation.server_nameYour domain (e.g., community.org). Must match your TLS certificate.
federation.media_proxytrueProxy remote media through your server (prevents IP leakage)
federation.outbox_drain_interval_secs5How often the outbox drain task runs (seconds)
federation.max_retry_attempts10Max delivery retries before an event is abandoned

Reverse Proxy

If you run behind Caddy, Nginx, or similar, ensure /_federation/* and /.well-known/loqa/* are forwarded to the Loqa backend. The discovery document must be publicly accessible without authentication.

Firewall Rules

Open port 443 for inbound HTTPS. Federation uses standard HTTPS — no custom ports or protocols. Outbound requests go to port 443 of peer domains.

Health Verification

After peering, hit GET /_federation/v1/peers/status on both sides to confirm connectivity. You should see the peer listed as active with matching protocol versions.

Troubleshooting

If events aren't flowing, check: (1) peer status is active, (2) guild is linked with correct direction, (3) federation_enabled = true on the guild's server, (4) outbox drain task is running (check logs for Federation event queued).

09API Reference

All federation endpoints are mounted under a single Axum router. Public endpoints require no authentication; peer endpoints require Ed25519 HTTP signature authentication. Admin endpoints are served separately via the main API.

MethodEndpointAuthPurpose
GET/.well-known/loqa/federationPublicDiscovery document — server identity, public key, capabilities
POST/_federation/v1/eventsEd25519Receive signed events (single or batch array)
GET/_federation/v1/peers/statusPublicHealth check — returns server name, version, capabilities
POST/_federation/v1/peers/inviteSignedReceive federation invite — persists peer as "pending"
POST/_federation/v1/peers/acceptSignedAccept invite — exchanges public keys, activates the peer link
GET/_federation/v1/guilds/:id/stateEd25519Full guild snapshot (guild, channels, roles, members, emoji, bans)
GET/_federation/v1/guilds/:id/membersEd25519Member list with profile data (username, display name, avatar)
POST/_federation/v1/guilds/:id/transfer/offerSignedReceive guild transfer offer from origin server
POST/_federation/v1/guilds/:id/transfer/snapshotSignedReceive full guild state snapshot during transfer
GET/_federation/v1/guilds/:id/transfer/statusPublicCheck transfer progress for a guild
GET/_federation/v1/keys/:user_idEd25519Claim a KeyPackage for a local user on behalf of a federated peer
POST/_federation/v1/mls/welcomeEd25519Receive and store a Welcome message for a local user joining a remote MLS group
POST/_federation/v1/mls/commitEd25519Receive a Commit from a remote peer — stores the commit and updates the local group epoch

10Reproducible Audit Scope

Every protocol claim in this whitepaper can be independently verified by reading 15 self-contained Rust source files. The federation crate has zero business logic dependencies — it relies only on standard cryptographic libraries (ed25519-dalek, aes-gcm, sha2) and PostgreSQL.

FilePurposeLines
lib.rsCrate root — FederationState initialization, module exports73
auth.rsHTTP signature construction & verification (Ed25519 + SHA-256)95
signing.rsEd25519 key generation, AES-256-GCM encrypted storage, signing & verifying155
config.rsFederation configuration (server_name, media_proxy, retry limits)56
discovery.rsRemote .well-known document fetching & validation66
transport.rsOutbound signed HTTP POST and GET transport84
outbox.rsPersistent event queue with exponential backoff retry147
hooks.rsFire-and-forget hooks — fan-out events to peers after local writes, MLS relay, moderation relay548
processor.rsInbound event processor — dedup, dispatch, and per-type handlers (incl. moderation, MLS)803
routes.rsAxum router — 14 federation endpoints, event signature verification179
handlers/peers.rsPeer invite, accept, and health check handlers150
handlers/guild.rsGuild state snapshot export with peer authentication267
handlers/transfer.rsGuild transfer — offer, snapshot import, tombstone, peer notification310
handlers/mod.rsHandler module re-exports6
handlers/mls.rsMLS federation handlers — KeyPackage claim, Welcome relay, Commit relay215
Total auditable surface3,067
🔬

For security researchers: These 15 files contain 100% of the federation protocol logic. No federation decisions are made outside this boundary. Every signature, every event dispatch, every peer authentication check is in these files.

📂 View source on GitHub → loqachat/loqa-federation-audit

🔐 See also: Encryption Whitepaper

Questions About Federation?

We welcome review from security researchers, self-hosting operators, and anyone interested in decentralized communication. We're happy to provide additional technical detail or discuss deployment.