How Mirage works.
What the cryptography does, what the infrastructure can and cannot see, and how the engine fits inside another product. Plain about the parts that are finished and the parts that are not.
Start with what it does not do.
Every messenger protects against something and leaves something else exposed. A design can only be judged once both halves are on the table.
Holds up against
- An ISP or network operator reading message or call content
- The relay operator reading anything it carries, including us
- Someone reading your contact list off our servers: it is not there
- Past traffic being decrypted after a key is later compromised
- Tampering with a message in transit, which is detected, not just unlikely
Does not protect against
- A compromised endpoint: malware or a screen reader on your own device
- The person you are talking to, who can always keep or forward what you send
- A global adversary correlating timing across the whole network
- Someone who obtains your recovery phrase
- Anyone watching your local link learning that you are connected at all
There is no account, only a key.
No phone number, no email, no registration. A user is a public key, and the identifier other people see is derived from it. Nothing about the identity needs a server to exist.
Identity is designed in two tiers so that losing hardware is not the same as losing the account. A root key (Ed25519) never encrypts anything; its only job is to sign a versioned roster, the list of devices that currently belong to you. Publishing a new roster version is how a stolen device gets revoked. Multi-device in progress
Verification works the way it should: compare a fingerprint out of band. The app shows it in short groups so it can be read aloud. If you lose every device, a BIP-39 recovery phrase restores the account.
Keys move forward, never back.
A conversation starts with an X3DH key agreement over X25519, which lets the first message go out even while the other side is offline. From there the session runs a Double Ratchet: every message advances the key material, so a key stolen today does not open yesterday's traffic, and the session heals once a compromised key falls out of use.
Calls run their own ratchet, separate from text, because media keys rotate on a different rhythm. Content is sealed with ChaCha20-Poly1305 or AES-256-GCM. Both are authenticated encryption, so tampering is detected rather than merely unlikely.
Several routes, raced in parallel.
NAT is why most peer-to-peer software quietly falls back to a server. Mirage tries several strategies at once and takes whichever wins, so a direct path is used whenever one exists.
Straight to the peer
If the address is already known from peer exchange, a QUIC connection opens immediately and no server is involved.
Introduced by the mesh early
When other desktop nodes are reachable, they can help both sides punch out through their NATs instead of the central relay.
Hole punching via the relay
The relay times a UDP rendezvous between the two sides. It introduces them; it does not carry the session.
Through the relay, still sealed
When no direct path works, the relay forwards packets it cannot open. A TCP/443 fallback for networks that drop UDP entirely is planned
What the relay can and cannot see.
The relay exists for discovery, NAT traversal and holding messages for people who are offline. It keeps no user accounts, and what it carries is sealed before it arrives. This is the honest inventory.
The "yes" rows are the part worth arguing about, and they are why the design pushes so hard toward direct connections: what the relay never carries, it can never be asked to hand over.
Sealed envelopes, held by someone who can't open them.
When the recipient is offline, the message is encrypted into an envelope and left with the relay. The envelope is authenticated, so the holder cannot alter it, and opaque, so the holder cannot read it. It is delivered when the recipient comes back; the relay learns only that something was waiting.
Today envelopes are kept in the relay's memory for up to a day. Letting desktop nodes in the mesh hold them instead, and encrypted history backup, are designed and in progress
Built for networks that get in the way.
Everything runs over QUIC on UDP/443, with TLS 1.3 inside, so certificates and session metadata are not visible to an analyser on the path. Control traffic (key exchange, peer discovery, presence) rides the same tunnel as messages. There is no second channel to watch.
Beyond that, the transport can shape its own packet stream so it does not carry the statistical fingerprint of a messenger. This hardening is an option for deployments on hostile or heavily filtered networks, not a requirement of the protocol.
The mechanism is deliberately not documented here. Publishing the specifics would mostly help the people building classifiers. The cryptography is the opposite case: its strength must never depend on secrecy, so it is described in full.
One idea for chats and calls: the room.
A text conversation and a call are the same object inside the engine: a room. Inside the QUIC connection each room uses independent streams, one for control, one for outgoing media and a separate incoming stream per participant, so one slow peer cannot stall everyone else.
Routers are blind: they forward sealed packets without holding the keys to any of them. One-to-one chats and calls ship today. Small group calls share a key agreed between the participants and are in testing for up to eight people. Group chats in progress
Group key management is moving to MLS (RFC 9420) through a Rust bridge around openmls. The bridge is built and load-tested; wiring it into the engine is the work in progress.
Five layers, nothing kept on disk.
All of the above lives in a C++20 core. The interface on top is replaceable; the desktop messenger is simply the first thing to sit there.
The core keeps nothing on disk. No database, no config files, no key store. It is an in-memory network engine that emits events, and everything persistent belongs to the application that embeds it. The rule exists for recoverability, and it happens to make the engine easy to host inside somebody else's product.
The same core, inside your product.
Mirage is proprietary and owned outright. If you need private communication inside something you are already building, the core can be licensed and integrated instead of rebuilt. The messenger is its reference application, not the only thing it can be.
A versioned C ABI
Callable from anything with an FFI: Node, Python, Go, Rust, C#, Swift. A context-owned, semantically versioned ABI 2.0 is in development and is the surface we will commit to.
Callback-driven
Register handlers for messages, room state, call events and decoded media. The engine drives your application; it does not impose an event loop of its own.
You own storage and UI
Keys, history and presentation stay inside your product, under your compliance and retention rules rather than ours.
Your infrastructure
The relay is a small Go service in one container. Run it in your own network and jurisdiction; it keeps no user accounts to migrate or subpoena.
/* ABI 2.0 (in development): the shape of an integration, names may still change */ mirage_abi_version(…); /* major must match, minor at least yours */ mirage_context_create(…); /* one engine per context, several per process */ mirage_set_callbacks(…); /* messages, rooms, calls, media */ mirage_room_create(…); mirage_room_send_text(…); mirage_room_leave(…); mirage_context_stop(…); mirage_context_destroy(…);
Realistic fits: clinics and advisory apps, field and industrial tools that must keep working on difficult networks, hardware that needs an encrypted control channel, and products that would rather not hand their users' conversations to a messaging vendor.
Want to walk through the design?
We're happy to go through the protocol, the code or an integration with your engineers. Design partners get early access and a real say in what comes next.
Book a technical walkthroughOr read about the design-partner programme.