This page is rendered directly from SPEC.md in the reference implementation — there is no second copy to drift. It is a snapshot of the reference implementation, not a frozen specification: where the two disagree, the implementation and the schemas in proto/ are authoritative.
The DMCN Protocol (DMCNP) — Core
DMCN (the Decentralized Mesh Communication Network) is a peer-to-peer, end-to-end-encrypted store-and-forward mail network where cryptographic identity replaces SMTP-style trust; the DMCN Protocol (DMCNP), specified here, is what its participants speak: every address is an Ed25519+X25519 keypair whose self-certifying record is served by the address's own domain fleet and discovered via DNS ("MX for identity" — no global DHT), and mail is hybrid-encrypted client-side and parked in recipient-designated relays' mailboxes.
This document describes the core protocol — what an independent implementation needs to
interoperate: resolve, verify, send, receive. It is a snapshot of the reference
implementation, not a frozen specification; where they disagree, the implementation and the
schemas in proto/ are authoritative. Operator/product surfaces (fleet administration,
hosting permits, provisioning, entitlements, relay-assisted client conveniences) are
extensions outside this core — see §8 for how they attach without touching it.
The layered stack#
user identity Ed25519 (sign) + X25519 (ECDH), address = local@domain
│ self-certifying IdentityRecord, served by the domain's fleet
resolution DNS `_dmcn.<domain>` (fp anchor + fleet= + seeds) → fetch signed
│ records from the domain's fleet over libp2p, verify vs the anchor
message model PlaintextMessage → SignedMessage → EncryptedEnvelope
│ per-message AES-256-GCM CEK, X25519-wrapped per recipient,
│ split header/body, padded to size-class buckets
routing operator-signed RelayHints (which relays hold my mailbox)
│
relay service /dmcn/relay/1.0.0 — STORE / FETCH / mailbox / onion / resolve
│
trust / federation Credential PKI (DNS-anchored DAR), /dmcn/join handshake
│
transport libp2p streams (no DHT — discovery is DNS-seeded)
1. Identity & addressing#
-
An identity is an Ed25519 signing key + X25519 key-exchange key; the address is
local@domain. -
It is a self-signed
IdentityRecordcarrying a monotonic owner-signedrevision. The owner self-signature covers the identity core (address, keys, created/expires, verification tier, onion flag, revision) — but notRelayHints(routing is operator-owned; see §4) and not the embedded operator credentials. -
Resolution is DNS-seeded, per-domain — there is no global directory. A resolver reads the mailbox domain's
_dmcn.<domain>DNS TXT record:_dmcn.<domain> TXT "dmcn-verification=v1; fp=<40-hex>[; fleet=<domain>][; seed=<multiaddr>]..."fp=is the trust anchor — the first 20 bytes ofSHA-256(ed25519_pub ‖ x25519_pub)of the domain's root key, uppercase hex.fleet=optionally defers hosting to another domain's nodes (discovery only — spoofing it is DoS, never forgery).seed=lists bootstrap multiaddrs, each ending/p2p/<peerID>so the transport handshake authenticates the endpoint. The resolver dials a seed, fetches the domain's DAR and theIdentityRecord(plus removal/blocklist companions), and verifies everything against the mailboxfp=. Records are self-certifying, so a wrong or hostile fleet is DoS-only, never a forgery vector; a domain is served only by its own fleet, never a global overlay a foreign majority could censor. NXDOMAIN / no_dmcnrecord means the address does not exist; transient DNS failure fails closed. -
Verification tiers: addresses register at
TierUnverified(valid but untrusted) and are raised toTierDomainDNSby a domain attestation. Verification is enforced reader-side, so unverified addresses still exist and function; trust is an upgrade, not a gate at registration.
2. Trust: the Credential PKI#
Trust is domain-anchored, not per-message. Each domain has a DomainAuthorityRecord
(DAR), served by the domain's fleet and anchored by its _dmcn DNS record. The root
delegates to issuers (carried in the DAR) under a monotone grants calculus — an issuer
cannot delegate more than it holds, scope only narrows, and grant-bearing credentials
require the grant capability to be issued.
A Credential binds a subject key to a domain along two independent axes: roles
(what the credential is) and grants (actions it may perform).
Core roles: authority / sub-authority (domain authority keys), node (a relay's
libp2p peer key — federating), bridge (an SMTP bridge peer key), client (a pure-client
peer key), address (the domain's attestation of an address↔key binding), routing (the
operator-owned RelayHints for an address). Core grants: routing, address, grant
(delegate). Further roles/grants exist as operator extensions (§8).
Issuance is authorized by grants, not by a role: any DAR-enrolled credential whose
grants cover a leaf's roles may issue it; an issued credential is not valid until signed.
Credentials verify by chaining to the DNS-anchored root (max depth 8), with a
root-signed CredentialBlockList companion record for timestamped / key-compromise
revocation. The address and routing credentials are embedded inside the
IdentityRecord and excluded from the owner self-signature — so the operator can
(re)issue them (e.g. to re-point routing) without the mailbox owner's key.
Signing convention (every signature in the protocol):
sig = Ed25519(priv, ctx ‖ deterministic_protobuf(message_without_signature))
The canonical form is deterministic protobuf serialization (stable map ordering) of the
message with its signature field cleared; ctx is a per-type NUL-terminated
domain-separation tag (e.g. dmcn-identity-self-v1\0, dmcn-dar-self-v1\0,
dmcn-credential-v1\0). One deliberate exception: the whole-message SignedMessage
signature is computed over the canonical plaintext with no context tag.
3. The message model (client-side, three layers)#
PlaintextMessage → SignedMessage (Ed25519 sender signature) → EncryptedEnvelope:
-
one per-message AES-256-GCM content key (CEK), wrapped per recipient device:
cek = random 32 bytes for each recipient device: eph = fresh X25519 keypair shared = X25519(eph_priv, recipient_pub) kwk = HKDF-SHA256(shared, info="dmcn-cek-wrap-v1") wrapped_cek = AES-256-GCM(kwk, cek) # nonce 12, tag 16 -
sealed blobs are padded to size-class buckets (1 KB / 4 KB / 16 KB / 64 KB / 256 KB / 1 MB, then round-up-to-MB; layout
[4-byte BE length][payload][zero padding]) for traffic-analysis resistance; -
envelope v2 is split into a small listable encrypted header (sender, subject, snippet, recipient lists, body commitments) and a large body, so listing an inbox never reads bodies; the bcc list appears only on the sender's own copy;
-
the body ciphertext blob (
body_nonce‖encrypted_body‖body_tag) is content-addressed (CIDv1(raw, sha2-256)=0x01 0x55 0x12 0x20 ‖ SHA-256(blob)): carried in the clear on the envelope for keyless relay verification and committed inside the signed header. Distinct from the whole-envelope SHA-256 used for retry idempotency. -
ratchet_pub_keyis reserved for a double-ratchet forward-secrecy upgrade and is zero in v1.
All cryptography is client-side; relays only ever handle sealed envelopes.
4. Routing#
- A recipient's mailbox lives on the relays named in its
RelayHints(an ordered list: primary + fallbacks). RelayHintsis operator-owned: carried in theroutingcredential (signed by arouting-granted issuer), not the owner self-signature — so an operator can re-point it without the mailbox owner's key.- Send = look up the recipient's record → STORE to the first reachable hint
(failover), or to all reachable hints when the recipient's domain DAR sets
PolicyReplicateMailbox. Receive = FETCH from all your hints and dedup. - Portability = republish with new hints; the address never changes.
- How an operator chooses and maintains hints (placement, reservation, rebalance, drain) is operator behavior outside the core; the core defines only the hints' format, ownership, and how senders/receivers use them.
5. Wire protocols (libp2p)#
| protocol | framing | purpose |
|---|---|---|
/dmcn/relay/1.0.0 |
4-byte big-endian length prefix + protobuf (4 MB frame cap; bodies chunked past it) | Store / chunked StoreInit, FetchInit → FetchChallenge → signed-nonce proof, mailbox ops (List/Body/Delete), the resolve ops (GetIdentity/GetDAR/GetFleetRoster/GetRemoval/GetBlocklist/GetRelayDescriptor) + PutRecord (record publication/replication, re-verified on ingest), Ack, Ping, OnionForward |
/dmcn/peers/1.0.0 |
length-prefixed JSON | cluster peer discovery |
/dmcn/join/1.0.0 |
varint-delimited protobuf | mutual credential handshake — each side presents its Credential + DAR; verified peers enter the set that gates federation |
Every core operation authorizes in one of three ways — none of which is a password:
message-authenticated ops carry their proof in the message (a store is valid because
the sender signed the envelope; a fetch is valid because the client answers the relay's
32-byte nonce challenge with a signature from the account key);
connection-gated ops (Ack, OnionForward) require a credential-admitted federated
peer; public reads return signed records the reader verifies independently — the one
public write, PutRecord, self-gates by re-verifying every record on ingest (self-sig,
DNS anchoring for a DAR, monotonic-revision anti-rollback), so an unverifiable record
simply cannot be stored.
Federation is deny-by-default and credential-gated: connections stay open so
/dmcn/join can run, and the participation gates check the join credential set.
Vacated arm numbers in RelayRequest/RelayResponse/MailboxOp are reserved with
gravestone comments — they belonged to operator extensions (§8) and must never be reused.
6. Onion routing (optional capability)#
Relays may serve a self-anchored RelayDescriptor (their X25519 onion key + node
credential, signed by their libp2p key — recoverable from the peer ID, so it verifies
without trusting the server), fetched via GetRelayDescriptor. A sender builds a
multi-hop onion route (default 3 hops; hops in distinct /24 networks and distinct
domains; optional stable guard entry), ending at the recipient's home relay. Each layer
is an OnionPacket sealed to one hop's key; only the innermost delivery layer is padded
to its own bucket classes. A mailbox (require_onion) or domain (PolicyRequireOnion)
can require onion delivery, in which case relays reject direct STOREs with
ONION_REQUIRED.
7. SMTP bridge (optional capability)#
An implementation may operate an SMTP↔DMCN bridge (bridge_capability on its identity
record). Inbound legacy mail is authenticated (SPF/DKIM/DMARC) at the bridge, and the
verdict travels as a signed BridgeClassificationRecord attachment
(application/x-dmcn-bridge-classification) inside the sealed envelope — verified by the
recipient's client against the bridge's published identity, never by trusting the relay.
Outbound delivery returns a signed BridgeDeliveryReceipt. These are message
payloads, not wire ops: the bridge speaks the same core relay protocol as everyone else.
Honest edge: mail crossing a bridge is TLS-in-transit on the legacy side, not
end-to-end encrypted.
8. Extension points (how everything else attaches)#
The core deliberately defines the attachment surfaces for extensions rather than the extensions themselves:
IdentityRecord.operator_credentials(field 28) — operator-attached credentials beyond routing, semantics identified by each credential's roles/attributes; excluded from the owner self-signature likeaddress_credential/routing_credential. The same-revision anti-rollback tiebreak is the newestissued_atacrossrouting_credentialand these.Credential.attributesunderext.-prefixed keys — signature-covered extension payloads (base64-encoded marshaled extension messages) without core schema coupling.- Separate libp2p protocol IDs — extension surfaces run their own protocols beside
the core (
/dmcn/relaynever carries them). Reserved core numbers mark where earlier drafts carried them. - DAR
policy_flags— core defines bits 0 (REQUIRE_COUNTERSIGN), 2 (REQUIRE_ONION) and 3 (REPLICATE_MAILBOX); other bits are reserved for extensions.
An implementation that ignores every extension interoperates fully: extensions may add operator capability, never interop requirements.