How Midnight Messenger is built: one map, three journeys

We drew a map of everything Midnight Messenger runs, and everything it talks to, each box placed where it really lives. Then we recorded three short videos of it. In each one, a dot follows a single request across the map, one line at a time, with a caption that says what happens on that line.

This post walks through the map and the three videos. It is not a mockup. It is the service our iOS and Android beta apps use today, on Midnight Preprod.

What the map shows

Read it left to right.

People and devices. The iPhone and Android apps. Your keys live on your device, and only there. The apps talk to our services only through midnightmessenger.app addresses.

Cloudflare. Every midnightmessenger.app name answers here. Cloudflare ends HTTPS, redirects plain http, refuses old TLS, and DNSSEC signs the zone. Then it passes each request down a tunnel to our servers. The tunnel connects outward from our side, so no port is open. Cloudflare sees visitors’ addresses and timing. It never sees message content.

Our servers. Separate virtual machines, each behind its own firewall.

  • One runs the tunnel, the beta gate and the relay. It is the only machine that can reach the others.
  • One runs the directory, the push gateway and the signed app config. It accepts connections only from the first.
  • The sponsor has a machine of its own. Only the first machine can reach it, and it reaches no private network.
  • The public proof server has a machine of its own too, locked down the same way.

Outside services. Midnight Preprod, where registrations land. Apple and Firebase, which deliver alerts. And a relay run by an independent operator from our operator kit. It sees only encrypted traffic, and we check its health every minute. Today the beta apps send messages through our own relay.

A small key marks each box that holds secrets. Those secrets never leave that machine.

Video 1: Sign up

Signup needs no phone number, no email, no wallet and no tokens. During the beta, it needs a beta code.

  1. A new person opens the app and enters their beta code.
  2. The request goes down the tunnel to our beta gate. The gate checks the code and gives the app a pass for the proof server.
  3. The app asks the public proof server for its identity proof, carrying the pass. A Cloudflare Worker checks the pass, and the proof server builds the zero-knowledge proof.
  4. The beta gate passes the fee request on to the sponsor.
  5. The sponsor proves the fee payment with its own proof server, which nobody else can reach.
  6. The registration goes on the Midnight chain, with the fee paid in DUST by the sponsor.
  7. The app registers a hash of its username with the directory. The directory checks the chain first.

What lands on chain is a commitment and a public key. No username, no phone number, nothing that says who you are.

One honest note on step 3. A proof is built from the secrets it protects, so the proof server sees those inputs while it works. That is why we run it ourselves, on its own machine, and never hand it to outside operators.

Video 2: Send a message

Seven steps, from Alice’s phone to Bob’s.

  1. Alice’s phone encrypts the message with MLS (RFC 9420). Only ciphertext leaves the phone.
  2. Cloudflare passes the connection down the tunnel. It sees an address and timing, never content.
  3. The relay gets the encrypted envelope for the chat’s room. It cannot read it.
  4. Bob is offline, so the relay holds the ciphertext, for up to 7 days.
  5. The relay hands our push gateway a sealed wake ticket. The relay never learns Bob’s push token.
  6. The gateway opens the ticket and asks Apple to show “New message”. Nothing else. No sender, no text.
  7. Bob’s phone wakes, connects, collects the message and confirms it. The relay deletes it.

On Android it works the same way, with Firebase in Apple’s place.

Video 3: Look up a name

  1. Alice types Bob’s username and his 6-digit contact code. Her phone hashes both first.
  2. The lookup goes down the tunnel.
  3. The directory gets a hash and a code slot. Never the plain name, never the code.
  4. If the code is right, it finds Bob’s keys under that slot. If not, nothing comes back.

Why a code at all? Hashing hides the list of names, but not a name that is easy to guess. With a contact code, guessing someone’s name is not enough to start a chat with them. That is the default, and you can change who may add you in Settings.

What the map does not hide

Every design has trade-offs. These are ours.

  • Cloudflare sees visitors’ addresses and timing, never message content.
  • The relay sees ciphertext, sizes and timing. Holding messages for someone who is offline means it learns that two keys exchanged something at some time. It cannot read what.
  • Apple and Firebase learn when a phone gets an alert. The alert says only “New message”.
  • Registrations on Midnight are public and permanent.
  • Usernames are hashed, so exact lookup works, but a common name can be guessed.

Try it, or ask us

The iOS and Android apps are in a private beta on Midnight Preprod. Not mainnet, and not in the app stores yet.

Closed beta opens next week, with 20 spots on iPhone and 20 on Android. We’ll share how to join in a separate post. Until then, if you have a question about any box or line on the map, join us on Discord in #midnight-messenger, or ask right here. We’re happy to go deeper on any part of it.