dmcn business Log in Talk to us
Platform Protocol Security Sovereignty Pricing For personal use
Log in Talk to us
Protocol reference

The DMCN protocol, on the wire.

DMCN is being built as an open protocol: nothing about your identity or your mail should require trusting us, and that has to be checkable. This is the wire-level reference for the core protocol — everything an independent implementation needs to interoperate, with real field numbers, real protocol IDs and real signature contexts. The reference tables are generated from the protocol's own schema, so they cannot drift from what actually ships.

The stack at a glance
§2Identityself-certifying records — an address is a keypair §3ResolutionDNS-seeded, per-domain — "MX for identity" §4Messagessigned, sealed per recipient, padded §5Transportlibp2p streams — three protocols §6Relay servicestore, fetch, operate — every op and its authorization §7Trustcredentials & domain authority
cross-cutting: the signing convention (§8) and onion delivery (§9)
On this page
1. Status of this document 2. Identity & addressing 3. Resolution: the _dmcn record 4. Message model & encryption 5. Wire protocols (libp2p) 6. Relay operations & authorization 7. Credentials, authority & permits 8. Signing convention 9. Onion routing (optional)

1. Status of this document

This is a reference snapshot of the running implementation — versioned with the code, generated in part from it, and corrected by it: where this page and the implementation disagree, the implementation wins.

Scope: core protocol only. This page documents what an implementation needs to interoperate — resolve, verify, send, receive. The protocol also defines operator surfaces (fleet provisioning and administration, operator-attached quotas and access windows); those are optional segments a fleet may run, never requirements for interoperation, and are deliberately left out here until they are documented as proper extensions. Where they touch a shared structure below, rows are marked (extension) so the schema stays fully accounted for.

The wire-reference tables below (identity record, credential, envelope, relay operations, tiers) are built from the protocol's protobuf schema — the same definitions the implementation compiles against — so field names and numbers on this page are correct by construction. The core protocol spec and an Apache-2.0 reference implementation are published in full at github.com/dmcnp/open-dmcn — anyone can read the wire format, audit it, or run their own server. It is an early release that tracks the implementation rather than a frozen standard: formal versioning and a conformance suite are on the roadmap.

Conventions: messages use the protobuf3 wire format; byte lengths appear in parentheses; where the protocol frames data by hand, integers are big-endian; signature context tags are NUL-terminated strings, written here as \0. Reserved field numbers must not be reused.

2. Identity & addressing

An address — alice@yourcompany.com — is a keypair: an Ed25519 signing key and an X25519 key-agreement key, bound in a self-certifying IdentityRecord that proves itself to any reader. No certificate authority issues it.

The record has two ownership zones. The identity core — address, keys, validity, tier, revision — is covered by the owner's self-signature: only the key holder can change it. The operational side — mailbox routing — rides as an operator-signed credential deliberately excluded from the self-signature, so a domain operator can re-point or rebalance routing without ever holding the owner's key (the credential verifies against the domain authority instead — see §7).

The owner-signed revision is monotonic: fleets and readers reject any record whose revision is lower than one they have already verified, so a replayed old record can never roll an identity back.

