Encrypting a message is close to solved. What survives encryption is everything else: who contacted whom, at what time, how often, and how much. A network observer answers all of that without breaking a single ciphertext — the information lives in the routing and the timing, not the payload.
Metadata is also the part that scales. Reading one person's messages takes a warrant and an analyst. Reading everyone's contact graph takes a database query.
So I built callwhite: a messenger with no server in the message path.
The mechanism
What an observer sees
Measured, not claimed
- 4096bytes, always Every blob on the wire is the same size. A lookup miss returns 4096 bytes of decoy, so occupancy is not an oracle either.
- 3.9MB / hour, constant Cover traffic runs at a fixed rate whether you are chatting or silent. Idle and active look identical from outside.
- 98ms proof of work Memory-hard Argon2id at 64 MiB, paid on every write, so flooding a relay costs attacker CPU rather than defender bandwidth.
-
412automated tests
Across the Rust core, the desktop client and the Android client. Clippy
clean at
-D warnings. - 43red-team findings From one structured internal round. Every one closed, and every one holds a named regression test so it cannot quietly return.
- 0servers that know No component in the message path holds routing state. There is no conversation graph to seize, subpoena, leak or sell.
Why it is written down this way
Most of what I have learned on this project came from being wrong in a way that looked right. A test that passed for the wrong reason. A security gate that reported "clean" because the tool underneath it had crashed and printed nothing. A refresh mechanism that quietly produced nonce reuse, where the only thing that saved me was an unrelated defence in another component.
So the code carries the reasoning, not the mechanics — what the failure was, why the fix is shaped the way it is, and what it costs. Anyone can read what a function does. What is expensive to reconstruct is why it does it that way, and what was tried first.
Read the long version