callwhite is a messenger with no server in the message path. Messages are written to, and read from, addresses derived from key material only the two parties share. What the relay is left holding is a fixed-size, opaque blob at an address it cannot derive — linkable neither to a sender nor to a reader. One-to-one and group chats, media included.
Encryption produces messages nobody can read. callwhite goes one step further and removes who talked to whom, when, and how often from the path as well. That is the half that scales, gets stored, and gets joined: reading one person's messages takes a warrant; reading everyone's contact graph takes one query.
What you get
Not a protocol paper. An app you install, on two platforms, with the same Rust core compiled into both.
- Pairing
- An invite link or a QR code scanned face to face. Both sides derive the same safety number independently; if the numbers differ, someone is in the middle, and the app will not send until the number is verified.
- Chats
- One-to-one and group. Membership is managed: invite, admit, remove, leave, disband. Pairing with someone you met in a group is one tap — they accept, no link or QR needed. Groups carry a name, every member picks their own, and notifications fire only when you are @mentioned. Removing someone publishes a new epoch under a fresh group secret; the removed member cannot read anything written under it and is told over their own channel. The admin names an heir who takes the group over if the admin's device is lost. The group's lifetime rule is one sentence, shown on screen: a group not synced for three days is deleted.
- Delivery
- One-to-one polls every two seconds; a message does not wait for the end of the round, it lands with the wave it arrives in. With a group screen open the poll is held at the relay and returns the moment a new message is written. On the phone, consecutive sends are queued with the order on screen; a stalled Tor circuit is replaced. Round time is measured and shown on screen.
- Media
- Photos and voice notes, one-to-one and in groups. Split into the same fixed-size blobs as text; every blob looks alike on the wire, and all an observer sees is how many of them moved.
- Censorship
- If Tor is blocked the app tries a direct connection, the embedded obfs4 bridges and Snowflake in turn, and remembers what worked; there is no bridge line to hunt for. Your own bridge (obfs4, WebTunnel, Snowflake) goes under Advanced; the transport ships in the package.
- Background
- By default the app polls while it is open and the session locks when it closes. On Android, background delivery is optional and capped at eight hours: turn it on and the phone keeps receiving in your pocket; when the cap is reached the session locks itself.
- Disguise
- The app can present itself as something ordinary — for the glance over your shoulder.
- Vault
- A single encrypted file whose size never changes, whatever it holds. Key derivation is memory-hard: guessing costs an attacker RAM, not a dictionary pass.
- Three passwords
- Your own opens your account. A second opens a separate decoy account that looks ordinary and lightly used. A third destroys the vault. Which is which cannot be told from the outside: any password that is not one of yours fails identically, with no “close” answer.
- Deputy
- Name a contact who can wipe your account remotely. The command travels over the channel you already share and takes effect the next time the app is opened and polls; until then it waits on the relay for seven days.
- Screen protection
- On Android the app runs with
FLAG_SECURE: no screenshots, and the window the recents preview is blanked. - Device health check
- A screen that reports what the build actually is: signing certificate fingerprint, version, whether Tor is up, whether the vault wrote successfully. “Did the update really land?” is answered here.
- Platforms
- Android and Windows desktop. The same Rust core in both, through a generated bridge rather than a rewrite — the phone cannot reimplement a primitive by accident.
Mechanism
What an observer sees
The read side runs on a fixed budget: a polling round asks the relay for a fixed 64 addresses of 4096 bytes each — two parties, one request each — and keeps going at randomly jittered intervals for as long as the vault is open, whether or not anyone is talking. Real slots are padded with cover addresses derived from the account secret; intersecting two consecutive rounds does not strip the padding, and the budget does not change with how many contacts you have. On the read side an idle chat and a busy one have the same shape; the write side goes out when you send, and sending is visible as sending.
Under seizure
Most of a messenger's threat model assumes the device stays with you. This one does not. The interesting case is the one where someone is holding your phone and watching your hands.
There is no recovery path, deliberately: a way back in is a way an attacker can walk too. The vault is a single fixed-size file; how much it holds — how many contacts, how many groups, whether you use it at all — cannot be read from outside.
Remote wipe works through a contact you named in advance, over the channel you already share, and takes effect the next time the app is opened and polls. To an outside observer a wrong password and the decoy password are indistinguishable.
Threat model
callwhite is built against a local network observer, an ISP, the relay operator, and someone who takes the device. None of the four learns what the messages say or what the vault holds, and the first three learn nothing about who is talking to whom.
The relay operator is the separate case: it sees neither content, nor identity, nor what is read — but it does see which address was written and when it was read. Addresses roll with every message and are not tied to an account; what the operator is left with is timing.
The transport is Tor, embedded and shipped; there is nothing to install. The client mirrors: it writes the same blob to every relay it knows and reads from all of them, so a relay going away does not take the conversation with it.
Wipe on Android deletes a key from the system keystore: what opens the vault is gone, independent of whether old blocks can be reached. On desktop the vault, key and counter files are overwritten before deletion, and Tor's and the view's working directories are removed.
Beyond the laws of physics
Part of security is a negotiation with physics: latency, bandwidth, battery. We measured the strictest setting — thirty-second rounds, every slot in turn, cover everywhere — and a group message took three to five minutes to land. To keep messaging instant we made the following choices.
- Global observer
- Against an observer that sees the whole network at once the only protection is a mixnet: messages are held and shuffled, and latency goes from seconds to minutes. It was not added for instant messaging; it can be added as a separate layer if there is demand.
- The write side
- The read side is covered; the write side goes out when you send. Covering writes too means constant fake writes from every device while nobody is talking — each with its proof of work: battery and bandwidth. That is the price of hiding the moment you send; today we do not charge it.
- An open group screen
- With a group screen open, the content ring is held at the relay and returns the moment a message is written; with the screen closed, polling is constant-rate and independent of membership. The trade: while the screen is open the relay sees the same ring polled back to back — not who, but that someone is active in that group. We chose speed; when the screen closes the pattern returns.
- The group ring
- A group's messages sit in a shared ring that rolls when membership changes; one write reaches every member. The relay sees the ring — not who reads it, but that it is alive and roughly how often it is written to. Trading the per-message address of one-to-one for a stable ring is the price of reaching many members with a single write.
- Bandwidth
- Constant-rate cover is paid in bandwidth: at a two-second round, roughly half a gigabyte an hour. The only way down is a sparser round, which means a slower message. We chose speed; on a phone we recommend Wi-Fi.
- Background
- An operating system either stops a background poller or lets it drain the battery. So the default is to lock when the app closes; optional background delivery is capped at eight hours and states its cost — data, charge, a persistent notification — up front.
Numbers
- 4096bytes, always Every blob on the wire is the same size. A query that finds nothing returns 4096 bytes of decoy; even occupancy is not an oracle.
- 64reads per round, always A polling round asks the relay for a fixed 64 addresses — real slots padded with decoys derived from the account secret; about 262 kB per round, in two requests. The budget does not change with how many contacts you have.
- 2gates of proof of work per write A 22-bit SHA-256 pre-gate, then memory-hard Argon2id at 64 MiB. The pre-gate costs the writer millions of hashes per write and the relay microseconds; Argon2 is 125 ms per call (measured on a desktop) and the difficulty expects two. Flooding a relay costs the attacker CPU, not the defender bandwidth.
-
1233automated tests
Rust core, desktop client, browser layer and Android. Clippy clean with
-D warnings. Closed code findings carry a regression test named after them wherever one can be written. - 9internal review rounds A finding only counts once it survives an attempt to refute it: one round confirmed 43 of 53 candidates, another 100 of 123; the last two ran with an independent refutation pass and 300 mutants actually executed. Every critical finding is closed; the high findings that remain open are deliberate choices, stated above: cover on the write side and the group ring.
- 0servers that know No component in the message path keeps routing state. There is no contact graph to seize, subpoena, leak or sell.
Download
Version 0.8.19. Licensed AGPL-3.0; the source is held for exclusive transfer.
Both packages work out of the box. Tor is inside; there is nothing else to install and no account to open.
-
Android
callwhite-0.8.19.apk
Download — 19.5 MB, arm64 only
SHA-256
2c021f905f8534d6e234f3d8ebf22f925634371b13839ceca1623b98c4a452d1
minisign signature: callwhite-0.8.19.apk.minisig — verify with
minisign -Vm callwhite-0.8.19.apk -P RWRpQTPa0GuDQEDI4CTSJxBJNQlbz2br8hmlWJO9hp0c6z4DNgpqUIF0
(public key callwhite.pub; the SHA-256 is the site's word, the signature is the publisher's — both must hold)
Signed, so Android enforces the same signature on every update. Verify before installing, outside the app withapksigner verify --print-certs; the certificate SHA-256 must match:
9A:E0:1A:B9:E5:C1:70:89:9F:D4:9D:BA:D5:5A:7E:95:3D:62:37:75:FD:31:89:FA:09:92:6A:A0:CA:6F:63:3B
If it differs, the build did not come from us — do not use it. The in-app Device health check screen only repeats what the installed package says about itself; a fake build can show the "right" value there too, so it is a reminder, not a check. This package carriesarm64-v8aonly, which covers every current phone; it will not install on an x86_64 emulator. -
Windows
callwhite-0.8.19-windows.zip
Download — 12.9 MB, .zip
SHA-256
e35821d7bc64d7eb9c10bc0821716ee6ffd6c477c66db0240ee6de2c9d0f2d33
minisign signature: callwhite-0.8.19-windows.zip.minisig — verify with
minisign -Vm callwhite-0.8.19-windows.zip -P RWRpQTPa0GuDQEDI4CTSJxBJNQlbz2br8hmlWJO9hp0c6z4DNgpqUIF0
(public key callwhite.pub; the SHA-256 is the site's word, the signature is the publisher's — both must hold)
No installer, nothing else to fetch. Right-click the zip → Extract All, open thecallwhitefolder, runcallwhite BASLAT.cmd. The binaries sit inprogram\, with Tor atprogram\Tor\tor.exe.
Windows certutil -hashfile callwhite-0.8.19.apk SHA256 Linux sha256sum callwhite-0.8.19.apk macOS shasum -a 256 callwhite-0.8.19.apk
A public relay address ships inside the binary; install and pair. You can add your own relay from Settings — it is added alongside the built-in one, and messages are written to both and read from both.
Every release is built from the stable Rust channel with a locked dependency graph
(Cargo.lock, --locked) and a dependency inventory. The APK is
signed and its certificate fingerprint is above; the SHA-256 of both packages is
there.
Release notes
0.8.22 — in preparation
Not yet published; the packages above are 0.8.19. What it brings: a message queue on the phone, a process-wide concurrency cap and replacement of stalled Tor circuits, round time measured and shown; with a group screen open, polling held at the relay and latency down to seconds; member names persistent, sender name above the bubble; an admin heir; the group lifetime rule on screen (a group not synced for three days is deleted); vault v19. The closures of the two-auditor round (red team + 300 mutants) are in these releases. The vault and group formats changed, so both devices update together.
0.8.19
Simpler invite flow on the phone: the "Wait for reply" and "(90 s grace)" buttons are gone. Once you create an invite the app polls for the reply by itself, on any screen; if you leave the app to send the link you get 90 seconds to come back — automatically, no button. A reply that arrives while you are elsewhere shows as a banner on the contact list. Optional background reception now has a hard cap: at most 8 hours in the background, then the session locks itself. The desktop wipe screens now state the BitLocker limit: without disk encryption, remnants of deleted files can be read from a removed disk.
0.8.18
Zero-configuration censorship circumvention: if Tor is blocked the app tries direct → built-in obfs4 bridges → Snowflake on its own and remembers what worked; no bridge lines to hunt for (the bridge field moved under Advanced). In-group friend adding: one tap on the Members screen, the other side accepts or declines, no link or QR needed. Background reception is optional and off by default: enable it under Settings → Advanced; it uses more data and battery, shows a persistent notification and keeps the session unlocked in the background (the cost is stated in the setting). Every parser that takes attacker-controlled bytes now has a byte-fuzzer gate.
0.8.17
Bridges on the phone: enter obfs4 or WebTunnel lines under Settings → Bridges; the transport (lyrebird 0.8.1) ships inside the package, and if lines are set but the transport is missing, Tor refuses to start — no silent fallback. On desktop the WebView2 background-network switches are now set from the environment (measured; Edge telemetry to Microsoft is the browser component's own limit, page-level egress is closed). Packages are now also minisign-signed (public key callwhite.pub).
0.8.16
Fixes from the wide red-team round plus four new features. The relay's write gate (PoW) was corrected and polling now goes out as one request (BATCH_GET): both devices must move to this version and the relay must be updated. New: everyone picks their own name in a group; group notifications only when you are @mentioned; polling every 10 seconds while a group screen is open; on desktop, bridge lines (obfs4 / WebTunnel) for networks that block Tor. Fixes: a channel no longer goes permanently deaf after a long silence; sealing v2 (a rolled-back vault cannot repeat a nonce); Tor circuits isolated per contact and per group; the group roster chain is pinned (fork detection); group control-box reads ride inside the cover party; a half-published membership change can be retried on desktop; a junk reply to an invite link can no longer jam pairing; the number of undelivered messages is reported; the clipboard is cleared when the grace period ends; Android wipe overwrites files. Vault format v17 (older vaults still open).
0.8.15
Desktop can now record voice notes from the microphone (RAM only, preview first). Verifying the safety number updates the contact list at once. On the phone the delete buttons are visible and deleting shows progress. Group messages are single bubbles; the group list no longer has a leave button (it lives in settings).
0.8.14
On the phone the join-request buttons were not visible (row layout); fixed. Requests now live only in group settings (⚙), with a red marker in the chat. On desktop, the immediate re-lock right after unlocking is fixed, and naming a contact now confirms.
0.8.13
Groups can be named: the admin publishes the name and members see it at their next sync. The name travels encrypted inside the group control box; the relay only sees ciphertext. Renaming publishes a new epoch, like adding a member. Both devices must be on 0.8.13 for this release.
0.8.12
On the phone, tapping the group name opens a members screen with join requests, and ⚙ opens a separate group settings screen, like the main Settings. The requests section is visible even when empty. The "same — verify" band now closes as soon as the safety number is verified. Desktop gets two matching panels.
0.8.11
On the phone, the network status and the settings button sit at the top right of the header. On desktop, the empty QR box in the deputy panel is hidden, and the "new message" note clears as soon as you open the chat.
0.8.10
The phone now follows the desktop layout: Contacts and Groups on separate tabs, new contact at the top, most recent conversation first. Settings open with one tap. In a group, join requests sit above the chat and can be approved without opening a panel; tapping the group name shows the members. Deputy assignment and the remote-wipe command are now written into the sender's own chat, and the wipe button locks once the command is sent. On desktop, notices moved to the top, the group screen fills the chat pane, and the Lock button is gone: closing the app locks.
0.8.9
A member removed from a group now learns it over their own channel, including which group. Deleting a group admin from your contacts also clears the group records tied to them; the screen says which groups will go before you delete. Fixed: a phone freezing on receiving a deputy assignment over the channel, and an unexpected-length frame halting delivery for one conversation.
0.8.8
The password meter now caps on a dictionary word rather than a shape. Link-based pairing on the phone. On desktop, contacts and groups share one panel with the most recent conversation on top.
0.8.7
Deputy assignment now travels over the established chat and is announced to the other side; the QR path remains. Group invite and group settings screens reworked. Wire change: both devices must be on this version for deputy assignment.
0.8.6
On the phone, messages appear the moment they arrive. The group-admit error now states the actual rule: only contacts paired through that group's own invite can be admitted.
0.8.5
A message no longer waits for the end of the round; it is delivered in the wave it arrives in. Settings → Advanced → Measure last receive round shows the round's duration and wave count.
0.8.4
Nine fixes from device testing: new contacts appear immediately, join requests can be declined, voice and photos can be reviewed before sending, export is disabled in the media player.
0.8.3
Wire format changed; 0.8.2 and earlier cannot write to the new relay — update. Fixed a failed send burning address indices.
0.8.0
No sending in a one-to-one chat until the safety number is verified. The number is shown only at pairing time.
Run on real devices
Unit tests show that a function behaves; they do not show that two machines can talk. The following were driven end to end, over the real transport, between real builds.
- Android to desktop and back, over Tor, through the relay — text, photo and voice note; one-to-one and in a group.
- Two machines on different networks paired by invite link, safety number compared by hand at both ends.
- Three relays at once: the same blob mirrored to all, one killed mid-conversation, delivery continued from the rest.
- Restart persistence: both clients closed and reopened, conversations resumed from the vault; no message lost, none shown twice.
- Seizure drill: on a device holding real data, first the decoy password, then the wipe password — the aftermath checked at file level.
How it is built
Eight Rust crates, two clients, one core. The cryptography does not know it has a user interface; the interface cannot make a cryptographic decision on its own.
- Core — Rust. Ratchet, addressing, framing, group epochs, cover traffic, vault. No I/O, no platform, no UI.
- Android — Kotlin and Jetpack Compose, bound to the same core through a generated UniFFI bridge.
- Desktop — Tauri; the interface is plain HTML and CSS, the decisions are in Rust. No framework, no bundled browser engine.
- Relay — an opaque key-value store: returns decoy bytes on a miss, demands proof of work on every write, and keeps nothing that could become a contact graph.
- Verification — 1233 automated tests. Every behavioural rule is pinned by a gate, and those gates are checked by deliberately breaking production code and watching them fail — 300 mutants were actually run in the last round.
- Build — pinned toolchain, Clippy with
-D warnings, a CycloneDX dependency inventory per release, and a signing key that never leaves the machine it lives on.
Contact
Available for remote contract and full-time work on systems, cryptography and Rust. The callwhite source is available for exclusive transfer.