Wire reference: IdentityRecord fields
#fieldtypenotes
1versionuint32record format version
2addressstringlocal@domain
3ed25519_public_keybytessigning key (32 bytes)
4x25519_public_keybyteskey-agreement key (32 bytes)
5created_atint64unix seconds
6expires_atint64unix seconds; 0 = no expiry
7relay_hintsrepeated stringreader-facing mirror only — NOT covered by the self-signature; the authoritative copy lives inside routing_credential (25)
8verification_tierVerificationTiertrust tier — see the tier table; enforcement is reader-side
9attestationsrepeated AttestationRecordweb-of-trust attestations (in-person, fingerprint, network, organizational)
10self_signaturebytesowner Ed25519 (64 bytes) over fields 1–6, 8, 23, 26; ctx dmcn-identity-self-v1\0
11–22(reserved)do not reuse
23require_onionboolmailbox requires onion delivery — relays reject direct stores; covered by the self-signature
24address_credentialCredentialthe domain's attestation of the address↔key binding (role address); issued after self-signing, excluded from the self-signature
25routing_credentialCredentialoperator-owned routing (role routing) carrying the authoritative relay_hints; excluded from the self-signature so operators can re-point routing without the owner's key
26revisionuint64monotonic and owner-signed — anti-rollback: a lower revision can never overwrite a higher one
27(reserved)do not reuse
28operator_credentialsrepeated Credentialgeneric operator extension point: operator-attached credentials beyond routing (semantics by role/attributes); excluded from the self-signature; anti-rollback tiebreaks on the newest issued_at across 25 and these
Wire reference: verification tiers
valuenamemeaning
0VERIFICATION_TIER_UNVERIFIEDvalid but untrusted — addresses register here and still function; trust is upgraded, never gated at registration
1(reserved)do not reuse
2VERIFICATION_TIER_DOMAIN_DNSraised by a domain attestation (address credential) chained to the DNS-anchored domain authority
3VERIFICATION_TIER_DANEreserved highest tier: DNSSEC/DANE-anchored

3. Resolution: the _dmcn record

Addresses resolve against the domain's own infrastructure, discovered through DNS — the same delegation model MX records use for mail routing. There is no global directory, so no foreign majority can censor or dominate resolution.

_dmcn.<domain>  TXT  "dmcn-verification=v1; fp=<40-hex>[; fleet=<domain>][; seed=<multiaddr>]..."

fp     trust anchor: first 20 bytes of SHA-256(ed25519_pub ‖ x25519_pub), uppercase hex
fleet  optional deferral to a hosting fleet's domain — discovery only; spoofing it is DoS, never forgery
seed   bootstrap multiaddrs; each ends /p2p/<peerID>, so the transport handshake authenticates the endpoint
The trust root, in your zone
$ dig +short TXT _dmcn.yourcompany.com
"dmcn-verification=v1; fp=9F2A…C41E; seed=/dns4/id1.yourcompany.com/tcp/4001/p2p/12D3Koo…"

A resolver dials a seed and fetches the domain authority record and the identity record, then verifies — in order:

  1. the domain authority record's root self-signature is valid;
  2. the authority's fingerprint equals the DNS fp= anchor (fail-closed);
  3. if the authority declares a fleet domain, it matches the DNS fleet= deferral (opt-in pinning);
  4. the identity record's owner self-signature is valid;
  5. the address credential chains to the domain authority (§7);
  6. neither key is blocked and the address is not tombstoned.

Because every record is self-certifying and anchored in the mailbox domain's DNS, a wrong or hostile server can only fail to answer — it can never fabricate or tamper undetected. Failure semantics are deliberate: no _dmcn record means the address does not exist; a transient DNS failure fails closed rather than downgrading trust.

4. Message model & encryption

A message is composed in plaintext, signed by the sender's key, then sealed once with a fresh per-message key that is wrapped separately for each recipient — three layers: PlaintextMessage → SignedMessage → EncryptedEnvelope.

Envelope version 2 splits the sealed content in two: a small, listable header (sender, subject, snippet, recipient lists, and signed commitments to the body) and the body (content and attachments), fetchable independently — a mailbox can list mail without pulling bodies. The bcc list appears only on the sender's own Sent copy; recipient copies carry an empty one.

cek = random 32 bytes                              # one per message
for each recipient device:
    eph         = fresh X25519 keypair
    shared      = X25519(eph_priv, recipient_pub)
    kwk         = HKDF-SHA256(shared, salt="", info=<context>)
    wrapped_cek = AES-256-GCM(kwk, cek)            # nonce 12, tag 16
content = AES-256-GCM(cek, plaintext, aad)         # header and body sealed separately in v2

# each wrap declares its own derivation in recipient_record.kdf; absent means 1
kdf=1: info = "dmcn-cek-wrap-v1"                              # bare label, binds nothing
kdf=2: info = "dmcn-cek-wrap-v2" ‖ eph_pub ‖ recipient_pub    # RFC 9180 §4.1 kem_context

