Skip to content

Concepts & Vocabulary

The shared glossary the rest of this survey is written in: every load-bearing iroh 1.0 noun — EndpointId, EndpointAddr, Ticket, ALPN, postcard, the Socket/Endpoint/Connection layering, path/multipath, QUIC Address Discovery, NAT traversal, address_lookup, Hash/bao/outboard, HashSeq, NamespaceId/AuthorId/Entry, gossip TopicId, relay/home relay, net_report — defined once, grounded in a cited source line, and linked onward to the deep-dive that treats it in depth.

Last reviewed: July 6, 2026

NOTE

This is a reference page, not a deep-dive: it fixes terminology and points at the authoritative treatment, so the deep-dives can use a term without re-defining it. Every definition here is pinned to iroh workspace v1.0.1 (commit 22cac742ca) and the matching crate tags (iroh-blobs 0.103.0, iroh-docs 0.101.0, iroh-gossip 0.101.0, noq noq-v1.0.1, bao-tree 0.16.0). iroh 1.0 is a major rename of the 0.x API — see The 1.0 rename cascade — so 0.x folklore (NodeId, magicsock, DISCO, STUN, Collection-as-protocol) does not compile against this tree. Part of the iroh survey.


Why this page

iroh's names are load-bearing. The single most consequential fact about the whole stack — that an endpoint's identity, address, and encryption root are the same Ed25519 key — is expressed purely through the type aliasing pub type EndpointId = PublicKey, and reading the code without that in hand is a maze. The 1.0 restructure then renamed roughly a dozen of the most-used identifiers at once. This page is the decoder ring: it gives one grounded definition per term and a link to where the mechanics live, so a reader (or a D re-implementer) can move between deep-dives without re-deriving vocabulary each time.

Terms are grouped by the layer they belong to, bottom-up: identity & addressingserialization & sharingthe connectivity layeringgetting connectedcontent addressing (blobs)documents & gossip. Two cross-cutting facts frame everything: the whole application stack serializes with one format (postcard), and the whole connectivity stack is one QUIC connection per peer carrying multiple paths.


The 1.0 rename cascade

iroh 1.0 renamed the vocabulary wholesale and deleted two 0.x subsystems outright. A term on the left will not be found in the pinned tree; use the right.

0.x term1.0 termNote
NodeIdEndpointIdstill an alias of PublicKey (key.rs)
NodeAddrEndpointAddrnow an id + a unified BTreeSet<TransportAddr>
NodeTicketEndpointTicketold node… ticket strings are unparseable by 1.0
magicsock / MagicSockthe socket layer / Socketpub(crate), restructured around noq multipath (socket.rs)
discovery / Discoveryaddress_lookup / AddressLookupa pure name→address directory (address_lookup.rs)
DiscoveryItem / NodeInfoItem / EndpointInfo / EndpointDatasee Address Lookup
DISCO (UDP side-protocol)gone → in-QUIC NAT traversal (QNT)no magic ping/pong packets on the wire
STUNgoneQUIC Address Discoveryno STUN code anywhere in the tree
netchecknet_reportsame shape, QAD+HTTPS substrate (net_report.rs)
DERPrelay (iroh-relay)WebSocket packet-forwarder, revised
Collection (a protocol type)a HashSeq conventionno longer known to the wire protocol
quinnnoqn0's fork adding multipath / QAD / QNT (README.md)

Identity & addressing

Everything an endpoint is and everywhere it can be reached hangs off one 32-byte Ed25519 key. The crate root states the dual role directly (iroh-base/src/key.rs):

"Each endpoint in iroh has a unique identifier created as a cryptographic key. This can be used to globally identify an endpoint. Since it is also a cryptographic key it is also the mechanism by which all traffic is always encrypted for a specific endpoint only."

