How Mirage works.

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.

Threat model

Start with what it does not do.

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

  • An ISP or network operator reading message or call content
  • The relay operator reading anything it carries, including us
  • A passive observer learning who talks to whom from our servers
  • A stolen device staying able to read new traffic once revoked
  • Past traffic being decrypted after a key is later compromised

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 material and your PIN
  • Anyone observing that you are connected at all, if they watch your local link
No external audit has been done. The design below is described so it can be reviewed, not because it has been. Treat it as a protocol worth examining rather than a protocol already vouched for.

Identity

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 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.

Sessions

Keys move forward and never back.

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.

Getting connected

Four routes, raced in parallel.

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.

01

Straight to the peer

If the address is already in the local peer-exchange cache, a QUIC connection opens immediately and no server is involved at all.

02

Hole punching through the mesh

Nearby desktop anchor nodes coordinate both sides to punch out through their own NATs, without the central relay taking part.

03

Hole punching through the relay

If the mesh cannot arrange it, the relay coordinates a conventional UDP rendezvous — it times the two sides, it does not carry the session.

04

Fall back to 443

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.

Infrastructure

What the relay can and cannot see.

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.

Message and call contentnever
Private keysnever
Your contact listnever
Backed-up historysealed blobs only
That an address connectedyes
Size and timing of relayed packetsyes
That an envelope is waiting for someoneyes

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.

Offline delivery

Sealed envelopes, held by people who cannot open them.

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.

Traffic shape

Designed not to stand out.

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.

Rooms and groups

One abstraction for chats, groups and calls.

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.

The engine

Five layers, no disk.

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.

API & I/O

Layer 5

Event dispatch across a C ABI, media bridge for capture and playback.

Domain & state

Layer 4

Rooms, sessions, presence, sealed backup and directory lookups.

Cryptography

Layer 3

Identity, ratchets, group keys and payload encryption.

Overlay routing

Layer 2

Blind routing between peers, NAT traversal and mesh control.

Transport

Layer 1

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.

Licensing

The engine can be licensed into your 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.

A stable C ABI

A versioned extern "C" surface — currently API version 3 — with an explicit version call, so you can pin and detect. Callable from anything with an FFI: Node, Python, Go, Rust, C#, Swift.

Callback-driven, not polled

Register handlers for messages, room state, call events and decoded media frames. The engine drives your application; it does not ask for a thread or an event loop of its own design.

You own storage and UI

Because the core writes nothing, key storage, history and presentation stay inside your product, under your compliance and retention rules rather than ours.

Bring your own infrastructure

The relay is a small stateless Go service. Run it yourself, in your own network and jurisdiction; it holds no user state to migrate or subpoena.

/* 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.

Worth saying plainly before you plan around it: the engine is at closed alpha, no external security audit has been carried out, and there is no mobile build yet. If you are evaluating it for production, that is the conversation to have first — and I would rather have it early than sell past it.

Get in touch

Happy to walk through the design, the code, or an integration.

Email hello@miragetalk.com