A federated end-to-end encrypted messaging protocol delivered over DNS
Project description
DNS Mesh Protocol
End-to-end encrypted messaging with no central server, no app store, no gatekeeper — delivered over DNS, on the relays and infrastructure the internet already runs on.
What this is
DMP is an open, free messaging protocol built around one job: deliver a private message from one person to another, even when the network actively tries to stop you. Not a company, not a product — a specification anyone can implement, plus a permissively-licensed reference implementation.
Think of it as the email of private messaging: nobody owns email, nobody can shut it off. DMP aims for the same durability, with end-to-end encryption baked in from the start.
Why it exists
Today's "private" messaging looks like this:
You ↔ One company ↔ The other person
That company is a single point of failure. They can be subpoenaed, blocked at a national border, acquired, hacked, or simply shut down. End-to-end encryption protects message content — none of it protects you from the company in the middle disappearing or being made to.
DMP removes the company from the middle.
Who this is for
- Journalists and sources in countries where the usual apps are blocked, monitored, or unsafe to have on a phone.
- Organizations that want a backup channel that keeps working when their primary vendor is down, regulated, or subpoenaed.
- Privacy advocates who don't want a phone number, an account, or any company in between two people who agreed to talk.
- Builders who need a small, auditable messaging primitive they can embed into their own products with no licensing or vendor lock-in.
If DNS works on your network, DMP works on your network. That's the whole pitch.
→ 5-minute training deck · → Try the public node · → Self-host
Status: alpha, pre-external-audit. Full federation (client fan-out + union reader + node-side anti-entropy + manifest gossip), bootstrap discovery, key rotation + revocation (M5.4), multi-tenant node auth with per-user publish tokens (M5.5), cross-zone receive
- first-message claim layer (M8 / 0.4.x), and the formal protocol spec are shipped. The remaining path to
v1.0is a certification backlog: external cryptographic audit, mobile/web clients, standalone CLI binaries, traffic-analysis hardening.Don't route secrets through DMP until the external cryptographic audit is done. The codebase has had ~40+ rounds of automated review across all milestones, but automated review is not a substitute for professional cryptanalysis. A human auditor catches a different class of bugs (crypto composition errors, side-channel weaknesses, protocol-level attacks, implementation-vs-spec drift) that no amount of LLM-driven pattern matching will find. The audit is a post-beta deliverable; until then treat DMP as experimental for confidentiality-critical traffic.
How it works (30-second version)
Instead of sending messages through a central server you have to trust,
DNS Mesh Protocol encrypts each message end-to-end and writes it as
DNS records on a node you choose. The recipient looks those records
up the same way any computer looks up google.com. If DNS works on
your network, DMP works on your network.
- One docker container is a complete, deployable node.
- One command-line tool covers identity, key management, send, and receive.
- One protocol composes Ed25519 signatures, X25519 ECDH, ChaCha20-Poly1305, Argon2id passphrase derivation, one-time prekeys for forward secrecy, and Reed-Solomon erasure coding for chunk loss.
Try it on dnsmesh.io
The public reference node at dnsmesh.io is open: no operator approval,
no email, just a CLI command. It advertises open registration, the
M8 claim-provider role, and acts as the canonical bootstrap seed for
the federated network.
pipx install dnsmesh
dnsmesh init alice --domain <your-zone> --endpoint dnsmesh.io
dnsmesh tsig register --node dnsmesh.io # one HTTPS hop, mints a TSIG key
dnsmesh identity publish # DNS UPDATE + TSIG, no HTTPS
After M9 the protocol is DNS both directions. The only HTTPS exchange
is the one-time tsig register step that gets you a TSIG key bound
to your zone. Every subsequent record write — identity, prekeys,
mailbox slots, chunks, claim publishes — goes over RFC 2136 DNS
UPDATE signed under that key. Reads are plain DNS TXT queries.
Curious what a node is currently publishing:
dig @dnsmesh.io _dnsmesh-heartbeat.dnsmesh.io TXT +short.
Self-host
git clone https://github.com/oscarvalenzuelab/DNSMeshProtocol.git
cd DNSMeshProtocol
# Install the CLI
pip install -e .
# Build and run the node
docker build -t dnsmesh-node:latest .
docker run -d -p 5353:5353/udp -p 8053:8053/tcp \
-v dnsmesh-data:/var/lib/dmp dnsmesh-node:latest
# Set up an identity and send your first message
export DMP_PASSPHRASE=a-strong-passphrase
dnsmesh init alice --domain mesh.local \
--endpoint http://127.0.0.1:8053 \
--dns-host 127.0.0.1 --dns-port 5353
dnsmesh identity publish
For a production Ubuntu deployment with auto-TLS and systemd:
curl -fsSL https://raw.githubusercontent.com/oscarvalenzuelab/DNSMeshProtocol/main/deploy/native-ubuntu/install.sh \
| sudo DMP_NODE_HOSTNAME=dmp.example.com bash
Then delegate a DNS subdomain to the node so clients on the public
internet can resolve records served by the node. Without this step,
dig @1.1.1.1 id-XXX.example.com TXT returns nothing even though the
node has the record — the registrar's nameservers don't know about
DMP records. The fix is one NS record at the registrar plus one env
var on the node:
At the registrar (DigitalOcean / Cloudflare / etc.):
mesh.example.com. IN NS example.com.
On the node:
DMP_DOMAIN=mesh.example.com
sudo systemctl restart dnsmesh-node
Full walk-through with screenshots and verification steps: Deployment → DNS subdomain delegation.
Once your node is up, point the CLI at it via the public hostname:
dnsmesh init alice@dmp.example.com # auto-splits user + zone
dnsmesh tsig register --node dmp.example.com # one-shot HTTPS, mints TSIG key
dnsmesh identity publish # DNS UPDATE under that key
The tsig register flow walks the same Ed25519 challenge/confirm
ceremony the legacy bearer-token path uses, but it returns a TSIG
key (RFC 8945 HMAC-SHA256) that's persisted in your config and
auto-attached to every subsequent record publish. The CLI rebuilds
its writer accordingly: _DnsUpdateWriter → port 53 → your home
node. After register, no HTTPS leaves your machine for the
protocol's normal flow.
Full walk-through with two users: Getting Started.
What you get
- End-to-end encryption with forward secrecy for prekey-consumed messages. Past messages stay safe if long-term keys leak later; see Forward secrecy and prekeys.
- Signed sender authentication. With pinned contacts, unknown signers are dropped. Without, receive runs in trust-on-first-use.
- Zone-anchored identity addresses.
alice@alice.example.com. Squatting requires compromising DNS for the zone. - Cross-chunk erasure coding. Loss of up to
n-kchunks still reconstructs the message. - Resolver resilience.
ResolverPoolfans queries across multiple upstream resolvers with oracle-based demotion on lying resolvers.dnsmesh resolvers discoverauto-builds the pool from public resolvers. - Multi-node federation (client AND node side).
FanoutWriterpublishes to every cluster node (quorum =ceil(N/2));UnionReaderreads the union with dedup. Nodes run pull-based anti-entropy against their peers so a node that was offline catches up when it rejoins. A 3-nodedocker-compose.cluster.ymlis a checked-in operator starting point; see Clustered deployment. - Key rotation + revocation.
dnsmesh identity rotate --experimentalpublishes a co-signedRotationRecord(new key ← old key) plus an optional self-signedRevocationRecordwhen--reason compromiseor--reason lost_keyis set. Rotation-aware contacts chain-walk from their pinned key to the current head automatically; a revocation aborts trust on any path that touches the revoked key. Seedocs/protocol/rotation.md. - DNS-only federation (M9). Inter-node coordination — heartbeat
publishes at
_dnsmesh-heartbeat.<zone>, transitive discovery via_dnsmesh-seen.<zone>, claim-provider discovery, even cross-zone first-contact claim publishes — all goes over plain DNS, no HTTP between independent nodes. The legacy/v1/heartbeat,/v1/nodes/seen,/v1/claim/publish, and/v1/infoHTTP routes are gone. Cluster anti-entropy (/v1/sync/digest+/v1/sync/pull) is the documented HA-only exception inside one operator's trust domain — see the boundary doc. - DNS UPDATE + TSIG writes (M9).
DMP_DNS_UPDATE_ENABLED=1switches publish from HTTP/v1/records/*to RFC 2136 UPDATE signed with RFC 8945 TSIG. Per-user keys minted viaPOST /v1/registration/tsig-confirm(the one-shot HTTPS registration step) carry a per-user pattern scope —slot-*.mb-*.<zone>,chunk-*-*.<zone>,id-<sha256(subject)>.<zone>— so registered users can't overwrite each other's records on shared zones. Operator caps (max_ttl,max_value_bytes,max_values_per_name) apply to UPDATE writes the same way they do to HTTP publishes. - Anti-takeover registration.
mint_for_subjectdoes the existence check + key insert atomically under one lock, so two concurrenttsig-confirmcalls for the same subject under different signing keys can't both succeed. - Zero-config onboarding via bootstrap discovery. Given just
alice@example.com,dnsmesh bootstrap discover me@my-domain --auto-pinresolves the cluster, verifies the two-hop trust chain (bootstrap signer → cluster operator), and cuts over atomically. - Persistent, size-bounded node. sqlite storage, TTL cleanup, token-bucket rate limits, bounded concurrency, Prometheus metrics.
- Docker-first deploy.
docker composefor dev, Caddy + Let's Encrypt overlay for production. - Formal protocol spec. Wire format, routing, flows, and threat model at docs/protocol/. Every constant cross-verified against the source; designed so a third party can build an interoperable client.
Running a node
# Dev
docker compose up -d
# Production (real hostname, auto TLS)
export DMP_NODE_HOSTNAME=dmp.example.com
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
See Deployment. Before shipping to production, read the operator hardening guide, a mandatory checklist covering TLS, token hygiene, operator signing-key handling, DNS zone hygiene, file permissions, and network exposure.
Project layout
dmp/
├── core/ Protocol primitives: crypto, chunking, erasure,
│ manifests, identity, prekeys, DNS encoding
├── network/ DNSRecordWriter / DNSRecordReader abstraction +
│ Cloudflare, Route53, BIND, in-memory backends
├── storage/ SqliteMailboxStore: persistent TTL-aware record store
├── server/ DMPNode: UDP DNS server, HTTP API, cleanup worker,
│ metrics, rate limiting, structured logging
├── client/ DMPClient: send / receive / identity / prekeys
└── cli.py `dnsmesh` command-line interface
docs/ Jekyll docs site (Just the Docs theme, GitHub Pages)
Includes docs/protocol/ for the formal wire spec
tests/ 1050+ unit, integration, fuzz, and docker-in-the-loop tests
Includes tests/fuzz/ (hypothesis property tests) and
tests/test_vectors.py (golden interop test vectors).
Dockerfile, docker-compose.yml, docker-compose.prod.yml, Caddyfile
Tests
pip install -e ".[dev]"
pytest # ~1050 tests (incl. fuzz)
docker build -t dnsmesh-node:latest .
pytest tests/test_docker_integration.py # 6 docker tests (incl. M5.4 rotation)
pytest tests/test_compose_cluster.py # 3 compose-cluster tests
python examples/docker_e2e_demo.py # single-node send/receive + rotation demo
python examples/cluster_e2e_demo.py # 3-node federated e2e demo
Production installs use the hashed lockfile:
pip install --require-hashes -r requirements.lock
pip install . --no-deps
Not a good fit for
- Real-time chat (seconds-to-minutes latency by design)
- File transfer or media payloads
- Anonymity from traffic analysis (DMP hides content, not metadata)
Contributing
See CONTRIBUTING.md. Every PR that changes behavior
needs a test. Security-sensitive changes in dmp/core/crypto.py,
dmp/core/manifest.py, dmp/core/prekeys.py, or the AEAD AAD surface
get an extra round of review.
License
AGPL-3.0. If you host DMP as a service you must publish your source changes.
Author
Oscar Valenzuela · AlkamoD
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file dnsmesh-0.5.1.tar.gz.
File metadata
- Download URL: dnsmesh-0.5.1.tar.gz
- Upload date:
- Size: 516.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a59c23a2c7034e36975d444c2d17166ef775d082bcf3a5fa6c5610eedaf46428
|
|
| MD5 |
f5e2f361b74c2e04694b665e2106323b
|
|
| BLAKE2b-256 |
fce33164f051b50048d06a528f26c491094181eab6397eb0a64398a6eff15f17
|
Provenance
The following attestation bundles were made for dnsmesh-0.5.1.tar.gz:
Publisher:
publish-pypi.yml on oscarvalenzuelab/DNSMeshProtocol
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
dnsmesh-0.5.1.tar.gz -
Subject digest:
a59c23a2c7034e36975d444c2d17166ef775d082bcf3a5fa6c5610eedaf46428 - Sigstore transparency entry: 1388490476
- Sigstore integration time:
-
Permalink:
oscarvalenzuelab/DNSMeshProtocol@6cd9a42626f13ed255bd8477f1c7dbf7874b7957 -
Branch / Tag:
refs/tags/sdk-v0.5.1 - Owner: https://github.com/oscarvalenzuelab
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-pypi.yml@6cd9a42626f13ed255bd8477f1c7dbf7874b7957 -
Trigger Event:
push
-
Statement type:
File details
Details for the file dnsmesh-0.5.1-py3-none-any.whl.
File metadata
- Download URL: dnsmesh-0.5.1-py3-none-any.whl
- Upload date:
- Size: 350.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6cd13062eb5f560d14900b7792c5e3823b566e1cb68ffd5b07130e2714a40529
|
|
| MD5 |
031fe26ca2907ec89e1831a39ae39adc
|
|
| BLAKE2b-256 |
7ba02f9517a3fbcaf079562f8a96b3d8d14d2b80f29fef11079bfeada4fb0a43
|
Provenance
The following attestation bundles were made for dnsmesh-0.5.1-py3-none-any.whl:
Publisher:
publish-pypi.yml on oscarvalenzuelab/DNSMeshProtocol
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
dnsmesh-0.5.1-py3-none-any.whl -
Subject digest:
6cd13062eb5f560d14900b7792c5e3823b566e1cb68ffd5b07130e2714a40529 - Sigstore transparency entry: 1388490574
- Sigstore integration time:
-
Permalink:
oscarvalenzuelab/DNSMeshProtocol@6cd9a42626f13ed255bd8477f1c7dbf7874b7957 -
Branch / Tag:
refs/tags/sdk-v0.5.1 - Owner: https://github.com/oscarvalenzuelab
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-pypi.yml@6cd9a42626f13ed255bd8477f1c7dbf7874b7957 -
Trigger Event:
push
-
Statement type: