Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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-launcher at main @56e94575 (“P-215: deterministic router conformance tests”). The xenon-* 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:

  1. 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.
  2. 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.
  3. 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_hash chain, 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);
  • revocations is append-only;
  • opaque statements give 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:

ALPNConstant / sitePurposeMembership-gated?
xenon/net/echo/1ALPN, crates/net/src/lib.rs:25Original echo experiment; NetworkProvider::dial defaultOnly if built with_membership
xenon/serve/tcp/1SERVE_ALPN, crates/net/src/lib.rs:31Peer-serve bearer-ticket byte tunnelNo — bearer secret only
xenon/net/group-sync/1GROUP_SYNC_ALPN, crates/net/src/group/protocol.rs:31Membership-gated head gossipYes
xenon/net/group-join/1GROUP_JOIN_ALPN, crates/net/src/group/protocol.rs:33Invite-gated join ceremonyPre-membership (invite + proof-of-possession)
xenon/substrate/1ALPN, crates/substrate-proto/src/wire.rs:14Remote substrate multi-lane session (dial_session, group/node.rs:414)Yes (member-checked at dial)
xenon/consent/test/1EXTRA_ALPN, crates/net/tests/extra_alpn.rs:25Test-only — proves extra ALPNs inherit the membership gateYes

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).