Private messaging over a real mix network with hybrid post-quantum encryption.
NobleChat encrypts every message end to end in the browser and routes it through a Sphinx style mix network, so the server never sees message content and cannot tell a real message from cover traffic. Keys are generated on your device and never leave it.
This is a working project, not a finished product. The individual cryptographic
primitives come from the audited @noble libraries, but the way they are
composed here (the Sphinx layer, the packet and mailbox format, the account and
multi device model) has not had an independent security audit. Do not rely on it
to protect real secrets until that review has happened. See
SECURITY.md for how to report issues.
New to the ideas behind it? The running app has a plain-language, animated
explainer at /info.html.
- Hybrid post-quantum key exchange (X25519 + ML-KEM-768) and hybrid signatures (Ed25519 + ML-DSA-65).
- A real mix network: layered mix nodes and providers, Poisson mixing, constant size packets, and cover traffic. Each node runs as its own process and only ever learns its own hop.
- A zero-knowledge gateway: it only handles opaque onion packets, a mailbox id, public contact cards, a password hash, and an opaque (client encrypted) contacts blob.
- Accounts with password login, multi device fan out, and durable offline delivery backed by PostgreSQL: messages wait in an encrypted mailbox until the recipient reconnects.
- 1:1 and group chats, encrypted file and image attachments, auto-deleting images, message replies, emoji reactions and delete-for-everyone.
- Voice and video calls over WebRTC, with the signalling carried end to end through the same channel.
- A switchable transport: the built-in mix network, or the public Nym mixnet via an optional nym-client sidecar. The gateway falls back to the internal network if the sidecar is not reachable, and restores nym automatically once it reconnects.
- An admin panel for announcements, maintenance mode, moderation, a server event log, runtime stats, mix node health and the transport switch.
- Installable clients: a PWA (desktop, Android, iOS home screen) plus native desktop and Android builds produced by CI.
browser client --ws--> gateway --http--> mix node L1 -> L2 -> L3 -> provider
(crypto) (accounts, (each is a separate process,
mailboxes, holds only its own key)
web, admin) |
delivers back to the gateway,
which pushes to the recipient
packages/cryptohybrid PQ primitives, AEAD, KDF.packages/sphinxfixed size onion packet format.packages/netmix router, directory, wire serialization, client helpers.packages/protocolcontent and envelope framing.apps/serverthe gateway (HTTP, WebSocket, accounts, admin, PostgreSQL).apps/nodea single mix node process.apps/webthe browser client and PWA.clients/desktopElectron shell,clients/mobileCapacitor shell.
Requires Docker and Docker Compose.
cp .env.example .env
# edit .env and set strong values for every secret
docker compose up -d --buildThe gateway comes up on http://127.0.0.1:8790 (loopback only; set HTTP_PORT in
.env if that port is taken). Put a reverse proxy (for example Caddy or nginx)
in front of it for TLS. If your proxy lives in a different compose project,
attach the noblechat service to its network with a docker-compose.override.yml
instead of exposing the port. The compose file also starts the mix nodes, the
providers, and a PostgreSQL database.
To route over the public Nym mixnet instead of the internal one, set
NYM_CLIENT_URL=ws://nym-client:1977 in .env. That starts the bundled
nym-client sidecar, which registers an identity on the Nym network (its data
lives in a named volume, so the address is stable across restarts). Once the
admin panel shows the sidecar connected, flip the transport there. Clients then
load the Nym web client on demand and send over the mixnet; leaving
NYM_CLIENT_URL empty keeps everything on the internal network.
Requires Node.js 20 or newer.
npm install
npm test # unit tests for crypto, sphinx, and the router
npm run build:web # bundle the browser clientnpm run smoke drives a RUNNING deployment end to end like a real browser
client (register, subscribe, send through the mix, verify the sender
signature, upload/download a file). Start the compose stack first, then:
SMOKE_URL=http://localhost:8790 npm run smokeOpen the deployment URL in any modern browser. To install as an app:
- Desktop (Chrome or Edge): use the install icon in the address bar.
- Android (Chrome): menu, then "Add to Home screen".
- iPhone (Safari): Share, then "Add to Home Screen".
Tagged releases also build native desktop installers (AppImage, dmg, exe) and an Android apk and attach them to the GitHub release. iOS runs as a home-screen web app; a native App Store build would need an Apple developer account.
Please report vulnerabilities privately. See SECURITY.md.
Contributions are welcome. See CONTRIBUTING.md and the code of conduct.
AGPL-3.0. See LICENSE.