Mesh, transports, and self-hosting
The lattice is eighteen nodes serving one protocol. This page is the fabric that joins them and the path to adding your own.
An earlier version described the mesh as a "gossip channel (zeq:mesh:gossip)" that each node
subscribes to on boot, and presented that as how nodes find each other and how fleet reads work.
That channel is real, but it is not the node-to-node transport — it is a Redis pub/sub bus, and
an off-box node can neither reach it nor be given credentials to it. The real answer is the two
transports below. What zeq:mesh:gossip actually is has its own section.
Transport 1 — ZeqSSH over raw TCP, port 18870
Replication between nodes does not go over HTTP. It rides a raw TCP session on the mesh port, with
no nginx in the path. Every entry in GET /api/mesh/peers carries tcp_port: 18870.
shared/api-core/src/lib/zeqTcp.ts is the whole transport:
| Layer | What it does |
|---|---|
| Framing | 4-byte big-endian length, then JSON. FRAME_MAX is 1 MiB — anything larger is refused. |
| Wire | HELLO (client) → HELLOACK (server) → FRAME / ACK, the last two carrying AES-256-GCM ciphertext. |
| Record keys | deriveRecordKey() derives a per-zeqond AES-256-GCM key by HKDF-SHA256, rekeyed every zeqond and separated by direction, so a captured frame's key never opens a later one. |
| Replay window | HELLO_SKEW = 300 zeqonds (~4 minutes). A HELLO further out of step is refused. |
The HELLO carries an HMAC-SHA256 over its canonical fields
(zeqssh-hello|sessionId|nonce|nodeId|zeqond), keyed on the shared ZEQ_MESH_SECRET. The
per-session key is HKDF(ZEQ_MESH_SECRET, sessionId, nonce), so both ends derive bit-identical keys
with no public-key exchange at all.
That means the shared secret is the trust boundary for everything riding this channel — which is
why a mesh_announce over TCP carries no additional signature. The source says so in its own words:
"Ed25519 peer identity can layer on later; the record layer is unchanged."
Ed25519 is real, and it is used — on the HTTPS peer protocol below, not here. Any document that describes ZeqSSH as carrying an Ed25519 identity is describing an intention.
Peer discovery rides the same channel. announceSelfOnce() sends a mesh_announce carrying this
node's descriptor to every known peer and merges back their peer sets, so the mesh becomes fully
connected from a single bootstrap peer with no environment surgery. That registry is what
GET /api/mesh/peers serves.
Port 18870 is not a public front door. lib/meshJoin.ts states that the replication transport
lives inside the fleet's docker network and is not reachable from off-host or behind NAT. A node on
someone else's hardware uses the HTTPS join below instead. (We could not probe the port from outside
the sandbox to confirm the filtering independently — this is the code's own statement.)
Transport 2 — the signed HTTPS peer protocol
Some reads have to be node-to-node over HTTPS rather than the replication channel — the
home-node directory is the clearest example. Those use a separate,
signature-based protocol implemented once in lib/peerAuth.ts, with no shared secret anywhere.
Three headers on every request:
| Header | Contents |
|---|---|
x-zeq-peer-origin | the caller's public origin, lowercased, no trailing slash |
x-zeq-peer-zeqond | the caller's zeqond at signing time |
x-zeq-peer-sig | Ed25519 signature over a canonical string |
Three checks on the receiving side, in order: the origin must be a framework-owned domain
(403 not_a_peer); the claimed zeqond must be within PEER_ZEQOND_WINDOW = 300 zeqonds
(403 stale_peer_request); and the signature must verify against the caller's published key,
fetched from its own GET /api/node/status and cached for 60 seconds (403 bad_peer_sig).
# every node publishes the key it will be checked against
curl -s https://zeqproof.com/api/node/status \
| python3 -c "import sys,json;d=json.load(sys.stdin);print(d['nodeId'], d['publicKeyHex'])"
# unsigned, so it is refused — the gate working
curl -s https://zeq.me/api/identity/peer/presence/ZEQTEST
# {"ok":false,"error":"not_a_peer"}
The trust anchor here is the signature against a published key, never the transport. Full detail on the spine page.
What zeq:mesh:gossip actually is
zeq:mesh:gossip exists — MESH_CHANNEL in lib/meshClient.ts — and it is worth knowing what it
does, because it is not what the old page claimed.
- It is a Redis pub/sub channel, on a dedicated Redis connection
(
createDedicatedRedis), structurally separate from the app-cache/session Redis. - It carries state-machine events —
state_tick,state_transition, machine announcements — so that observers and aggregators on a node see peer events alongside their own. It does not carry WORM replication; that is the TCP channel above. - It only reaches processes on the same Redis. The source is explicit that a node on another host "cannot reach that Redis and must not be given credentials to it."
The HTTP counterpart is POST /api/mesh/gossip, which is the front door for off-box nodes and is
HMAC-authenticated on the x-node-handshake header:
curl -s -X POST https://zeq.me/api/mesh/gossip -H 'content-type: application/json' -d '{}'
# {"error":"Node handshake verification failed","code":"MISSING_HANDSHAKE"}
Two peer relationships, two transports: Redis is the co-located fleet's internal bus, HTTPS is the front door, and neither substitutes for the other.
The open join — how an off-box node pulls the shared log
You do not need the mesh secret to read the federated spine. lib/meshJoin.ts implements an
open-contributor join over 443, priced in proof-of-compute rather than credentials:
POST /api/mesh/join→ a Landauer proof-of-compute challenge (429 POW_REQUIRED).- Solve it — hashcash:
sha256(challenge + ':' + solution)must have at leastdifficultyBitsleading zero bits. Live on 2026-09-03:difficultyBits: 18,expiresInSec: 600, tagged with the operator that priced it (LZ1) and its Landauer energy floor. POST /api/mesh/join { pow: { challenge, solution } }→ a signed join token, the full peer roster, and this node's anchor.GET /api/worm/fed/stream?after_seq=Nwith headerx-zeq-mesh-token→ the federated sub-chain.
curl -s -X POST https://zeq.me/api/mesh/join -H 'content-type: application/json' -d '{}'
{ "ok": false, "code": "POW_REQUIRED",
"challenge": "18.1788434951758.87ade8e8e68da933b492cdde6bcddc70.d5fbbd1ca834b6b10f03ed06f60c7f50",
"difficultyBits": 18, "expiresInSec": 600,
"algorithm": "sha256(challenge + ':' + solution) must have >= difficultyBits leading zero bits",
"landauerJoules": 5.167761993141703e-20, "operator": "LZ1" }
The gate allows a small free quota per caller before it starts demanding work, so a first call may
return the token directly. The token is an HMAC over the fleet mesh secret, so a token issued by
any node verifies on every node — one join, whole fleet — and it expires after
JOIN_TOKEN_WINDOW_Z = 600 zeqonds (about 7–8 minutes).
The successful response also hands back the bootstrap roster you would otherwise have to be told:
{ "ok": true, "token": "eyJ0eXAiOiJtZXNoLWpvaW4i…", "issuer": "machine-zeqme",
"roster": { "self": { "node_id": "machine-zeqme", "base_url": "https://zeq.me",
"tcp_host": "zeq.me", "tcp_port": 18870 },
"peers": [ /* the other seventeen */ ] },
"anchor": { "node": "machine-zeqme", "ledger_root": "1f8b002a…", "ledger_count": 650 },
"hint": "GET /api/worm/fed/stream?after_seq=N with header x-zeq-mesh-token …" }
GET /api/mesh/peers is the unauthenticated version of that roster, if all you want is the map.
Certificates
Application-layer TLS is the framework's own: Zeq-SSL (lib/sslHandshake.ts,
lib/sslCredentials.ts, lib/sslTrustGraph.ts) issues and manages credentials for hosted
channels and fork domains. Its record layer is the same deriveRecordKey() that the
TCP transport above encrypts frames with. The SSL routes live in the
security reference.
Standing up your own node
A fork is a full node that speaks the same protocol. The shape:
- Bootstrap secrets — generated by
infra/air-gapped/bootstrap.sh. They live server-side and are never checked into a repo. - Run the stack — the api-core kernel plus its own Postgres and Redis, from the air-gapped
docker-compose.yml. The node boots and starts its Zeqond clock and seal spine. - Point a domain at it — nginx routes your domain's root to the node;
/api/…is the live surface,/s/…serves hosted channels. - Join and pull — the proof-of-compute join above gets you the roster and a token for
/api/worm/fed/stream. Your node verifies every row it applies rather than trusting the sender. - Verify parity — the acceptance test is the one from Nodes: your node's
GET /api/endpointshashes identical to the reference fleet.
Joining gets you read access to the federated spine. It does not make you the issuer — exactly
one node seals issuance, and the other seventeen production nodes run with ZEQ_MESH_ISSUER=false
(the issuer). It also does not replicate anyone's private state:
machines, envelopes, coins, pages and vault secrets never enter the wire from any node
(what travels).
Because verification travels but private state doesn't, a fork is a peer, not a replica: machines spun up on your node live on your node, and their proofs verify anywhere — including back on the reference fleet.
What stays the same everywhere
The kernel, the operator registry, the constants, the clock and the proof formats are identical on every node by construction — that's what makes a ZeqProof from one node checkable on another. A fork customises its surface (domain, branding, which channels it hosts), never the maths.
Read next
- Nodes and node-locality — the eighteen by name, and the parity check.
- The spine and what replicates — both transports in full, and how a receiver re-derives every row.
- ZeqDNS & node-scoped names — the naming layer a new node brings with it.
- Proofs — why a fork's results are trustworthy off its own node.