Introduction
DeadLetter
DeadLetter is a censorship-resistant, identity-driven dead-drop communication system.
It allows senders to deliver encrypted messages to recipients without knowing where they are, while recipients can safely fetch, decrypt and acknowledge messages using strong cryptography.
No accounts. No metadata. No central trust beyond cryptographic verification.
Core properties
- Zero knowledge backend — servers never see plaintext
- Post-quantum-safe hybrid crypto design (X25519 + Ed25519 + AES-GCM + HKDF)
- Device trust pinning
- ESP32 hardware dead-drop nodes
- CLI inbox with offline decryption
- Fully self-hostable
Quickstart
1. Download device setup tool
This tool is used to configure the physical ESP32 DeadLetter device with the correct Backend and Registry gateway addresses over USB.
macOS
curl http://YOUR_VPS_IP/dist/deadletter-setup-mac -o deadletter-setup
chmod +x deadletter-setup
sudo mv deadletter-setup /usr/local/bin/deadletter-setup
Linux
curl http://YOUR_VPS_IP/dist/deadletter-setup-linux -o deadletter-setup
chmod +x deadletter-setup
sudo mv deadletter-setup /usr/local/bin/deadletter-setup
Windows (PowerShell)
curl http://YOUR_VPS_IP/dist/deadletter-setup-windows.exe -OutFile deadletter-setup.exe
Run from the same directory:
.\deadletter-setup.exe --help
2. Verify checksum
Always verify the downloaded binary before running it.
Download checksums:
curl http://YOUR_VPS_IP/dist/SHA256SUMS.txt -o SHA256SUMS.txt
Verify:
sha256sum -c SHA256SUMS.txt 2>/dev/null | grep deadletter-setup
On macOS if sha256sum is missing:
shasum -a 256 deadletter-setup
Compare the hash with the value inside SHA256SUMS.txt.
3. Configure the ESP32 device
Plug in your DeadLetter ESP32 over USB and run:
deadletter-setup --registry REGISTRY_IP:8081 --backend BACKEND_IP:8080
This permanently stores the gateway addresses inside the device.
Architecture
Overview of the Dead Letter architecture.
Architecture
DeadLetter is a privacy-first, offline-capable dead-drop communication system built around three principles:
- No persistent sender identity
- Receiver-controlled trust
- Zero-knowledge infrastructure
The system is composed of four independent layers:
1. Device Layer (ESP32 DeadDrop)
The ESP32 device is responsible for:
- Generating ephemeral encryption keys
- Encrypting payloads using X25519 + AES-256-GCM
- Never storing plaintext
- Never learning recipient private keys
Each device only knows:
- Its own device ID
- The gateway addresses (registry + backend)
- A pinned root trust fingerprint
2. Registry Layer (Trust Authority)
The registry acts as a minimal Certificate Authority.
Responsibilities:
- Issues signed certificates binding:
handleencryption public keykeyIdexpiry
- Signs certificates with a long-lived root signing key
- Publishes the root public key at
/keys/
Security properties:
- Registry never sees messages
- Compromise cannot decrypt past traffic
- Clients verify all certificates locally
3. Backend Layer (Dead Drop Store)
The backend is a dumb encrypted mailbox.
Responsibilities:
- Accepts encrypted envelopes via
POST /post - Returns encrypted envelopes via
GET /inbox/:handle - Deletes messages only after:
- Signed delete request
- Verified against registry certificate
Security properties:
- Backend cannot decrypt messages
- Backend cannot forge delete requests
- Messages expire automatically (TTL purge)
4. Client CLI Layer
The CLI is the user's trust anchor.
Responsibilities:
- Fetches and verifies registry root key
- Verifies receiver certificates
- Derives shared secrets locally
- Decrypts messages
- Signs delete acknowledgements
The CLI never transmits private keys.
Message Flow
ESP32 Sender → Encrypt → Backend Store → CLI Fetch → Decrypt → Signed Delete
Threat Model Summary
| Component | Can Decrypt | Can Impersonate | Can Delete |
|---|---|---|---|
| ESP32 | No | No | No |
| Registry | No | Yes (future) | No |
| Backend | No | No | No |
| CLI | Yes | Yes (local) | Yes |
Compromising any single layer is insufficient to break privacy.
Security Model
DeadLetter is designed as a trust-minimized dead drop messaging system.
No single component can read or forge messages, even if fully compromised.
This document describes the threat model, trust boundaries, and cryptographic guarantees.
Threat Model
We assume the following adversaries may exist:
| Actor | Capabilities |
|---|---|
| Malicious Backend Operator | Full read/write access to message storage and transport |
| Malicious Registry Operator | Can attempt to forge certificates |
| Network Attacker | Full MITM on all traffic |
| Device Thief | Gains physical access to ESP32 or client computer |
| Passive Observer | Can log IPs, timing, and metadata |
We do not assume any server is honest.
Core Guarantees
DeadLetter provides:
- End‑to‑end encryption — only sender and receiver can read messages
- Forward secrecy — compromise of long‑term keys does not reveal past messages
- No backend trust — backend stores opaque blobs only
- No registry trust — all registry data is signed and verified by clients
- Offline‑safe messaging — sender and receiver never need to be online simultaneously
Key Types
Each identity owns two keypairs:
| Key | Algorithm | Purpose |
|---|---|---|
| Encryption key | X25519 | Message encryption |
| Signing key | Ed25519 | Certificate verification & deletion authorization |
Certificate Chain
- Registry has a root signing key.
- Each user certificate is signed by this root.
- Clients verify:
root_pub -> verifies -> user_cert -> authorizes -> encryption_pub
The backend never sees private keys.
Message Encryption
Every message uses a new ephemeral keypair.
sender_ephemeral_priv + receiver_enc_pub
↓ X25519
shared_secret
↓ HKDF
session_key
↓ AES‑256‑GCM
ciphertext
Salt:
SHA256(ephemeral_pub || receiver_pub)
Message Deletion Authorization
When a user deletes a message, the client signs:
"<message_id>:delete"
using its Ed25519 signing key.
The backend verifies this signature against the registry certificate.
No one else can delete your messages.
Metadata Leakage
DeadLetter intentionally minimizes metadata but cannot eliminate:
- IP addresses (unless Tor used)
- Timing correlations
- Message sizes
Trust Boundaries
| Component | Trusted? |
|---|---|
| Sender device | Yes |
| Receiver device | Yes |
| Backend | No |
| Registry | No |
| Network | No |
Security Properties Summary
| Property | Status |
|---|---|
| Confidentiality | ✅ End‑to‑end |
| Integrity | ✅ Signed + authenticated |
| Authenticity | ✅ Certificate chain |
| Forward Secrecy | ✅ Ephemeral keys |
| Server Compromise Safety | ✅ Backend & registry are untrusted |
| Offline Delivery | ✅ Dead drop model |
DeadLetter is built on the assumption that servers will eventually be hostile.
Your security lives entirely on your devices — and that is exactly the point.
Trust Model
DeadLetter is built around cryptographic trust pinning instead of central user accounts.
No part of the system ever requires trusting the backend with plaintext data.
Root of Trust
The system has a single Root Signing Key.
This key:
- Lives only on the registry server
- Signs all user certificates
- Is fetched once by clients and ESP devices
- Is pinned locally and never auto‑updated
If the root key changes, all clients must explicitly clear trust.
This makes silent compromise of the registry impossible.
Certificate Structure
Each user has a certificate:
{
"handle": "alice",
"encPub": "<base64 X25519 public key>",
"keyId": "<random>",
"expiresAt": 1735689600
}
This certificate is signed by the root key using Ed25519.
Certificate Verification Flow
When a client or ESP wants to send or read messages:
- Fetch
/keys/from registry - Receive:
root_pub_b64- signing algorithm
- Pin the root key locally
- Fetch
/keys/<handle> - Verify signature using pinned root key
- Reject any unsigned or invalid certificate
ESP Trust Pinning
Each ESP device stores:
| Item | Stored In |
|---|---|
| Root public key fingerprint | NVS |
| Registry gateway address | NVS |
| Backend gateway address | NVS |
This prevents:
- Evil‑twin registry attacks
- Gateway redirection
- Silent man‑in‑the‑middle attacks
Fingerprint Verification
On first boot the ESP prints:
Root Trust Fingerprint:
aa:bb:cc:dd:ee:ff:00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:00:11:22:33:44:55:66:77:88:99
This value is unique per installation.
Never trust the example above – always compare the fingerprint shown on your own ESP device with the one printed by your CLI.
This must be compared with the fingerprint shown by the CLI.
Only if both match is trust established.
Where this happens
This process is fully described in:
→ Device Setup → CLI Usage → Trust Commands
The sender ESP device is always the authority for fingerprint verification.
Trust Revocation
Trust is only reset when:
- The user runs the setup CLI again
- Or manually clears NVS
- Or reflashes firmware
There is no remote trust reset endpoint.
Why This Matters
This model guarantees:
| Threat | Outcome |
|---|---|
| Backend compromised | Cannot read messages |
| Registry compromised | Cannot forge certificates |
| Network MITM | Detected by pinned root key |
| Rogue ESP | Cannot impersonate other devices |
Trust Summary
DeadLetter does not trust infrastructure.
It only trusts:
- Locally pinned keys
- Cryptographic verification
- Physical access when provisioning
Device Setup
How to set up and configure devices.
Device Setup
This guide walks you through setting up a physical DeadLetter ESP32 device (the sender).
The device is responsible for encrypting messages and pinning the registry trust anchor.
You will configure the device using the deadletter-setup CLI tool over USB.
Requirements
- ESP32 DeadLetter device flashed with firmware
- USB cable
- One computer running macOS, Linux, or Windows
- Access to your Registry and Backend gateway IP addresses
Step 1 — Download Device Setup Tool
Download the correct binary for your platform from the DeadLetter download server:
macOS
curl http://YOUR_VPS_IP/dist/deadletter-setup-mac -o deadletter-setup
chmod +x deadletter-setup
sudo mv deadletter-setup /usr/local/bin/
Linux
curl http://YOUR_VPS_IP/dist/deadletter-setup-linux -o deadletter-setup
chmod +x deadletter-setup
sudo mv deadletter-setup /usr/local/bin/
Windows (PowerShell)
curl http://YOUR_VPS_IP/dist/deadletter-setup-windows.exe -o deadletter-setup.exe
Step 2 — Verify Checksum (Critical)
Always verify the checksum before executing:
curl http://YOUR_VPS_IP/dist/SHA256SUMS.txt
sha256sum deadletter-setup
Ensure the output matches the entry in SHA256SUMS.txt.
Step 3 — Plug In the ESP Device
Connect the ESP32 to your computer using USB.
The setup tool will automatically detect available serial ports.
Step 4 — Configure Registry & Backend
Run:
deadletter-setup --registry REGISTRY_IP:PORT --backend BACKEND_IP:PORT
Example:
deadletter-setup --registry 192.168.1.200:8081 --backend 192.168.1.200:8080
If multiple devices are connected you will be prompted to select one.
Step 5 — Trust Fingerprint Ceremony
During setup the device prints a Root Trust Fingerprint:
Root Trust Fingerprint:
aa:bb:cc:dd:ee:ff:...
This fingerprint must be verified out-of-band with the receiver.
Do NOT copy fingerprints from documentation or screenshots.
This is the moment your trust anchor is pinned permanently.
Step 6 — Completion
If successful you will see:
✓ Configuration Complete - Device Rebooting
The device is now ready to securely send DeadLetter messages.
Troubleshooting
| Issue | Fix |
|---|---|
| No device detected | Check USB cable and drivers |
| Permission denied (Linux) | sudo usermod -a -G dialout $USER then relogin |
| Wrong gateway saved | Re-run setup tool |
| Fingerprint mismatch | Abort setup immediately |
The ESP device is now cryptographically bound to your registry. All trust flows from this step.
CLI Usage (Receiver)
The DeadLetter receiver CLI is a native Rust binary.
It runs without any runtime dependencies once installed.
⚠️ Prebuilt binaries are not yet published. They will be made available for download soon. Until then, this document describes the intended workflow.
Overview
The receiver CLI is used by the message recipient to:
- Initialize a local identity (keys + handle)
- Fetch and verify trust anchors from the registry
- Download, decrypt and manage inbox messages
- Save attached files
- Acknowledge and delete messages securely
All cryptography and trust validation happens locally.
Planned Installation
Soon you will be able to download a platform-specific binary:
| Platform | Binary |
|---|---|
| macOS | deadletter |
| Linux | deadletter |
| Windows | deadletter.exe |
Example (macOS / Linux):
curl http://<download-host>/deadletter -o deadletter
chmod +x deadletter
sudo mv deadletter /usr/local/bin/
Initialize Identity
Each receiver has a handle and a local keypair stored in:
~/.deadletter/<handle>/
Create a new identity:
deadletter init alice
This generates:
enc_private.key– encryption private keyenc_public.key– encryption public keysig_private.key– signing key
These never leave your machine.
Fetch Inbox
To fetch and decrypt messages:
deadletter inbox alice
Flow:
- Fetch trust anchor from registry
/keys/ - Fetch and verify certificate for
alice - Fetch inbox from backend
- Display message list
- Decrypt selected message locally
- Optionally delete message from server
Trust & Verification
The receiver can manually inspect and verify the current trust anchor (root signing key) used by the registry.
deadletter trust
This command will:
- Fetch the current root signing key from the registry
/keys/ - Display the root public key fingerprint
- Warn if the trust anchor has changed since last verification
Example output (example only — your fingerprint will be different):
Root Trust Fingerprint: aa:bb:cc:dd:ee:ff:00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:00:11:22:33:44:55:66:77:88:99
⚠️ This fingerprint is only an example.
The sender (ESP32 device) and the receiver (CLI user) must exchange and verify their displayed fingerprints using a trusted out‑of‑band channel (e.g. in person, secure chat, phone call).
Never trust the registry blindly — always confirm that both sides see the same root trust fingerprint to detect any possible man‑in‑the‑middle attack.
Reading Messages
After running inbox you will see:
📬 2 message(s)
1 770c6253-a430-4e 2025-12-30T12:24:37Z
2 a6120b86-57e3-43 2025-12-30T12:18:01Z
Select msg (q=quit):
Select a number to decrypt.
File Attachments
If the message contains a file payload, it will be written to the current working directory:
📎 File saved: ./report.jpg (image/jpeg, 34812 bytes)
Deleting Messages
After viewing a message:
Delete message? (y/n):
If confirmed, the CLI signs a deletion request using your signing key and sends it to the backend.
Deletion is idempotent – even if the backend already purged the message, your local inbox will stay consistent.
Important Notes
- The CLI is offline-safe – all cryptography happens locally.
- No plaintext ever reaches the backend.
- Messages cannot be decrypted without the recipient private key.
- This tool is strictly for receivers, not ESP devices.
Coming Soon
- Public binary downloads
- Checksum verification instructions
- Automated installers for macOS, Linux and Windows
Backend Service
The Backend is the message transport layer in DeadLetter. It never sees plaintext and has no ability to decrypt messages. Its sole responsibility is to accept encrypted envelopes from senders and deliver them to the correct recipient inbox.
Responsibilities
The backend handles:
- Receiving encrypted message envelopes from ESP32 sender devices
- Storing envelopes temporarily until fetched by the receiver
- Serving inbox contents to receivers
- Verifying delete acknowledgements
- Purging expired messages automatically
At no point does the backend have access to message plaintext or encryption keys.
Data Model
Each stored message is an envelope:
| Field | Description |
|---|---|
id | Unique message identifier |
ephemeral_pub | Sender ephemeral X25519 public key (base64) |
iv | AES‑GCM IV (base64) |
ciphertext | Encrypted payload (base64) |
tag | AES‑GCM authentication tag (base64) |
receivedAt | Server timestamp |
Endpoints
Fetch inbox
GET /inbox/{handle}
Returns all encrypted envelopes for the specified handle.
Delete message (acknowledge)
POST /ack-delete
Body:
{
"id": "<message_id>",
"handle": "<handle>",
"sig": "<base64-ed25519-signature>"
}
The backend verifies the signature against the recipient certificate before deleting the message.
Security Properties
- Backend never holds encryption keys.
- Messages are end‑to‑end encrypted between ESP sender and CLI receiver.
- Delete requests are signed with the receiver’s private signing key.
- Backend is safe to run over Tor or clearnet.
Expiration
Messages are stored with a TTL and are purged automatically even if never fetched.
This ensures the backend never becomes a long‑term message archive.
Threat Model
The backend is assumed to be:
- Curious
- Potentially compromised
- Observable
Even in this worst case:
- No plaintext is exposed
- Tampering is detected via AES‑GCM
- Deletions require cryptographic proof of ownership
Summary
The backend is a blind courier.
It moves opaque cryptographic envelopes between parties, but cannot read, forge, or impersonate any participant.
Registry Service
The Registry is the cryptographic root of trust in DeadLetter.
It does not store messages. Its sole responsibility is to bind human‑readable handles to encryption public keys and to sign those bindings using a long‑lived root signing key.
All clients (CLI + ESP32 firmware) trust the registry only via its root public key fingerprint.
Responsibilities
The registry provides three core guarantees:
-
Handle ownership
- A handle (e.g.
alice) is bound to exactly one encryption public key. - Only the holder of the corresponding signing key may update or revoke it.
- A handle (e.g.
-
Certificate issuance
- The registry signs a canonical certificate object:
{ "handle": "alice", "encPub": "<base64-x25519-public-key>", "keyId": "v1", "expiresAt": 1735689600 } -
Trust anchor distribution
- The root signing public key is distributed through
/keys/ - Clients verify all certificates using this root key.
- The root signing public key is distributed through
Endpoints
| Method | Path | Description |
|---|---|---|
| GET | /keys/ | Returns the current root signing public key |
| GET | /keys/:handle | Returns signed certificate for a handle |
| POST | /register | Register or rotate a handle certificate |
| POST | /challenge | Request registration challenge |
| POST | /verify | Verify signed challenge response |
Root Key Endpoint
GET /keys/
Example response:
{
"root_pub_b64": "MCowBQYDK2VwAyEA7qZ9...",
"algorithm": "ed25519",
"issued_at": 1735611120
}
Clients store the fingerprint of this key locally and warn if it ever changes.
Certificate Fetch
GET /keys/alice
Example:
{
"cert": {
"handle": "alice",
"encPub": "YzN4...",
"keyId": "v1",
"expiresAt": 1735689600
},
"sig": "b64-ed25519-signature"
}
The CLI verifies:
SHA256( canonical(cert) ) verified by root public key
Registration Flow
- Client requests a challenge
- Registry returns a random nonce
- Client signs nonce using their signing key
- Registry verifies and stores the new certificate
- Registry persists to
registry.store.json
This prevents unauthorized handle hijacking.
Local Persistence
The registry stores all handle mappings in:
dl/packages/registry/registry.store.json
The file is loaded at startup and written atomically after each update.
Security Properties
| Threat | Mitigation |
|---|---|
| MITM during registration | Challenge‑response signing |
| Registry database tampering | Root‑signed certificates invalidate forged entries |
| Handle hijack | Only holder of signing key can rotate |
| Silent key swap | CLI warns if trust anchor fingerprint changes |
What the Registry Never Sees
- Plaintext messages
- Encryption private keys
- Message metadata beyond handle → pubkey mapping
The registry cannot read, decrypt, or enumerate any inbox contents.
Operational Advice
- Back up
registry.store.jsonfrequently. - Never rotate the root key unless all clients are prepared to re‑trust.
- Host registry behind Tor gateway if anonymity is required.
Tor Gateway
The Tor Gateway is a small always-on computer (for example a Raspberry Pi or VPS) that bridges the ESP32 devices to the Tor network.
The ESP32 itself cannot run Tor. It does not have enough memory, storage, or a full TCP/IP stack capable of handling Tor’s circuit building, directory fetches, and encryption layers.
For this reason every DeadLetter device must talk to a trusted gateway which then forwards traffic into Tor.
Why a Gateway is Required
The ESP32 firmware is intentionally minimal:
- No Tor binary on the device
- No SOCKS implementation
- No directory consensus parsing
- No onion address resolution
Instead, the ESP32 only performs:
- Key generation
- Encryption / decryption
- Trust fingerprint display
- Plain HTTP communication to the gateway
All Tor-specific logic lives on the gateway.
Gateway Responsibilities
The gateway is responsible for:
- Running Tor in client mode
- Exposing a local HTTP service for the ESP32
- Forwarding all traffic through Tor using a SOCKS proxy
- Resolving
.onionregistry and backend addresses - Acting as a privacy boundary between the device and the public internet
The flow is:
ESP32 → Gateway (HTTP) → Tor (SOCKS) → Onion Registry / Backend
Example Setup: Raspberry Pi Gateway
A Raspberry Pi running Debian is sufficient.
Install Tor:
sudo apt update
sudo apt install tor socat
Verify Tor is running:
sudo ss -lntp | grep 9050
You should see Tor listening on 127.0.0.1:9050.
Forwarding via socat
The gateway exposes a local port that forwards traffic into Tor:
socat TCP-LISTEN:8080,reuseaddr,fork SOCKS4A:127.0.0.1:REGISTRY_ONION.onion:80,socksport=9050
This means:
- ESP32 connects to
http://gateway-ip:8080 - socat forwards the traffic through Tor to the hidden service
You can run multiple listeners for backend and registry.
Current Limitation
At the moment the gateway hardcodes the onion addresses inside the systemd service files.
This means:
- Onion URLs cannot yet be updated dynamically
- Changing backend or registry onion requires editing the gateway config and restarting services
This is a known limitation and will be improved in future versions.
Trust Boundary
The gateway is trusted for transport only, not for message confidentiality.
- Messages are always end-to-end encrypted between sender and receiver
- The gateway cannot decrypt message payloads
- Fingerprint verification still happens on the ESP32 device itself
If a gateway is compromised, the attacker can observe traffic patterns but cannot read message contents.
Troubleshooting
This page lists the most common problems when running DeadLetter and how to fix them.
Device setup tool does not detect my ESP32
Symptoms
No devices detected- The setup tool lists zero serial ports.
Fix
- Make sure the ESP32 is connected with a data USB cable.
- Install USB‑UART drivers:
- macOS / Linux: CP210x or CH340 drivers if your board requires them.
- Windows: Install Silicon Labs CP210x or CH340 drivers.
- On Linux, ensure you are in the
dialoutgroup:
sudo usermod -a -G dialout $USER
# log out and back in
Permission denied opening serial port
Symptoms
Permission denied: /dev/ttyUSB0
Fix
sudo usermod -a -G dialout $USER
Log out and log back in.
Setup tool says “Registry gateway is NOT reachable”
Symptoms
- Device prints:
Registry gateway is NOT reachable!
Cause The ESP32 cannot reach the configured Tor gateway on the local network.
Fix
- Ensure the Tor gateway (e.g. Raspberry Pi) is powered on.
- Verify gateway IP + port are correct:
deadletter-setup -r <gateway-ip:port> -b <gateway-ip:port> - Check the gateway firewall allows inbound connections.
Device keeps printing Registry gateway: UNSET
Cause Gateway IP was not saved to NVS or device rebooted before storing.
Fix
Run device setup again and wait until it prints Registry gateway set before disconnecting USB.
Inbox shows messages but decryption fails
Symptoms
Decryption failed - invalid key or corrupted data
Fix
- Make sure the correct identity directory exists:
~/.deadletter/<handle>/ - Ensure
enc_private.keyandenc_public.keybelong to the same identity used by the sender. - Run:
and verify the fingerprint matches what the sender sees on their ESP32.deadletter trust
File downloads are saved as unreadable garbage
Cause Base64 payload corrupted or wrong key used.
Fix
- Ensure sender and receiver verified the same trust fingerprint.
- Retry
deadletter inbox <handle>.
deadletter trust reports trust anchor changed
Symptoms
- Warning about trust anchor mismatch.
Fix Stop immediately. Compare fingerprints with your sender through a secure channel. Only proceed if you are 100% sure the new fingerprint is legitimate.
deadletter command not found
Cause Binary is not in PATH.
Fix
sudo mv deadletter /usr/local/bin/
chmod +x /usr/local/bin/deadletter
Setup tool times out waiting for device ready
Fix
- Unplug and reconnect the ESP32.
- Wait until it prints
Type 'help'before running setup again.
Still broken?
Enable verbose output:
deadletter-setup -v -r <gateway> -b <backend>
Attach the output when asking for support.