Security model
What me3 is designed to protect, how, and — just as important — what it can’t protect. The full threat model is in the project’s SECURITY.md; this is the readable version.
The honest limits first
- No independent audit yet. me3’s code has been tested (including known-answer tests against the published test vectors and an independent re-implementation of our key steps), but no professional security reviewer has examined it. Don’t rely on it for high-risk use — journalism under threat, abuse situations, anything where being read could put someone in danger — until that review has happened.
- Web delivery means trusting our server. Your browser downloads me3’s code from me3.org.uk. If our server were hacked or forced to, it could send changed code that leaks your keys. Installed apps from app stores share this kind of risk with the store, but a website can change between visits. me3 keeps a cached copy on your phone and checks each script against a fixed fingerprint, which helps, but it isn’t a full answer.
- Metadata is reduced, not hidden. See what the server can see.
- Your device is the vault. Someone who has your unlocked phone, or malware on it, can read your messages. JavaScript in a browser also can’t promise the constant-time behaviour that native crypto code aims for.
What me3 aims to protect
- The content of your messages, from our server, your internet provider and anyone in between.
- Past messages, if your keys are stolen later (forward secrecy), and future messages once the conversation has moved on (the ratchet “heals”).
- Who you talk to, from someone who later gets a copy of our database: it holds only ciphertext under mailbox numbers that change every six hours.
- Your messages on the device, from someone who has the phone but not your passphrase (they are encrypted at rest).
The cryptography
me3 doesn’t invent cryptography. Every primitive comes from libsodium.js (version 0.8.4, built from libsodium 1.0.22), and the protocols follow Signal’s published specifications:
| Job | What me3 uses | Specification |
|---|---|---|
| First contact (key agreement) | X3DH with X25519, SHA-256, a signed prekey and a one-time prekey in every invite | X3DH, revision 1 (2016) |
| Prekey signature | XEdDSA (XEd25519) — signatures with the X25519 identity key | XEdDSA, revision 1 (2016) |
| Message keys | Double Ratchet (section 3), HKDF-SHA256 root chain, HMAC-SHA256 message chains | Double Ratchet, revision 4 (2025) |
| Message encryption | XChaCha20-Poly1305 (an AEAD); key and nonce derived from each one-time message key with HKDF | Allowed by Double Ratchet section 3.1 |
| Passphrase | Argon2id (3 passes, 64 MiB) unlocks a random key that encrypts everything stored on the device | libsodium crypto_pwhash |
| Contact codes, mailbox numbers | HMAC-SHA256 of a per-pair secret derived from the X3DH result | me3’s own glue — see below |
Where me3 makes its own choices, they’re built only from the pieces above and written down for reviewers: the invite format (the X3DH “prekey bundle” travels in the QR code rather than being fetched from a server), an outer encrypted envelope that hides ratchet headers from the server, the rotating mailbox and code derivations, padding to fixed sizes, and a safety number modelled on Signal’s (but not compatible with it). Signal’s optional header-encryption variant and its newer post-quantum ratchet are not implemented.
What the server can see
- IP addresses and timing. Every request comes from an IP address at a particular time. If someone watched the server live, they could notice that one IP posts to a mailbox and another collects from it soon after, and link the two. Rotating mailboxes stop a stored database from revealing a social graph, but they don’t stop live observation. Using a VPN or Tor helps.
- Sizes. Messages are padded to a few standard sizes (up to 256 bytes, 1 KB, 4 KB, 16 KB, 64 KB), so only the size band shows.
- How many mailboxes you check. Your phone adds made-up mailbox numbers to every check so the count doesn’t reveal exactly how many friends you have.
- What it never gets: keys, names, phone numbers, emails, contact lists or message contents.
Our database doesn’t record IP addresses. Rate limiting uses a keyed hash of the address that is replaced every day and deleted within two hours. The web host’s own access logs are separate — see the privacy notice.
Checking the code
The libsodium.js file we serve is byte-for-byte the official dist/browsers-sumo/sodium.js from release 0.8.4, checked against the npm registry’s signed hash and the GitHub release tree. Its SHA-256 is:
5d6f10ef3e530b45536ee0aa40fa54a46220cc78984df6992a9d48e9a741e8f7
The app page loads every script with a Subresource Integrity hash, so the browser refuses a file that doesn’t match. The tests that check our code against the published test vectors ship with the project.
Found a problem?
Please email hello@me3.org.uk with “security” in the subject. We’ll reply, fix what we can and credit you if you’d like.
Sources
- Signal: The X3DH Key Agreement Protocol (rev. 1, 2016-11-04)
- Signal: The Double Ratchet Algorithm (rev. 4, 2025-11-04)
- Signal: The XEdDSA and VXEdDSA Signature Schemes (rev. 1, 2016-10-20)
- libsodium.js (source code and releases) · libsodium documentation
Checked 20 October 2026.