Transport & identity (iroh)
This layer is how two Xe Computer devices find each other, prove who they are, and move bytes — with no server in the middle deciding who may talk to whom. Everything above it (peer serving, the fleet catalog, remote engines) rides on the primitives described here.
As-built truth. Code citations are repo-relative in
agent54/xenon-launcheratmain @56e94575(“P-215: deterministic router conformance tests”). Thexenon-*repo name is historical; the product is the Xe Computer (see Glossary). Where design and code diverge, this chapter says so and points at the design review.
Primer: the words this chapter assumes
What is iroh? iroh is a peer-to-peer networking library (from the n0 / number0 team) built on QUIC. Each node has an Ed25519 keypair; the public key is the node’s address. iroh dials by key, hole-punches through NATs where it can, and falls back to relays when it can’t. Docs: iroh.computer/docs.
What is QUIC / ALPN? QUIC (RFC 9000) is an encrypted, multiplexed transport over UDP; its handshake is TLS 1.3 (RFC 9001). ALPN (RFC 7301) is the handshake field that names which application protocol a connection speaks — Xe uses one ALPN string per plane, so a single device key can serve several distinct protocols without confusion.
What is a relay? A rendezvous-and-fallback server: peers register their presence, coordinate hole-punching through it, and — when a direct path fails — carry their (still end-to-end encrypted) traffic through it. See iroh’s relay docs.
What is Ed25519? A signature scheme (RFC 8032). One 32-byte seed yields a signing key; in Xe that one seed is simultaneously the device’s identity and its network address.
Zoom 1 — one key, three planes
A single 32-byte Ed25519 seed is both the Xe device identity and the iroh
signing key — the launcher identity seed is fed directly into iroh via
SecretKey::from_bytes (crates/net/src/iroh_provider.rs:41-45, :94-104).
On top of that one key, three planes move bytes today:
- App-serving plane (peer-serve). A publisher binds an iroh endpoint and forwards each authenticated tunnel to a named local TCP port. Authentication is a 32-byte bearer secret carried in a ticket — not group membership. This is the plane the browser actually talks through. Detail: Peer serving & the fleet.
- Membership plane (group). A fully built, signed, hash-chained
group-document system with a join ceremony, head gossip, revocation, and
restart anti-rollback (
crates/net/src/group/). It is not wired into the serving plane — the honest seam this book keeps returning to. - Fleet catalog plane. No new protocol at all: an immutable JSON app index travels as ordinary HTTP over the already-authenticated serving tunnel, pinned by a SHA-256 digest carried in the ticket. Detail: Peer serving & the fleet.
flowchart TB
KEY["One Ed25519 device key<br/>(identity seed = iroh node key)"]
KEY --> EP1["Peer-serve endpoint<br/>ALPN xenon/serve/tcp/1 (+ echo)"]
KEY --> EP2["Group daemon endpoint<br/>ALPNs group-sync · group-join · substrate"]
EP1 -->|"bearer secret (ticket)"| T["byte tunnel → local app port"]
EP2 -->|"signed membership"| G["group document · gossip · join · revocation"]
T -.->|"not membership-checked today"| G
The two endpoints are separate binaries with separate accept loops: an
IrohProvider endpoint advertises the echo + serve ALPNs
(iroh_provider.rs:96), while a GroupNode advertises the group-sync,
group-join, and any extra ALPNs (crates/net/src/group/node.rs:539-543).
Zoom 2 — subsystems
The endpoint, and honest path receipts
The endpoint is iroh 1.0.2 userspace QUIC. On bind it waits up to 10 s for
endpoint.online() (iroh_provider.rs:105-109). For every live connection,
selected_path reads connection.paths(), finds the selected transport, and
maps it to a three-valued receipt (iroh_provider.rs:307-317, enum at
crates/net/src/lib.rs:64-72):
Direct— a hole-punched or LAN IP path,Relay— bytes are being carried through a relay,Unknown— the library reported something else; say so rather than guess.
Peer-serve prints the observed path per tunnel and per index probe — a receipt
of what actually happened, never an optimistic claim. For testing, a
force_relay switch strips IP transports so relay carriage can be proved
rather than assumed (iroh_provider.rs:98-100).
Membership enforcement (where it is wired)
When a provider is built with_membership, every inbound connection is
authorized against the current signed group head
(iroh_provider.rs:200-226), and every dial authorizes the remote key before
bytes flow (:158-179). Live connections are registered so that revocation
closes them immediately — with QUIC error code 0x5845 (“XE”) and the
reason string xenon: membership revoked (:242-245). Removal from the group
is not merely “no new connections”; it is an active disconnect.
The serving plane constructs its provider with IrohProvider::new — the
no-membership path — so none of this applies to app-serving tunnels today
(crates/launcher/src/peer_serve.rs:435, :591). That gap is
G4 in the review.
The signed group document
Group state is a single signed document, hash-chained across versions
(crates/net/src/group/mod.rs:198-296):
- monotonic
version+previous_hashchain, over a canonical big-endian binary encoding (crates/net/src/group/canonical.rs) — two implementations that agree on bytes agree on hashes; - one Ed25519 authority signs successors (
AuthorityMode::Single— a single authority key, not a quorum, is the current model); revocationsis append-only;- opaque
statementsgive forward compatibility for records the current code does not understand.
Succession is deliberately strict: a successor must be exactly one version
newer and must name the prior envelope’s hash; gaps, rollbacks, and forks are
rejected; the first valid successor wins — there is no quorum or conflict
resolution (mod.rs:11-16, DocumentChain::accept, :540-590). Across
restarts, a persisted MembershipLowWaterMark refuses to load any document
older than one already seen (:473-509) — an anti-rollback floor.
Join and gossip
The join ceremony (crates/net/src/group/protocol.rs) uses a single-use
signed invite plus an Ed25519 proof-of-possession from the joining
device. The invite is durably consumed before the successor document adding
the member is minted (serve_join_inner, :321-368) — a crashed join burns
the invite rather than leaving a replayable one.
Head gossip is a symmetric hello / need / document / done exchange in which
the behind side pulls one verified successor at a time (run_sync,
:422-479) — each step re-validated against the chain rules above.
Recovery statements — headroom, not a lifecycle
crates/net/src/group/recovery.rs defines five typed, signed statement
families — successor, takeover, counter, freeze, rotation — with a 72-hour
default challenge window (:74), plus reserved types for a future FROST
(threshold-signature) v2. This is statement vocabulary only: no ceremony,
no UX, and no networking drives these statements yet. The review treats this
as designed headroom, not a working key-recovery lifecycle.
Zoom 3 — module detail
ALPN registry
Every ALPN string in the codebase, and whether the connection it names is membership-gated:
| ALPN | Constant / site | Purpose | Membership-gated? |
|---|---|---|---|
xenon/net/echo/1 | ALPN, crates/net/src/lib.rs:25 | Original echo experiment; NetworkProvider::dial default | Only if built with_membership |
xenon/serve/tcp/1 | SERVE_ALPN, crates/net/src/lib.rs:31 | Peer-serve bearer-ticket byte tunnel | No — bearer secret only |
xenon/net/group-sync/1 | GROUP_SYNC_ALPN, crates/net/src/group/protocol.rs:31 | Membership-gated head gossip | Yes |
xenon/net/group-join/1 | GROUP_JOIN_ALPN, crates/net/src/group/protocol.rs:33 | Invite-gated join ceremony | Pre-membership (invite + proof-of-possession) |
xenon/substrate/1 | ALPN, crates/substrate-proto/src/wire.rs:14 | Remote substrate multi-lane session (dial_session, group/node.rs:414) | Yes (member-checked at dial) |
xenon/consent/test/1 | EXTRA_ALPN, crates/net/tests/extra_alpn.rs:25 | Test-only — proves extra ALPNs inherit the membership gate | Yes |
A hardened form of this table is a Part V candidate: ALPN & signing-domain registry.
Signing domains (not ALPNs)
Domain-separation strings prefix what gets signed, so a signature made for
one purpose can never be replayed as another. They are listed here so they are
not confused with the wire registry above: xenon/group-document/v1\0,
xenon/group-envelope/v1\0, xenon/group-join-proof/v1\0,
xenon/recovery/{successor,takeover,counter,freeze,rotation,rotation-accept}/v1\0,
xenon/wireguard-key/v1\0, xenon/interface-*, xenon/mod-manifest/v1\0,
xenon/profile-*; and in crates/xe-sync: xenon/group-document/v2\0,
xenon/group-envelope/v2\0, xenon/member-endpoint-binding/v1\0.
Frames and limits
Group join/sync frames are 4-byte big-endian length + strict JSON, capped at
64 KiB (group/protocol.rs:36, :123-153). Small, boring, auditable.
Two group-document lineages — an honest fork
Two parallel membership lineages exist in-tree:
- v1 in
crates/net/src/group/— what gossip, join, and the membership-gated provider compile against today; - v2 in
crates/xe-sync/src/group.rs— newer:MemberKind{Device, Companion}, member endpoint bindings, and v2 canonical signing domains.
Which lineage is canonical for the shipped serving path is undecided
in-tree. MemberKind::Companion has no serving semantics at all yet (may a
companion consume but never publish?). This fork blocks the central design
move of this beat — gating serving on membership — and is questions 1 and 3 on
the review’s session agenda.
Where identity does not reach yet
To keep the seam explicit before you read on: the membership plane above is built, signed, and revocation-capable — and the app-serving plane does not use it. Serving auth today is a bearer secret in a ticket file. What that means in practice, and the options for closing the seam, are the subject of Peer serving & the fleet and the two reviews (transport, peer serving).
Links
- iroh: iroh.computer · docs · relays
- QUIC: RFC 9000 · TLS handshake RFC 9001 · ALPN RFC 7301
- Ed25519: RFC 8032 · did:key method
- UCAN (discussed in the review): spec