# header and body share one CEK, so each seal binds a label to keep them apart.
# the labels are selected by the same kdf value, so blobs and wraps cannot disagree.
kdf=1: aad = none
kdf=2: aad = "dmcn-aad-hdr-v1\0"  |  "dmcn-aad-body-v1\0"

A reader dispatches on the declared value and never tries derivations in turn. The field rides the recipient record rather than the envelope because the record is what survives storage — a mailbox keeps per-recipient entries and drops the envelope's version, message id and timestamp.

A reader must also check that the sender addressed the message to the mailbox reading it — that some address in the signed audience (recipient_address, to, cc) resolves to the key the envelope was sealed to. That check, not the encryption, is what detects a message re-targeted at someone it was not addressed to: a legitimate recipient holds the message key, so it can re-seal the same signed header to a third party — but it cannot change the audience the sender signed. The comparison is on the key rather than the address text, so several addresses sharing one mailbox need no special case.

Sealed blobs are padded into standard size buckets so ciphertext length reveals as little as possible; the padded layout is [4-byte BE length][payload][zero padding]. The body blob also carries a cleartext content address any relay can verify without a key:

body_content_address = 0x01 0x55 0x12 0x20 ‖ SHA-256(body_nonce ‖ encrypted_body ‖ body_tag)
                       CIDv1(raw, sha2-256), 36 bytes — the signed copy inside the header is authoritative

Honest edge: v1 provides at-rest and in-transit confidentiality with per-message keys, but not full forward secrecy — the ratchet_pub_key field is reserved for a double-ratchet upgrade and is zero today.

Wire reference: EncryptedEnvelope fields
#fieldtypenotes
1versionuint321 = single-blob payload; 2 = split header/body
2message_idbytes16-byte UUID
3recipientsrepeated RecipientRecordone per recipient device: ephemeral X25519 public key + the wrapped CEK (nonce 12, tag 16)
4encrypted_payloadbytesv1 only: AES-256-GCM ciphertext of the whole SignedMessage
5payload_noncebytes12 bytes
6payload_tagbytes16 bytes
7payload_size_classuint32padded size bucket — see size classes
8created_atint64unix seconds
9ratchet_pub_keybytesreserved for forward secrecy (protocol v2); zero in v1
10encrypted_headerbytesv2 split: sealed SignedHeader — small and listable (sender, subject, snippet, recipient lists, body commitments)
11header_noncebytes12 bytes
12header_tagbytes16 bytes
13header_size_classuint32padded size bucket
14encrypted_bodybytesv2 split: sealed MessageContent (body + attachments), fetchable separately from the header
15body_noncebytes12 bytes
16body_tagbytes16 bytes
17body_size_classuint32padded size bucket
18body_content_addressbytescleartext CIDv1 of the body blob — lets relays verify integrity without any key; the authoritative copy is signed inside the header
Wire reference: envelope size classes
classbucket
11 KB
24 KB
316 KB
464 KB
5256 KB
61 MB
7+above 1 MB: round up to the next whole MB

5. Wire protocols (libp2p)

Every DMCN conversation is a libp2p stream: authenticated, encrypted transport where the peer's identity is cryptographically proven in the handshake — a peer ID cannot be spoofed without its key.

Three protocol IDs carry the core. Device pairing deliberately has no protocol of its own — the pairing exchange rides the ordinary relay mailbox path (under a synthetic pairing.local domain), with the human-verified SAS code committed under the dmcn-pair-sas-v1\0 context.

Wire reference: libp2p protocol IDs & framing
protocol IDframingpurpose
/dmcn/relay/1.0.04-byte big-endian length prefix + protobuf; 4 MB frame cap; bodies chunked past it (to 64 MB)the workhorse: store, fetch, mailbox, resolve and onion ops (§6)
/dmcn/peers/1.0.0length-prefixed JSONcluster peer discovery — a node returns its configured peer list
/dmcn/join/1.0.0varint-delimited protobufmutual credential handshake; gates federation, deny-by-default
(extensions)the operator and client-extension surfaces ride separate product protocols (/dmcn/operate, /dmcn/mailbox-ext, provisioning, permits) — not part of the core protocol; to be documented separately

