How it works
Sending a message
One message, followed end to end. At every hop the server sees only a recipient pseudonym, a ciphertext blob, and a size — never the sender, the content, or an IP.
You and your friend write in a language only the two of you agreed on, before the note ever leaves your hand. When you drop it off, the doorman only reads “deliver to” — the sender’s name is folded up inside the sealed note. If your friend is out, the note waits in their mailbox and is shredded the instant they pick it up.
The client ratchet-encrypts the message, wraps it in a Sealed Sender envelope, and POSTs it. The authenticated sender id is used only as a Redis rate-limit key — never stored with the row. If the recipient is online it’s pushed over WebSocket with a signed timestamp; otherwise it waits in the UNLOGGED queue under a per-message TTL and drains on reconnect. An ACK hard-deletes the row.
Step by step
Fetches Bob’s prekey bundle (which costs a proof-of-work) with a transparency proof, verifies it against a pinned key, and derives a Double Ratchet session.
Ratchet-encrypts the text (a fresh key per message), pads it to 1024 bytes, and seals the sender identity inside.
Rate-limits by a keyed hash of the sender, then enqueues the ciphertext addressed only to Bob.
If Bob is online, pushes it instantly over the WebSocket with a signed timestamp; if offline, the row waits under its TTL.
Opens the seal, verifies Alice’s signature, decrypts, and acknowledges.
Hard-deletes the row on ACK. Anything unacked past its TTL is swept within 10 minutes.
What the server learns
Server can observe
- Recipient id, ciphertext size, queue time, expiry, delivery event.
- The sender — but only transiently, for auth and rate-limiting.
Server cannot learn
- Message plaintext, or the sender stored with the row.
- Conversation id, read content, or contact names.
- A permanent read-receipt or delivery log.