TermDefinitionOwning page · cite
PublicKeyA #[repr(transparent)] newtype over the 32-byte compressed Edwards y-coordinate of an Ed25519 verifying key; LENGTH = 32. Verification is verify_strict, not the permissive verify.identity-crypto · key.rs
EndpointIdThe same type, pub type EndpointId = PublicKey. Convention: say PublicKey for crypto operations, EndpointId when naming an endpoint. Self-certifying — the name is the verification key.identity-crypto · key.rs
SecretKeyThe 32-byte Ed25519 seed wrapping ed25519_dalek::SigningKey, ZeroizeOnDrop. public() re-derives the PublicKey; the same secret signs relay, pkarr, and docs surfaces with no extra crypto.identity-crypto · key.rs
SignatureA 64-byte Ed25519 signature; on the wire it is 64 raw bytes, no length prefix.identity-crypto · key.rs
EndpointAddrEndpointAddr { id: EndpointId, addrs: BTreeSet<TransportAddr> } — an id plus a set of location hints. Dialing by bare id (empty addrs) is legal; address lookup fills the rest.wire · endpoint_addr.rs
TransportAddrThe #[non_exhaustive] enum of one location hint: Relay(RelayUrl) (postcard tag 0), Ip(SocketAddr) (1), Custom(CustomAddr) (2). Derived Ord = (variant index, payload), which is the ticket byte order.wire · endpoint_addr.rs
RelayUrlA newtype over Arc<Url> naming a relay server (e.g. http://derp.me./); it appears as TransportAddr::Relay and as an endpoint's home relay.relay · endpoint_addr.rs
EndpointIdMappedAddrA synthetic IPv6 Unique-Local-Address (fd15:70a:510b::/64, dummy port 12345) minted one-per-remote so noq, which only understands SocketAddr, can dial a peer that has no fixed IP. struct EndpointIdMappedAddr(Ipv6Addr).socket · mapped_addrs.rs

Two subtleties a reader trips on. First, an EndpointId string is 64-char lowercase hex by Display (the 0.x base32 Display is gone), though FromStr still accepts either hex or BASE32_NOPAD. Second, the EndpointIdMappedAddr is one of a family of "fake" ULA addresses the socket uses to name non-IP destinations to QUIC — RelayMappedAddr and CustomMappedAddr are the siblings; the mapping table is append-only for process lifetime (mapped_addrs.rs).


Serialization & sharing

postcard

The one application-level serialization format across the whole stack — a compact, non-self-describing binary serde encoding. There is no protobuf, no bincode, and no JSON on any hot path. The rules a codec must implement: unsigned integers are LEB128 varints (little-endian 7-bit groups, MSB = continuation); signed integers are zigzag-then-varint; a fixed [u8; N] (a key, a hash) is N raw bytes with no length prefix; an enum is a varint of the declaration index then the payload; a struct is its fields concatenated in declaration order with no tags. Because nothing is self-describing, the Rust declaration order is the wire contract — reordering a struct's fields is a silent wire break. A second varint dialect coexists: the QUIC varint (RFC 9000 §16, big-endian, 2-bit length tag) is used for relay frame types and QUIC-native fields; a port implements both and must never confuse them. Full byte-level treatment in Wire Formats & Serialization.

ALPN

An Application-Layer Protocol Negotiation byte string (RFC 7301) chosen per protocol; every iroh connection is opened under one, and the accepting protocol router dispatches on an exact-match of the bytes (the accept side picks the winner; connect-side order is irrelevant). The production registry:

ALPNOwnerCarries
/iroh-bytes/4iroh-blobscontent-addressed blob transfer
/iroh-sync/1iroh-docsdocument range-sync
/iroh-gossip/1iroh-gossipepidemic broadcast on a topic
/iroh-qad/0iroh-relayQUIC Address Discovery listener
DUMBPIPEV0dumbpiperaw byte pipe (example app)

The full registry, hex, and citations are in Wire Formats & Serialization. Note that ALPN is not the TLS server name: the SNI is a synthetic base32(EndpointId).iroh.invalid that carries the dialed id and buckets 0-RTT tickets per peer.

Ticket and its three kinds

A ticket is the copy-pasteable string that bootstraps a connection: a KIND prefix plus the BASE32_NOPAD of a postcard payload wrapped in a single-variant enum (so byte 0x00 opens every ticket, a one-byte version discriminant bought cheaply). The Ticket trait leaves the byte format to the implementer but recommends postcard, which all three concrete tickets use.

TicketKINDPayloadCite
EndpointTicketendpointEndpointId + BTreeSet<TransportAddr> (the unified 1.0 address shape, Variant1)endpoint.rs
BlobTicketbloblegacy 0.x layout: EndpointId + Option<RelayUrl> + BTreeSet<SocketAddr> + BlobFormat + Hashticket.rs
DocTicketdoca Capability (Read = a NamespaceId, or Write = a raw 32-byte NamespaceSecret) + Vec<EndpointAddr>ticket.rs

Two hazards worth stating in the glossary. BlobTicket still ships the pre-1.0 address shape and silently drops Custom transports and all but the first relay on encode — a port cannot reuse one address encoder for all three. And a DocTicket whose Capability is Write embeds the document's raw secret key: tickets are bearer credentials, not just addresses.


The connectivity layering

Three objects sit in a strict stack, and confusing them is the most common vocabulary error. From bottom to top:

ObjectWhat it isOwning page · cite
SocketThe multipath connectivity fabric (ex-magicsock, pub(crate)). It presents noq with a single object that looks like one UDP socket (noq::AsyncUdpSocket) while fanning datagrams across IP, one relay, and custom transports, and owns path selection and hole-punch scheduling.socket · socket.rs
EndpointThe public front door: a cheaply-clonable Arc<EndpointInner> over the QUIC stack + identity + a protocol Router. Recommended one per application so all connections share peer-to-peer state.endpoint · endpoint.rs
ConnectionOne QUIC connection to one peer (over noq::Connection), typed Connection<State> for 0-RTT correctness. A single connection carries multiple concurrent multipath paths.endpoint · quic-transport

The Endpoint doc comment fixes the "one per application" convention (endpoint.rs):

"It is recommended to only create a single instance per application. This ensures all the connections made share the same peer-to-peer connections to other iroh endpoints, while still remaining independent connections."

The Runtime / AsyncUdpSocket seam

The seam between the QUIC state machine and its host is the single most important interface for a port. noq is sans-io: noq-proto is pure protocol logic with no clock and no sockets, and the async noq crate reaches the outside world through four traits — Runtime (new_timer, spawn, wrap_udp_socket, now), AsyncUdpSocket (create_sender, poll_recv, local_addr), UdpSender (poll_send), and AsyncTimer. iroh plugs its multipath Socket in as an AsyncUdpSocket via new_with_abstract_socket (so wrap_udp_socket is dead code — the QUIC stack never touches a raw UDP fd), and drives every timer/task through Runtime. That trait object is exactly what a D port replaces with an event-horizon capability row + Scope, and the sans-io core it fronts is what makes the port tractable at all (noq/src/runtime/mod.rs).

path (direct vs relay) and multipath

A path in iroh 1.0 is a QUIC-Multipath path, not a mode. The decisive change from 0.x: there is no separate "relay mode" any more — the relay and every direct address are concurrent multipath paths on one connection, and "upgrading from relay to direct" is just flipping a path's QUIC PathStatus from Backup to Available and letting noq route (socket.rs).

  • A direct path is a hole-punched UDP 4-tuple between the two peers (see NAT traversal).
  • A relay path carries the same encrypted QUIC datagrams through a relay server by EndpointId.
  • Internally noq separates a PathId (a multipath packet-number space: its own packet numbers, ACK state, loss timers) from a PathData (the 4-tuple in use: RTT, congestion controller, MTU); one PathId can migrate across 4-tuples. Scheduling is a strict two-tier priority — lowest-PathId validated Available path wins; Backup paths carry data only when no Available path exists.

iroh configures 8 concurrent paths, a 5 s heartbeat, and a 15 s per-path idle timeout; smarter selection (RTT-weighting, sticky failover) lives in the socket's per-remote actor, above noq.


Getting connected

Four subsystems cooperate to turn a bare EndpointId into a working direct path. STUN and the DISCO UDP side-protocol of 0.x are both gone — every mechanism below rides inside QUIC or over HTTPS.

QUIC Address Discovery (QAD)

The STUN replacement: the peer at the other end of a QUIC connection tells you the source SocketAddr it observes for you, in an OBSERVED_ADDRESS frame. It is negotiated by the transport parameter ObservedAddr = 0x9f81a176 (role ∈ {send-only, receive-only, both}) and carried by frames ObservedIpv4Addr = 0x9f81a6 / ObservedIpv6Addr = 0x9f81a7 (noq-proto, transport_parameters.rs). iroh consumes it two ways: dedicated relay QAD servers (ALPN /iroh-qad/0 on UDP port 7842) that net_report probes to learn its own public address, and in-band on every regular connection. Because the probe rides the very socket whose mapping it measures, the discovered address is a valid direct-path candidate. Draft: draft-seemann-quic-address-discovery. See NAT Traversal and Net Report.

NAT traversal (holepunch)

iroh punches through NATs entirely inside QUIC, via n0's own extension QNT — "inspired by draft-seemann-quic-nat-traversal-02, simplified to n0's own protocol". Both peers advertise their QAD-learned reflexive addresses with ADD_ADDRESS/REMOVE_ADDRESS/REACH_OUT frames (block 0x3d7f90…0x3d7f94), then fire off-path PATH_CHALLENGE/PATH_RESPONSE probes simultaneously so each NAT sees the inbound as a reply and installs a mapping (simultaneous open). A successful probe becomes a validated multipath path. QNT requires multipath and negotiates via transport parameter N0NatTraversal = 0x3d7f91120401 (value = max advertised addresses); roles are fixed by the QUIC client/server side, not by who is behind NAT. iroh allows 32 QNT addresses. The engine is sans-io; orchestration lives in the socket per-remote actor. Full treatment in NAT Traversal & Address Discovery.

address_lookup, pkarr, and DNS

address_lookup (what 0.x called discovery) is the directory that maps an EndpointId to transport addresses — deliberately not the dialer. The AddressLookup trait is asymmetric: publish is fire-and-forget, resolve is a cancellable multi-result stream. Its doc comment (address_lookup.rs):

"Publishes the given EndpointData to the Address Lookup mechanism. This is fire and forget, since the Endpoint can not wait for successful publishing."

Two nouns name the how, not the what:

  • pkarrPublic-Key Addressable Resource Records: the self-authenticating record format. An endpoint's addressing info is packed as an RFC 1035 DNS reply packet, wrapped in a BEP-44 mutable-item encoding, signed by the SecretKey. The 1104-byte layout is a 32-byte public key, a 64-byte signature, an 8-byte microsecond timestamp, then a ≤1000-byte DNS packet (iroh-dns/src/pkarr.rs). Any party can verify; only the key-holder can mint a newer (monotonic-timestamp) record. iroh vendors the format (~400 lines) rather than depending on the upstream pkarr crate.
  • DNS — one transport for pkarr records: the same signed DNS packet is served under a z-base-32 domain label, so resolvers can cache it. The N0 preset pairs a PkarrPublisher (publish over HTTP) with a DnsAddressLookup (resolve over cheap, cacheable DNS).

The default publisher filter is relay_only() — raw IPs are not leaked to a public pkarr server unless the operator opts in. Full treatment in Address Lookup (Discovery).

relay and home relay

A relay is a public, always-reachable server that both peers connect to and that forwards opaque already-encrypted QUIC datagrams between them by destination EndpointId — a revised DERP. It is simultaneously the rendezvous (a fixed address both peers can reach) and the fallback data path (a relay path until direct hole-punching succeeds). It never sees plaintext (iroh-relay/src/lib.rs):

"The relay server helps establish connections by temporarily routing encrypted traffic until a direct, P2P connection is feasible. Once this direct path is set up, the relay server steps back, and the data flows directly between devices."

The data plane is binary WebSocket messages over HTTP(S) (so it runs unmodified in a browser); the relay binary also co-hosts the QAD endpoint. A node's home relay is the single lowest-latency relay it camps on, chosen by net_report with hysteresis to prevent flapping between near-equal servers. Full treatment in The Relay Protocol.

net_report

The connectivity-sensing report (descendant of Tailscale's netcheck): it answers "what does the network look like from here right now" — is UDP working over IPv4/IPv6, what public SocketAddr does the world assign me (global_v4/global_v6), is the NAT mapping endpoint-dependent (symmetric, which defeats naive hole-punching), which relay is the home relay, and is a captive portal intercepting traffic (report.rs). In 1.0 its only probes are QAD (over the main endpoint) and HTTPS; STUN and ICMP are gone. It also owns two sibling inputs — netwatch (interface/route watching + the rebindable UdpSocket) and portmapper (UPnP/PCP/NAT-PMP). Full treatment in Net Report.


Content addressing (blobs)

Hash and BLAKE3

A Hash is a 32-byte newtype over blake3::Hash — the content address at the heart of blobs. On the wire it is 32 raw bytes, no length prefix; Display is 64-char lowercase hex, FromStr also accepts 52-char base32-nopad. Hash::EMPTY (the BLAKE3 hash of b"") is a hard special case: never stored, always "present", the only hash for which a zero-size response is legal (hash.rs). The hash is the request: because response geometry is fully determined by (hash, size, ranges), there is exactly one correct byte sequence per request, and tampering is detected locally.

bao, outboard, and ChunkRanges

BLAKE3 internally hashes a blob as a binary Merkle tree over 1024-byte chunks; bao-tree exposes that tree as a verified-streaming codec, so a receiver holding only the 32-byte root can pull an arbitrary subset from an untrusted peer and verify each piece as it arrives, aborting the moment a hash mismatches — "the requester will notice if data is incorrect after at most 16 KiB of data" (iroh-blobs/DESIGN.md).

TermDefinitionCite
bao / bao-treeThe verified-streaming format (wire-compatible with the bao crate at block size 0), extended with runtime-configurable chunk groups and multi-range queries.bao-tree · tree.rs
outboardThe tree of interior BLAKE3 hashes (64-byte parent chaining-value pairs) stored/transmitted separately from the data, so ranges can be verified without re-hashing the whole blob.bao-tree · tree.rs
BlockSize / chunk groupThe runtime leaf granularity, log2(group bytes / 1024). iroh fixes IROH_BLOCK_SIZE = BlockSize::from_chunk_log(4) = 16 KiB ("n0-flavoured bao"), cutting the outboard 16× versus per-1024-byte-chunk classic bao.bao-tree · tree.rs
ChunkRanges / ChunkRangesSeqA run-length-encoded sorted set of non-overlapping chunk ranges (units of 1024-byte chunks) selecting exactly which parts of a blob (or hash-sequence) to fetch and verify. Encoded as alternating deselect/select span widths for small postcard varints.blobs · protocol.rs

HashSeq and Collection

A HashSeq is the only structural type the blobs protocol knows beyond a raw blob: a blob whose bytes are a raw concatenation of 32-byte hashes (len % 32 == 0, get(i) = bytes [i*32 .. (i+1)*32], no header) (hashseq.rs). BlobFormat is therefore just Raw | HashSeq. A Collection is a convention layered on top (invisible to the protocol): a HashSeq whose link 0 is the hash of a metadata blob whose postcard-encoded CollectionMeta carries a header: [u8; 13] = b"CollectionV0." and a list of names, so names[i] labels link[i+1] — a named directory of blobs (format/collection.rs). In 0.x Collection was a protocol concept; in 1.0 it is only this convention.


Documents & gossip

blob, document, and gossip topic

The three application protocols name three different data shapes:

TermData shapeALPNOwning page
blobAn immutable, content-addressed byte sequence named by its Hash./iroh-bytes/4blobs
documentA multi-writer, eventually-consistent key–value map (a replica) of signed (namespace, author, key) entries./iroh-sync/1docs-sync
gossip topicA TopicId — a 32-byte identifier naming one independent broadcast swarm and its membership + broadcast state machines./iroh-gossip/1gossip

Gossip is a control-plane transport (default per-message ceiling 4096 bytes): documents ride it to fan out change notifications, and bulk content moves through blobs.

NamespaceId, AuthorId, and Entry

A document's identity model is three Ed25519-keyed nouns:

  • NamespaceId — a [u8; 32] newtype over an Ed25519 public key that names the document. Its secret, NamespaceSecret, is the write capability for the whole document.
  • AuthorId — a [u8; 32] public-key newtype naming a writer; its secret Author is an authorship proof. Any number of authors may write the same key.
  • Entry — the unit of state: Entry { id: RecordIdentifier, record: Record }, where RecordIdentifier is the triple (NamespaceId, AuthorId, key-bytes) and Record { len, hash, timestamp } is a metadata pointer (the content bytes live in blobs, fetched by hash). A SignedEntry carries two Ed25519 signatures over one canonical payload — one by the author, one by the namespace (sync.rs, keys.rs).

Conflicts resolve last-writer-wins (timestamp, then content hash); a deletion is an empty entry (Hash::EMPTY, len == 0), itself a signed, replicated fact. Peers converge by range-based set reconciliation — recursively fingerprinting ranges of the ordered key space and descending only where fingerprints disagree.

HyParView and Plumtree

The two classic algorithms gossip composes, each a faithful implementation of a Leitão et al. paper:

  • HyParView — the membership protocol (DSN 2007). Each node keeps a small active_view (size 5) of peers it holds live bidirectional connections to, plus a larger passive_view (size 30) address book it draws from to heal the active view after a failure. Random walks (ForwardJoin, Shuffle) diffuse membership across the swarm.
  • Plumtree — the broadcast protocol (SRDS 2007). Each active neighbor is either eager (receives full payloads) or lazy (receives only a blake3 message-id announcement, IHave). The eager set forms a low-redundancy spanning tree that carries each payload once per edge; the lazy set is a backstop that repairs gaps and continually re-optimizes the tree toward lowest latency by promoting a lazy peer to eager whenever it supplies a missing message.

Both run as one pure, IO-less state machine per topic (proto.rs), driven over per-peer noq QUIC connections by the net/ actor — the sans-io shape a D port drives directly on event-horizon fibers.


Terms at a glance

TermOne linePage
EndpointId / PublicKey32-byte Ed25519 key; the self-certifying endpoint name = verification keyidentity-crypto
SecretKey32-byte Ed25519 seed; signs TLS, relay, pkarr, docsidentity-crypto
EndpointAddrid + BTreeSet<TransportAddr> location hintswire
TransportAddrone hint: Relay | Ip | Customwire
RelayUrlURL of a relay serverrelay
EndpointIdMappedAddrsynthetic ULA so QUIC can dial a non-IP peersocket
postcardthe one non-self-describing binary wire codecwire
ALPNper-protocol negotiation byte string; router dispatch keyendpoint
Ticketbase32 shareable string; endpoint / blob / docwire
Socketmultipath fabric under QUIC (ex-magicsock)socket
Endpointpublic front door; one per appendpoint
Connectionone QUIC connection to one peer, multi-pathendpoint
Runtime / AsyncUdpSocketthe sans-io host seam a D port replacesquic
path / multipatha QUIC-Multipath path; relay & direct are concurrent pathsquic
QUIC Address DiscoverySTUN replacement; OBSERVED_ADDRESS framenat-traversal
NAT traversal (QNT)in-QUIC simultaneous-open hole-punchnat-traversal
address_lookupEndpointId → addresses directorydiscovery
pkarrself-authenticating signed-DNS-packet record formatdiscovery
relay / home relayencrypted-datagram forwarder / lowest-latency relayrelay
net_reportconnectivity-sensing report (ex-netcheck)net-report
Hash / BLAKE332-byte content addressblobs
bao / outboardverified-streaming tree / stored interior hashesbao-tree
ChunkRangesRLE sorted set of chunk ranges to fetch/verifyblobs
HashSeq / Collectionconcatenated-hashes blob / named-directory conventionblobs
NamespaceId / AuthorId / Entrydoc identity + signed (ns, author, key) factdocs-sync
HyParView / Plumtreemembership / eager-lazy broadcastgossip

Sources