6. Relay operations & authorization

One protocol carries all relay work, and every core operation authorizes in one of three ways — none of which is a password.

Message-authenticated ops carry their proof in the message itself: a store is valid because the sender signed the envelope; a fetch is valid because the client answers the relay's nonce challenge with a signature from the account key. Connection-gated ops require the calling peer to have presented a valid credential at the join handshake. Public reads return signed records the reader verifies independently; the one public write, put_record, self-gates by re-verifying every record on ingest — an unverifiable record simply cannot be stored. The operator surface — fleet administration and operator-attached entitlements — rides two separate product-side protocols (/dmcn/operate, /dmcn/mailbox-ext) outside the core; the arm numbers it once occupied here are reserved, and the table marks them so.

Wire reference: relay operations (core)
#opauth classauthorization
1storemessagesender's Ed25519 signature over the envelope; the sender must resolve and pass the recipient domain's policy gates
2fetch_initmessageopens the mailbox challenge — the relay returns a fresh nonce
3fetch_proofmessageEd25519 proof over the relay's nonce with the account key; no passwords, no bearer tokens
4ackconnectioncredential-admitted federated peer
5pingpublicliveness
6mailbox_opmessagelist / body / delete, mail-filter and personal-KV ops — each under a fresh fetch proof
7store_initmessagechunked store for bodies past the frame cap; sender signature like store, or node role for relay→relay drain handoff
8onion_forwardconnectioncredential-admitted federated peer — peel one layer, forward or deliver
9–18(reserved)vacated by the operator extension protocols; do not reuse
19get_identitypublic readreturns the signed IdentityRecord; the reader verifies it against the DNS anchor
20get_darpublic readreturns the signed domain authority record; verified against the DNS fingerprint
21get_fleet_rosterpublic readfleet-root-signed node roster
22get_removalpublic readroot-signed address tombstone
23get_blocklistpublic readroot-signed credential revocation list
24put_recordself-gatingevery record is re-verified on ingest (self-certifying); a fleet may add publisher gating
25get_relay_descriptorpublic readonion descriptor, self-anchored to the relay's peer ID
26(reserved)vacated by the operator extension protocols; do not reuse

7. Credentials, authority & permits

Trust is domain-anchored and explicit: a credential's roles say what a key is, its grants say what it may do — and no key can sign anything its grants don't cover.

Every credential chain resolves to the domain's root key — the one anchored by the DNS fingerprint (§3). Delegation is monotone: an issuer can never grant more than it holds, scope can only narrow (a subdomain scope covers its children, never its parents), and chains are capped at depth 8. Powerful grants stay on offline keys by convention; the high-traffic online services carry only what they operationally need.

The domain authority record is the domain's administrative root: its issuer set, reserved local-parts, key-rotation history, and policy flags. Administrative authority over an address is distinct from cryptographic ownership of it — a domain authority can provision, attest and revoke bindings on its namespace, but it never holds private keys and can never read mail. Hosting-consent mechanics between domain owners and fleet operators exist as an operator extension, outside this core reference.

