Skip to content
Backend and platform engineer in Nairobi. Everything on this page is running now, and every figure is measured by the site itself. Saturday 10 October 2026 · Nairobi
The LighthouseThe engineering portfolio of Martin Omwenga
Projects / GhostChat / Architecture

How GhostChat is put together

Everything secret happens in the two browsers. The server in between checks, stores and relays messages it can't read, can't write and can't quietly reorder, and publishes everyone's public keys in a log it can't rewrite without being caught.

1. The sender's browser
2. The server and its database (never holds a private key)
3. The recipient's browser

The parts and the walk-through are listed in full below.

Every part

Relay

Takes signed envelopes over a WebSocket and delivers them to whoever is in the conversation.

Socket.IO, with Redis between instances so a message sent to one instance reaches a recipient connected to another. It sees who talks to whom and when. It never sees what was said.

Code backend/src/realtime.js backend/src/auth.js

Chain keeper

Accepts a message only if it is properly signed and links onto the newest message in its conversation.

When two people send at the same moment only one can be next. The other is answered with a conflict and the message it missed, and its browser links onto that, signs again and resends without the person doing anything.

Code backend/src/services/chain.js

More about this in the story →

Walk-through, step by step

One message, from the sender to the recipient

Follow a single message from one person's keyboard to another's screen. At each stage, notice what the server is given: enough to check and deliver the message, never enough to read or fake it.

  1. Keys that never leftPrivate keys → Lock and sign

    The sender's private keys are already unlocked in their browser. They were made there, and the server has only ever held a locked copy.

  2. Lock, link, sign, sendLock and sign → Relay

    The browser encrypts the text with a fresh key, seals that key to the recipient, adds the hash of the message before it, signs the lot and sends the envelope.

  3. Is it next in line?Relay → Chain keeper

    The server checks the signature and that the envelope links onto the newest message. If the other person sent at the same moment and got in first, the sender's browser is told, links onto that message, signs again and resends.

  4. Stored, still lockedChain keeper → Stored messages

    The envelope is saved as it arrived. The server holds the locked text and the sealed keys and can open neither.

  5. DeliveredRelay → Check and open

    The envelope goes to the recipient's browser over its open connection, even if that connection is to a different server instance.

  6. Who signed this?Check and open → Whose key is this?

    Before decrypting, the recipient's browser checks the signature and the link to the message before, and asks whether the signing key is really the sender's.

  7. Proof, not the server's wordWhose key is this? → Key log

    The sender's key history is checked link by link and against the public key log. Only then is the message opened and shown, with a shield beside it.

How we know: Tests send two messages at the same moment and check one lands and the other is answered with what it missed (backend/tests/realtime.test.js); browser tests tamper with a stored message and check the conversation shows the break (e2e/tests/tampering.spec.js).