How Zitrone works

Zero-knowledge architecture means the server never sees, stores, or logs plaintext message content under any circumstances. Not as a policy. As a property of the design. Here's the whole thing, in plain language.

Signal Protocol

Zitrone uses the Signal Protocol — the X3DH key agreement for establishing a shared secret with someone you've never messaged before, and the Double Ratchet for everything after. It's the most scrutinized messaging cryptography in existence. We didn't invent our own. You should be suspicious of anyone who does.

Every single message is encrypted with its own AES-256-GCM message key, derived by the ratchet and discarded after use. Keys rotate with every message, which gives you forward secrecy: if a key were ever compromised, it unlocks one message, not your history — and your history is probably already gone anyway.

The implementations are established libraries, not homemade: the shipped Android client uses libsignal-android, and the reference crypto for the in-development clients is built on libsodium.

Key generation and storage

Your identity is a Curve25519 keypair generated on your device. The private half never leaves it. There is no account recovery, because there is nothing on our side to recover.

Where your keys live depends on your platform:

The vault — and plausible deniability

On Android, everything Zitrone holds — keys, contacts, messages — lives inside a single encrypted vault image, sealed under a passphrase you set at first launch. The unlock key is derived on-device with Argon2id; nothing about the vault ever reaches a server, which is also why the passphrase is unrecoverable: there is no reset and nothing on our side to recover.

The vault image has a fixed number of slots, and an unused slot is uniformly random filler — byte-indistinguishable from a used one. The number of vaults on a device is never stored anywhere. That structure exists for a reason: Zitrone supports a second, plausibly-deniable vault — a fully independent identity behind a different passphrase, with no cryptographic evidence that it exists. There is deliberately no UI for it anywhere in the app; the complete instructions, and the sharp edges you must read before relying on it, live on the how-to page.

Pucker Burn — the duress password

A separate password that, entered at the lock screen, erases everything Zitrone holds on the device — every vault, preferences, keystore entries, caches — and closes the app. Set it under Settings → Account. Two properties are deliberate: the app offers no readback anywhere — it cannot tell you whether a burn password is set, because that answer would itself prove a duress credential exists — and consequently, forgetting it is unrecoverable. A burn is device-local: it does not delete your account on the relay. An armed device is byte-indistinguishable from an unarmed one.

What the server stores (and doesn't)

The server is a relay, not an archive. Store-and-forward only: a message sits on the server as an opaque encrypted blob until your recipient's device confirms delivery, at which point it is deleted immediately. Undelivered messages are purged after 72 hours and the sender is told so.

The complete list of what the server stores:

And what it never stores:

Attachments are blind blobs: a file is encrypted with its own key on your device and uploaded to the relay, which stores it under a hash of a token it never sees. The token and the key travel inside the ratchet-encrypted message — the relay holds ciphertext it cannot decrypt and cannot even look up without the token.

Transport is WebSocket over TLS 1.3, with certificate pinning in the shipped Android client. Typing indicators and read receipts travel as encrypted signals too — the server can't even tell whether you've read something. Deleting your account purges everything: prekeys, pending envelopes, the account record. Irreversibly.

Lemon drops — QR dead drops

A message can be sealed into a one-time QR "drop" hosted on the relay instead of sent to a contact. The drop is sealed to one recipient's keys, and the QR is a pointer to it, not a key: anyone can scan the image, but only the device it was sealed for can open it — anyone else gets an explanatory screen and can neither read the message nor destroy it (the fetch is non-destructive on the relay, and burning requires a token that only successful decryption reveals). That is what lets a drop hide in plain sight — printed, posted, passed through a channel you don't trust. Opening consumes it, and a drop unclaimed by its sender-chosen deadline is destroyed. Sealing a drop solves a small proof-of-work deposit, so the relay can't be flooded with them for free. The feature is off by default; the how-to page covers enabling and using it.

Cover traffic

The Android client emits synthetic traffic so that a passive network observer — an ISP, a hostile Wi-Fi network — cannot pick out a real send by its timing. Every real send is paired with a cover frame of the same length, in unpredictable order, with a randomized delay. Two things are deliberate about it. First, it has no UI at all — no toggle, no indicator, nothing in Settings — because a visible switch would itself be a signal. Second, a real message is never delayed, reordered, blocked, or made less durable to produce cover; under any contention, cover yields and the real send proceeds. Honest limits: cover is paired with real sends, so periods when you send nothing are not covered — dead air is not disguised. And it does not hide who talks to whom from the relay itself, which sees envelope routing regardless.

Screenshot protection, by platform

Each platform allows a different level of protection, so we're specific about it. Zitrone ships on Android today — the iOS and Linux clients below are in development and not yet available for download.

Honesty clause: a compromised device with an OS-level keylogger, or someone pointing a second camera at the screen, is outside any app's power. We don't pretend otherwise.

The network layer: I2P, Tor, clearnet

Network-level metadata — who connected, from where — is the hardest thing for any messenger to hide. Zitrone keeps stored metadata minimal, and on the wire it resolves its transport in a fixed order:

Open source and audit history

Everything — the encryption, the server, the shipped Android app and the in-development clients — is open source under AGPL-3.0 at github.com/jackofall1232/zitrone. The AGPL means anyone running a modified Zitrone as a service must publish their changes. No silent forks with weakened crypto.

Audit history: no third-party audits have been conducted yet. We'd rather tell you that plainly than imply otherwise. The audit log updates as audits complete, and researchers who want to conduct one — or who find a vulnerability — should follow the responsible disclosure policy: acknowledgement within 48 hours, fix target within 90 days, good-faith research explicitly authorized.

For the full technical treatment — threat model, transport details, the works — read the security model documentation. For a hands-on walkthrough of every shipped feature, see the how-to guide.