How it works

Every layer, named and checkable.

No black boxes. Kant is built entirely from published, well-studied building blocks — so you don’t have to take our word for any of it.

The message path

From your screen to theirs.

Every hop, every encryption boundary, and every component that touches the data — in one picture.

Sender
Encrypts on device
Double Ratchet session
encrypted frame
Relay
circuit-relay v2
sees ciphertext only
same frame, unchanged
Recipient
Verified fingerprint
decrypts locally
Registry
/register — signed addr + nonce/lookup — resolve peer addressTTL eviction + disconnect pruning
Circuit relay v2
Reservation-based forwardingNAT traversal for all peer typesDeterministic relay identity
Operations
/healthz · /readyz/metrics — Prometheus/admin — token gated
Push proxy
Wake signal only — no message content
Android
Wakes up, reconnects, fetches the encrypted message
Firebase credentials never reach the relay operator — the push proxy is the only trust boundary for notifications.
Identity & handshake

No account. No one hands out your identity.

Your identity is a keypair (Ed25519) created on your own device from a private seed. Nobody issues it, and nobody can revoke it. It stays stable across restarts because it’s derived, not assigned.

The first time two people connect, Kant runs the X3DH handshake to agree on a shared secret that is never sent over the network. From then on, every conversation runs on a Double Ratchet: each message moves the keys forward, so one exposed key reveals nothing before or after it.

The same code runs in the Android, desktop and command-line apps — it lives in one shared core library.

The encryption stack
Application
Messages · Groups · Files · Receipts
Session
Double Ratchet — a new key for every message
Handshake
X3DH — prekeys + one-time prekeys, no secret transmitted
Identity
Ed25519 keypair — derived on device, no account server
Transport
libp2p noise protocol — WebSockets + yamux
Primitives
libsodium — XChaCha20-Poly1305 · BLAKE2b · Curve25519
The relay

A helper, not a server that keeps things.

Kant’s relay is a stateless libp2p circuit-relay v2 bootstrap node. Devices register a signed address, and other devices look it up. That’s the whole job: introductions and relay-assisted connections — never storing messages.

Every request carries a nonce and duplicates are rejected, so a replayed /register call gets nothing. Entries expire on a timer and are removed on disconnect.

The relay’s own identity is derived from a seed, so its address survives restarts and clients reconnect without fuss.

Relay endpoints
POST/registerPublish signed circuit address + nonce
GET/lookupResolve a peer's current circuit address
GET/relay-infoRelay identity, multiaddr, peer count
GET/healthzLiveness probe
GET/readyzReadiness probe
GET/metricsPrometheus metrics scrape endpoint
GET/admin/reservationstokenActive circuit reservations
Delivery

Messages, groups and files.

Transport

Messages travel as length-prefixed frames on two dedicated libp2p protocols.

/kant/ping/1.0.0
encrypted payloads + group msgs
/kant/receipt/1.0.0
delivery + read receipts

Frames are one-way: the sender writes and closes. No connection is held open waiting for a reply.

Group chats

Groups have their own key handling and delivery path, separate from one-to-one chats.

  • ✓Membership and key rotation handled on devices
  • ✓Group state never routed through the relay
  • ✓The relay still only sees encrypted frames

File transfer

Files are split into chunks, each encrypted separately and sent so transfers can resume.

  • ✓A dropped connection doesn’t mean starting over
  • ✓Every chunk is verified on its own
  • ✓Retries don’t need re-encryption

Onion routing

Optional

Multi-hop onion routing with cover traffic, layered on the same transport. For people who need to hide who is talking to whom from the relay itself — not just what they say.

Direct or relay-assisted connections are the default. Onion routing is there for the situations that call for it.

Notifications that carry nothing

On Android, a separate push proxy sends a bare “wake up” signal, so relay operators never need Firebase credentials.

1Relay asks the push proxy to wake a device
2Proxy sends a wake-up — no message content
3The phone wakes and reconnects
4The encrypted message arrives directly
For operators

Run a relay without flying blind.

The relay exposes the same health and metrics surface you’d expect from any production service. Prometheus-native, no extra tooling.

$ curl https://your-relay/healthz
{ "status": "ok" }
$ curl https://your-relay/readyz
{ "ready": true, "peers": 12 }
$ curl https://your-relay/metrics
# HELP kant_relay_peers Active peer count
# TYPE kant_relay_peers gauge
kant_relay_peers 12

Prometheus-native metrics

Scrape straight into Grafana. A reference dashboard ships in the repo under ops/monitoring/.

Admin access is locked by default

/admin/reservations needs a configured bearer token and isn’t reachable at all without one.