The design, in enough detail to judge it. What the cryptography does, what the infrastructure can and cannot see, and where the engine can be licensed into another product.
Every messenger protects against something and leaves something else exposed. Mirage is no exception, and a design is only judgeable once both halves are on the table.
✓ Holds up against
✕ Does not protect against
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 requires a server to exist.
Identity is split in two 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. Devices are peers: any of them can publish a new roster version, which revokes stolen hardware and isolates it at the mesh level rather than only in the interface.
Verification is done the way it should be — by comparing a fingerprint out of band. The interface shows it in short groups so it can be read aloud.
A conversation starts with an X3DH key agreement over X25519, which lets the first message be sent even if the other side is offline. From there the session runs a Double Ratchet: every message advances the key material, so compromising a key today does not open yesterday's traffic, and the session heals itself once a compromised key falls out of use.
Calls run their own ratchet, separate from text, because media keys rotate
on a different rhythm than messages. Content is sealed with
ChaCha20-Poly1305 or AES-256-GCM — authenticated
encryption in both cases, so tampering is detected rather than merely
unlikely.
NAT is the reason most peer-to-peer software quietly falls back to a server. Mirage races several strategies at once and takes whichever wins, so a direct path is used whenever one exists.
If the address is already in the local peer-exchange cache, a QUIC connection opens immediately and no server is involved at all.
Nearby desktop anchor nodes coordinate both sides to punch out through their own NATs, without the central relay taking part.
If the mesh cannot arrange it, the relay coordinates a conventional UDP rendezvous — it times the two sides, it does not carry the session.
Where UDP is blocked outright, the client degrades to TCP on port 443 through the relay. Slower, but it connects on networks that drop everything else.
The relay exists for discovery, NAT traversal and holding messages for people who are offline. It is stateless about users, and the parts it carries are sealed before they reach it. This is the honest inventory.
The right-hand column is the part worth arguing about, and it is why the design pushes so hard toward direct connections: what the relay never carries, it can never be asked to hand over.
When the recipient is offline, the message is encrypted into an envelope and handed to anchor nodes to hold. The envelope is authenticated, so a holder cannot alter it, and it is opaque, so a holder cannot read it. It is delivered when the recipient reappears, and the holder learns only that something was waiting.
The same idea covers history backup: the client encrypts locally and uploads opaque blobs. Recovery material is wrapped with a key derived from your PIN, so the server holds a container it has no way of opening.
Everything runs over QUIC on UDP/443, with TLS 1.3 encapsulated 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 shapes its own packet stream so that the statistical fingerprints normally used to classify a messenger do not separate it from ordinary media traffic. The mechanism is deliberately not documented here. Publishing the specifics would mostly serve the people building classifiers, and unlike the cryptography — whose strength must never depend on secrecy — this part gains nothing from being described in public.
Text conversations, group chats and voice conferences are the same object internally: 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 that one slow peer cannot stall everyone else.
Group routing is handled by whichever participant is best placed to do it, with the relay as a fallback for anyone behind a strict NAT. Both kinds of router are blind: they forward sealed packets without holding the keys to any of them. If the routing peer disappears, the room migrates to another one without tearing down the encryption session.
Group key management is moving to MLS (RFC 9420) through a
Rust bridge around openmls. That work is in progress and not
yet the shipping path — today's groups use a sender-key ratchet.
All of the above lives in a C++20 core. The interface on top of it is replaceable; the desktop client is simply the first thing to sit there.
Event dispatch across a C ABI, media bridge for capture and playback.
Rooms, sessions, presence, sealed backup and directory lookups.
Identity, ratchets, group keys and payload encryption.
Blind routing between peers, NAT traversal and mesh control.
QUIC multiplexing over ngtcp2, traffic shaping and TCP fallback.
The core never touches the filesystem. No database, no config files, no writes of any kind — it is an in-memory network engine that emits events. Everything persistent belongs to the application embedding it. That constraint exists for recoverability, and it happens to make the engine straightforward to host inside somebody else's product.
Mirage is proprietary and owned outright, with no copyleft dependencies in the way. If you need secure peer-to-peer communication inside something you are already building, the core can be licensed and integrated rather than rebuilt — the messenger is the reference application for it, not the only thing it can be.
/* the shape of it — abbreviated */ uint32_t mirage_get_api_version(void); bool mirage_init(const char* relay_ip, uint16_t relay_port, const char* my_id, const char* pub_key_hex, const char* priv_key_hex); bool mirage_room_join(const char* room_id); uint64_t mirage_room_send_text(const char* room_id, const char* text); void mirage_set_room_text_callback(mirage_on_room_text_cb cb); void mirage_set_call_event_callback(mirage_on_call_event_cb cb); void mirage_set_video_callback(mirage_on_video_cb cb);
Realistic fits: privacy-sensitive industry apps, field and logistics tools that must keep working on hostile networks, hardware that needs an encrypted control channel, and products that would rather not hand their users' conversations to a third-party messaging vendor.