Wire reference: Credential fields
#fieldtypenotes
1versionuint32credential format version
2subjectbytesEd25519 public key — the libp2p peer ID for infrastructure and clients, the identity key for addresses
3domainstringthe domain this credential is scoped to
4addressstringrole address only: the attested local@domain
5rolesrepeated stringwhat the subject IS — see the roles table
6grantsrepeated stringwhat the subject may ISSUE or DO — see the grants table
7attributesmap<string, string>free-form: multiaddr, ip, x25519 onion key, …
8issued_atint64unix seconds
9not_afterint64unix seconds; 0 = never expires
10scopestringauthority credentials: subdomain scope (empty = whole domain); delegation can only narrow it
11issuer_pubbytesthe domain root, or any enrolled issuer whose own grants cover this credential
12signaturebytesissuer Ed25519 over fields 1–11, 13 and 14; ctx dmcn-credential-v1\0
13relay_hintsrepeated stringrole routing only: the authoritative operator-signed mailbox relays
14effective_fromint64unix seconds not-before; the validity window is [effective_from, not_after]
Wire reference: credential roles (core)
rolewhat the subject is
authoritya domain's root signing authority (its key anchors the DNS fingerprint)
sub-authoritya delegated issuer enrolled by the root
nodea relay/storage node
bridgean SMTP bridge
clientan end-user-facing client peer (mints nothing by itself)
addressan attested address↔key binding (rides in IdentityRecord.address_credential)
routingan operator routing attestation (rides in IdentityRecord.routing_credential)
(extensions)further roles carry operator-attached entitlements — not part of the core protocol; to be documented separately
Wire reference: credential grants (core)
grantwhat the holder may do
addresssign address credentials (attest address↔key bindings)
routingsign routing credentials (attest mailbox routing)
grantdelegate: issue credentials that themselves carry grants
(extensions)further grants cover operator entitlements and fleet administration — not part of the core protocol; to be documented separately
Wire reference: domain policy flags (DAR policy_flags bits, core)
bitflageffect
1 << 0REQUIRE_COUNTERSIGNuncountersigned addresses are unusable on this domain — enforced reader-side and at the mailbox fetch gate
1 << 2REQUIRE_ONIONevery address on the domain must receive via onion delivery
1 << 3REPLICATE_MAILBOXsenders store to every listed relay hint instead of first-reachable failover
1 << 1, 1 << 4(extensions)operator surface — not part of the core protocol; to be documented separately as an optional extension

8. Signing convention

Every signature in DMCN follows one rule.

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, so a signature over one record type can never be replayed as another. One deliberate exception: the whole-message SignedMessage signature is computed over the canonical plaintext with no context tag.

Wire reference: signature context tags (core)
context tagsigns
dmcn-identity-self-v1\0IdentityRecord owner self-signature
dmcn-dar-self-v1\0DomainAuthorityRecord root self-signature
dmcn-credential-v1\0every Credential
dmcn-address-removal-v1\0AddressRemovalRecord (root-signed tombstone)
dmcn-key-compromise-v1\0KeyCompromiseRecord
dmcn-fleet-roster-v1\0FleetRoster
dmcn-msg-header-v1\0SignedHeader (the split-format message header)
dmcn-pair-sas-v1\0the device-pairing SAS commitment
(extensions)further tags sign operator-surface records — not part of the core protocol; to be documented separately

9. Onion routing (optional)

An optional delivery mode that hides who is talking to whom from the relays in between. The packet format below is implemented; today's deployments run single-hop, and multi-hop is the design default — stated plainly so nobody buys metadata privacy this page hasn't earned yet.

A route is 3 hops by default, ending at the recipient's home relay; hops must sit in distinct /24 networks and distinct domains (no two hops share an operator), with an optional stable guard as the entry. Each relay publishes a RelayDescriptor — its onion key and addresses — signed by its own libp2p key, which is recoverable from its peer ID, so descriptors verify without trusting whoever served them.

OnionPacket { version, ephemeral_pub (32), nonce (12), tag (16), encrypted_layer }
OnionLayer  { next_hop | "DELIVER", ttl_unix, inner_packet | delivery }

each relay: derive layer key from ephemeral_pub + own X25519 key → peel → forward (or deliver)

Only the innermost delivery layer is padded to its own bucket classes (up to 36 MB, matching the message ceiling); forwarding layers add routing overhead only. A mailbox sets require_onion (or its domain sets the REQUIRE_ONION policy) to make the relay reject direct stores with ONION_REQUIRED.

Building against this? Talk to the people who designed it.

Bring your hardest questions — and run the drills on your own domain.

Book a design-partner pilot