Skip to content

Federation overview

Federation lets independently operated Zentalk servers exchange messages, discover each other’s users, and route traffic across server boundaries while each operator keeps full control over their own data and users.

Zentalk federates at two distinct layers. Both must be healthy for cross-server messaging to work end to end.

Layer Component Transport Purpose
API federation zentalk-api HTTPS (mTLS in production) Server registration, challenge/verify authentication, signed message delivery, user lookup
Node federation zentalk-node (relay + mesh) libp2p (TCP, TLS 1.3/Noise) Shared Kademlia DHT, distributed user directory, relay routing, delivery confirmation

Each API server holds an Ed25519 server identity. The server ID is cryptographically derived from the public key, so a peer cannot claim an ID it does not own — registration is rejected if the claimed ID does not match the key. The keypair is generated with federation-keygen and stored encrypted with a passphrase.

Servers authenticate to each other with a challenge/verify handshake, then exchange messages through a persistent inbox/outbox pipeline with retry, replay protection (nonce store), and per-server rate limiting.

Relays and mesh nodes from all federated servers join a shared DHT (via a common bootstrap node). The node-level federation package (zentalk-node/pkg/federation) provides:

  • Distributed user directory — user-to-home-server mappings stored encrypted in the DHT. DHT keys are derived with BLAKE2b (one-way), entries are ECDSA-signed (P-256) by the user’s own key — unlike server-to-server federation, which uses Ed25519 server identities — and privacy levels (PUBLIC, CONTACTS_ONLY, PRIVATE) control discoverability. Entries expire after 7 days.
  • Anti-enumeration — lookup rate limiting (100 lookups per minute per IP), proof-of-work challenges for bulk lookups, honeypot entries, and automatic blocking of suspicious lookup patterns.
  • Federated blocking, rate limiting, and delivery confirmation — cross-server enforcement of user blocks, per-peer quotas, and acknowledgement of relayed messages.

All API-layer federation endpoints live under /api/federation. Endpoints marked Signed require the request-signing headers described below; /health and /status are exempt from authentication.

Endpoint Method Auth Purpose
/api/federation/register POST Registration payload signature Register a peer server (ID, URL, public key, signed challenge)
/api/federation/servers GET Signed List all known federated servers
/api/federation/servers/{serverID} GET Signed Get details for one server
/api/federation/auth/challenge POST Signed Issue a single-use authentication challenge for a server ID
/api/federation/auth/verify POST Signed Verify a signed challenge response
/api/federation/message POST Signed Receive a federated message (signature and forwarding chain verified, then queued to the inbox)
/api/federation/users/lookup POST Signed Resolve a user address to its home server
/api/federation/status GET None Federation status: enabled flag, server ID, known-server count
/api/federation/health GET None Health check for peer monitoring

Every federation request except /register, /health, and /status must carry:

Header Content
X-Server-ID The calling server’s ID
X-Timestamp Unix timestamp (seconds); rejected if older than 5 minutes or more than 1 minute in the future
X-Nonce Unique nonce; duplicates within the timestamp window are rejected as replay attacks
X-Signature Base64 Ed25519 signature over timestamp + nonce + body

The receiving server verifies the signature against the public key it recorded for that server ID at registration time.

Registration and re-authentication use a challenge flow: the peer requests a challenge via /auth/challenge, signs it, and presents the signature to /auth/verify (or in the /register payload). With federation auth v2 enabled (ZENTALK_FEDERATION_AUTH_V2=true), challenges are server-issued, persisted, and consumed single-use — a peer cannot supply its own challenge value. The v2 registration payload additionally binds the server URL and public-key fingerprint into the signed data.

Legacy v1 peers (client-supplied challenge, legacy signature payload) are accepted only while the grace window is open. The grace window closes at the deadline configured by ZENTALK_FEDERATION_V1_DEADLINE (RFC 3339; default 2026-08-01T00:00:00Z), after which v1 authentication is rejected regardless of the v2 flag. V1 acceptances are counted in the zentalk_federation_auth_v1_grace_total metric so operators can monitor remaining v1 usage before cutover.

Federation peers are authenticated at two layers:

  • Application layer — per-peer Ed25519 public-key fingerprint pinning via ZENTALK_FEDERATION_PEERS. Server IDs are derived from public keys, registration signatures prove key ownership, and every subsequent request is signature-verified against the pinned key.
  • Transport layer — mutual TLS with TLS 1.3 required and hot-reloadable certificates. Plaintext HTTP between federation peers is refused in production (ZENTALK_REQUIRE_HTTPS=true). Outbound federation connections additionally support per-server certificate pinning (SHA-256 hashes of peer certificates); when unpinned connections are disallowed, connections to servers without pins are rejected.

Operators can also restrict the peer set explicitly with ZENTALK_ALLOWED_SERVER_IDS and ZENTALK_BLOCKED_SERVER_IDS.

The federation middleware enforces per-server and per-IP limits. Violations accumulate into temporary bans with exponential backoff.

Category Limit Window
Registration (per IP) 5 1 hour
Message delivery 50 1 minute
Server sync 10 1 minute
Query/search 20 1 minute
Default (other endpoints) 30 1 minute
Ban trigger Duration
3 violations within 5 minutes 15 minutes
10 violations within 1 hour 1 hour
Repeat offender Previous duration × 2 per ban, capped at 24 hours