iroh Deep Dive: A Dial-by-Key P2P Transport Stack as an Alternative to libp2p
In the P2P transport-stack space, libp2p has long been the default — but its protocol stack is large and its configuration surface wide; every new project ends up choosing combinations from a long list of features (tcp/quic/webrtc/noise/yamux/identify/kad/relay/dcutr). iroh is the Rust library that takes the other road: it does only QUIC + dial-by-public-key + NAT traversal, collapses the transport layer into something small, and layers three optional modules on top (blobs/gossip/docs). On June 15, 2026, n0 released iroh 1.0 with the slogan “Dial Keys, not IPs” — the first stable release after 4 years of development and 65 pre-release versions.
This post surveys iroh’s full technical landscape and contrasts it with the libp2p material already in this series (“Rust P2P Development in Practice”, “The libp2p Stack and BitTorrent”, “P2P Signaling and Relay Server Survey”), answering one question: in which scenarios is iroh a better fit than libp2p, and in which is it not.
Core Concepts
| Concept | One-line explanation |
|---|---|
| iroh | A Rust P2P transport library maintained by n0; the core crate provides end-to-end encrypted direct connections keyed by public key |
| NodeId | An Ed25519 public key (32 bytes); the unique node identity in iroh, used directly as a dialing address |
| SecretKey | The Ed25519 private key paired with NodeId; persistence is left to the developer |
| NodeAddr | Everything needed to reach a node: NodeId + candidate RelayUrl list + direct address list |
| Endpoint | iroh’s main entry point — analogous to libp2p’s Swarm; handles listening, dialing, and accepting |
| ALPN | TLS Application-Layer Protocol Negotiation; iroh uses it to multiplex multiple custom protocols on the same QUIC connection |
| Relay | iroh’s relay server, an HTTP/QUIC protocol — stateless, unlike libp2p’s stateful relay |
| pkarr | A signed-record publication protocol over the BitTorrent mainline DHT; iroh uses it for node discovery |
| iroh-blobs | Content-addressed data transfer over iroh: BLAKE3 hashing + verified streaming |
| iroh-gossip | Group message broadcast based on Plumtree (Epidemic Broadcast Trees) |
| iroh-docs | A CRDT-based multi-dimensional key-value store layered on blobs + gossip |
| Ticket | A serializable connection credential that bundles NodeAddr + protocol parameters into a shareable string |
Project Positioning and History
iroh is maintained by n0 (N0, Inc.). The core team comes from the Radicle project (decentralized code collaboration), so iroh was designed from the start for the “let any two devices sync data directly” use case. The source lives at GitHub n0-computer/iroh, licensed Apache-2.0 OR MIT, pure Rust.
The version history reveals the direction:
| Stage | Versions | Shape |
|---|---|---|
| Early (2022–2023) | pre-0.3 | IPFS-like content-addressed store; included bitswap, UnixFS |
| Convergence (2023–2024) | 0.10 – 0.20 | Spun out iroh-net; positioned as “direct-connection transport library”; storage layer de-emphasized |
| Refactor (2024–2025) | 0.25 – 0.30 | Introduced Endpoint::builder + ALPN + ProtocolHandler; later moved to accept + custom protocols |
| Split (2025) | around 0.90 | Moved iroh-blobs/iroh-gossip/iroh-docs/iroh-sync to separate repos; core repo kept only transport |
| Stable (2026-06) | 1.0 | GA; API stability promised; n0’s public relay service commercialized |
The current (2026-07) repo layout:
n0-computer/iroh— core; containsiroh(Endpoint/connections),iroh-relay(relay protocol and server),iroh-base(basic types like NodeId/Hash),iroh-dns-server(DNS server for pkarr)n0-computer/iroh-blobs— BLAKE3 content-addressed transfern0-computer/iroh-gossip— Plumtree group broadcastn0-computer/iroh-docs— CRDT key-value storen0-computer/iroh-examples— official example collection
Note that iroh renamed and split crates several times during 0.x (iroh-p2p → iroh-net → iroh). When reading older blog posts or code, mind the version. All API references in this post are against 1.0.
Design Philosophy: Dial-by-Key and QUIC-First
iroh’s core ideas can be condensed to two points:
1. Dial a Public Key, Not an Address
In libp2p, to connect to a node you first need a Multiaddr (e.g. /ip4/1.2.3.4/udp/4001/quic-v1), then extract the PeerId from it to verify identity. IP and PeerId are two separate pieces of information: IPs change (home broadband, mobile networks, laptops on different Wi-Fi), PeerIds don’t, but the application has to deal with “how do I get the current IP” every time — usually through DHT, rendezvous, or relay message passing.
iroh flips this abstraction: you dial a NodeId (a public key), not an address. How to actually reach it is decided internally by the Endpoint:
- First try known direct addresses;
- If direct connection fails, fall back to a relay;
- While relaying, attempt hole-punching and upgrade to direct on success.
The application only ever sees endpoint.connect(node_addr, ALPN). The relay and direct addresses in NodeAddr are merely “hints” — you can connect even if some are missing (relay fallback) or stale (re-discovered via pkarr DHT). Upper-layer code never has to care about “which IP is the device on right now”.
2. QUIC Only
iroh does not provide pluggable transports like libp2p’s TCP/QUIC/WebRTC/WebSocket. It supports QUIC only (built on the quinn crate), running TLS 1.3 directly on top. This decision has several direct consequences:
| Decision | Benefit | Cost |
|---|---|---|
| QUIC-only | TLS 1.3 built in; ALPN native; multiplexing native; 0-RTT reconnect | Browser clients need a WebTransport bridge; no direct use |
| Always encrypted | No “raw transport + upper-layer encryption” ambiguity | Must accept the TLS certificate model |
| Single transport | Simple implementation, small bug surface | In UDP-blocking networks (some corporate nets), relay is the only option |
This trade-off is explicit: iroh serves “device-to-device” application-layer communication (sync, file transfer, private protocols), not “browser-to-server” web apps. If your target platform is the browser, iroh is not the right choice.
Layered Architecture
iroh is a layered but independently usable stack. Upper layers depend on lower ones but nothing is forced:
flowchart TD
A["App layer<br/>custom protocol / iroh-docs"] --> B["iroh-gossip<br/>Plumtree group broadcast"]
A --> C["iroh-blobs<br/>BLAKE3 content addressing"]
B --> D["iroh core<br/>Endpoint / QUIC conn"]
C --> D
D --> E["Discovery<br/>pkarr DHT / DNS / static"]
D --> F["Relay<br/>iroh-relay (stateless)"]
classDef app fill:#E8F5E9,stroke:#4CAF50,color:#1B5E20
classDef proto fill:#E3F2FD,stroke:#2196F3,color:#0D47A1
classDef base fill:#FFF3E0,stroke:#FF9800,color:#BF360C
classDef infra fill:#F3E5F5,stroke:#9C27B0,color:#4A148C
class A app
class B,C proto
class D base
class E,F infrairoh’s layering is “pay for what you use”: for a direct connection between two nodes running a custom protocol, only the core iroh crate is needed; add iroh-gossip for group broadcast; iroh-blobs for file/content transfer; iroh-docs only when you need CRDT semantics (collaborative editing, state sync). Each layer has a clear responsibility boundary — unlike libp2p, which stuffs Gossipsub/Kad/Identify into a single Swarm.
Core: Endpoint and Connection Lifecycle
What the iroh core crate provides is exactly one thing: “establish an end-to-end encrypted QUIC connection”. An iroh connection is fundamentally a QUIC connection with an identical API — you can open unidirectional and bidirectional streams; what protocol runs on each stream is identified by ALPN.
Endpoint is the core entry point, equivalent to libp2p’s Swarm but with a smaller API surface:
| |
On the accepting side:
| |
Key points:
Endpoint::builder().bind()does all the underlying work: binds the UDP socket, registers ALPNs, starts the relay client, starts discovery.connect()takes aNodeAddr, andNodeAddr = NodeId + relay_urls + direct_addresses. Any subset is connectable — relay as fallback, pkarr for re-discovery.- A connection is a QUIC connection. All custom protocols run as multiple streams on the same connection via
open_bi/accept_bi; ALPN decides the initial handshake protocol.
This API has a much smaller cognitive load than libp2p’s SwarmBuilder + NetworkBehaviour + a pile of From implementations.
iroh-blobs: BLAKE3 Content Addressing
iroh-blobs is the content-addressed data transfer protocol over iroh — like a simplified bitswap, but with key differences:
| Dimension | iroh-blobs | IPFS/bitswap |
|---|---|---|
| Hash | BLAKE3 | SHA2-256 / multihash |
| Data unit | blob + sequence (sequence of blobs) | block (~256KB chunk) |
| Transfer | request-response, BLAKE3 verified streaming | block-level exchange, demand-driven |
| Verification | streaming verification, integrity checked during download | block-level verification |
iroh-blobs’ “verified streaming” relies on BLAKE3’s streaming hash tree: each chunk’s hash is derivable from the root hash, so data at any position can be verified on the fly without waiting for the whole blob. This makes chunked download and resume of large files natural.
Minimal usage (write a blob to the local store and generate a shareable ticket):
| |
The receiving side uses the same client to request from the peer and verify.
iroh-gossip: Plumtree Group Broadcast
iroh-gossip uses Plumtree (Epidemic Broadcast Trees, INFORUM 2007), not a random-mesh protocol like libp2p’s Gossipsub.
The two differ at the mechanism level:
| Dimension | Plumtree (iroh-gossip) | Gossipsub (libp2p) |
|---|---|---|
| Topology | Maintains one or more trees of “reliable links” | Maintains a random mesh; each node connects to mesh_n neighbors |
| Forwarding | Along the tree; each message traverses O(N-1) edges | Each node forwards to mesh neighbors, with IWANT/IHAVE for dedup |
| Healing | When a tree breaks, random gossip links re-fill and rebuild | When mesh health degrades, swap neighbors |
| Fit | Stable subscriber groups, message-forwarding-cost-sensitive | Frequent node join/leave, large-scale dynamic subscription |
iroh-gossip’s API is topic-based: a node join(topic), and any broadcast to that topic propagates along the Plumtree tree to all subscribers. It’s close to libp2p’s gossipsub.publish(topic, msg) in shape, but the underlying mechanism is completely different.
iroh-docs: CRDT Key-Value Store
iroh-docs is the topmost “meta protocol” — essentially a CRDT-based (specifically iroh-sync’s automerge-like implementation) multi-dimensional key-value store. Each document is a Map<Author, Key, Value>, where Author is a signer identity and Value is serialized to a blob in the blobs layer.
It stacks on top of blobs + gossip:
- Write: the author signs an entry with the Author private key; the entry’s value is written to the blobs layer (yielding a BLAKE3 hash).
- Sync: gossip broadcasts the new entry hash to group members.
- Merge: every node merges received entries under CRDT rules; results converge.
Typical scenarios for iroh-docs are collaborative editing, shared configuration, multi-device sync — anything that needs “eventual consistency across endpoints”. Sharing a document is done via a DocTicket; after joining, the full document history syncs automatically.
Design Philosophy Compared with libp2p
iroh and libp2p are not “old vs new” — they are two different design philosophies. Read this alongside the libp2p posts in the series and the contrast becomes sharper.
Identity and Addressing
| Dimension | iroh | libp2p |
|---|---|---|
| Node identity | NodeId (Ed25519 public key, 32 bytes) | PeerId (multihash, usually derived from a public key) |
| Dialing unit | NodeAddr (NodeId + relay + direct addresses) | Multiaddr (/ip4/.../udp/.../quic-v1) |
| Identity vs address | Identity is the public key; addresses are connection hints | Address comes first; identity extracted and verified |
| Who handles “find current address” | Endpoint internals (relay + pkarr DHT) | Application layer (DHT / rendezvous / bootstrap) |
The upside of iroh’s design is that upper-layer code is free from address drift. The cost is that you have to trust iroh’s discovery layer (by default n0’s public pkarr nodes and n0’s public relay; self-hosting requires extra deployment).
Transport
| Dimension | iroh | libp2p |
|---|---|---|
| Transport | QUIC only (built on quinn) | TCP / QUIC / WebRTC / WebSocket / Bluetooth; pluggable |
| Encryption | TLS 1.3 built into QUIC | Noise (libp2p’s own protocol), TLS optional |
| Multiplexing | Native to QUIC | yamux / mplex (StreamMuxer) |
| Protocol multiplexing | ALPN | StreamMuxer + Protocol ID (/ipfs/ping/1.0.0) |
iroh’s use of ALPN for protocol multiplexing is the more “standard” path (HTTP/2 and HTTP/3 both use ALPN). libp2p’s Protocol ID strings are more flexible but you manage versioning yourself.
NAT Traversal and Relay
This is iroh’s largest engineering difference from libp2p:
| Dimension | iroh relay | libp2p relay (DCUtR) |
|---|---|---|
| Server state | Stateless | Stateful (maintains connection mappings) |
| Protocol | HTTP/2 or QUIC | own relay protocol (over libp2p stream) |
| Horizontal scaling | Easy (any stateless load balancer) | Hard (sticky sessions or inter-node state sync) |
| Upgrade to direct | auto-switches on hole-punch success | explicit DCUtR protocol upgrade |
| Commercial relay service | n0 runs public relays, free + commercial tiers | no official public relay; community-operated |
The reason iroh relay can be stateless is that it reuses QUIC connections — the client-to-relay connection is a single QUIC connection; the relay only forwards and does not decrypt; the connection itself is maintained by the QUIC state machines on both ends. libp2p relay is built on StreamMuxer, so the relay must maintain a stream-to-peer mapping.
When n0 announced 1.0, they disclosed that their public relay already carried 200 million connections — only possible at that scale because of statelessness.
Stack Size
From a dependency perspective, iroh’s stack is more compact:
| Dimension | iroh core | rust-libp2p 0.54 |
|---|---|---|
| Feature flags | Few; mostly ALPN/discovery/relay | Dozens (one feature per protocol) |
| Default binary size | Smaller | Larger (depends on noise/yamux/quinn/tls and more) |
| Minimum-viable lines of code | ~20 (see comparison below) | ~50+ |
iroh’s design assumption is “you just want an encrypted direct connection”; libp2p’s assumption is “you may be building a full P2P system that needs Kad/Gossipsub/PubSub/Identify”. Both are reasonable for their targets.
Minimal Node Code, Side by Side
Implementing the same need — “two nodes establish an encrypted direct connection and exchange one message” — in iroh and rust-libp2p makes the API difference concrete.
iroh Version
| |
Cargo.toml:
| |
rust-libp2p Counterpart (Same Need)
The libp2p equivalent (see “Rust P2P Development in Practice” in this series), for the same “encrypted connection + one message” goal, requires:
SwarmBuilder::with_new_identity()to generate identity.with_tokio().with_quic()to configure transport.with_ping()to compose the NetworkBehaviour- listen + dial + event loop matching
SwarmEvent+ping::Event Fromimplementations to route behaviour events to swarm out_event
That’s roughly 2–3× the code of iroh, and you need to understand the Swarm / Behaviour / Event triad. This doesn’t mean libp2p is worse — it built these abstractions to support complex protocol compositions (Kad + Gossipsub + Identify simultaneously). But if what you want is “an encrypted direct connection running a custom protocol”, iroh’s abstraction level is closer to the need.
Selection Boundaries: When iroh, When Not
iroh’s strengths cluster in the peer-to-peer direct connection + device-to-device sync quadrant. Set against other scenarios in the series:
Scenarios Where iroh Fits
- Multi-device sync: notes, configs, photo albums that need to sync across a user’s own devices. iroh-docs’ CRDT model fits naturally.
- File transfer and sharing: iroh-blobs’ BLAKE3 verified streaming fits large files, resume, and content verification.
- Private end-to-end encrypted channels for custom protocols: embedded devices, IoT gateways, edge nodes running custom protocols — iroh core + ALPN is enough.
- Need NAT traversal but don’t want to self-host TURN: n0’s public relay starts free, and self-hosting relay is much cheaper than TURN because of statelessness.
Scenarios Where iroh Does Not Fit
- Browser-side P2P: iroh only supports QUIC; browsers cannot use it directly. You need an extra WebTransport bridge or gateway. If your target is WebRTC-compatible browser communication, libp2p’s WebRTC transport or native WebRTC is more appropriate (see “NVR Remote Access P2P Technical Survey”).
- Large-scale decentralized routing: iroh’s pkarr discovery depends on the BitTorrent mainline DHT and has no large-scale Kad routing layer of its own. If your application is a public-chain node, a file-sharing public network (millions sharing one DHT), libp2p’s Kad implementation is more mature (see “Kademlia DHT Protocol Deep Dive”, “The libp2p Stack and BitTorrent”).
- Multi-language clients: iroh is Rust-first; there is no complete official equivalent in Go/JS/Python. libp2p has Go, JS, Rust, Python implementations — for cross-language P2P systems, libp2p is the default.
- Forced TCP/HTTPS networks: iroh runs QUIC only (UDP); in some corporate networks that block UDP, relay is the only fallback. If your target network is UDP-unfriendly, libp2p’s TCP + Noise path is more robust.
- Complex protocol composition (Kad + Gossipsub + Identify + PubSub): libp2p’s Swarm/Behaviour abstraction is designed for exactly this; iroh requires you to assemble the pieces yourself.
Decision Tree
flowchart TD
A["Need P2P comm"] --> B{"Browser as client?"}
B -->|"yes"| C["libp2p WebRTC<br/>or native WebRTC"]
B -->|"no"| D{"Cross-language clients?"}
D -->|"yes"| E["libp2p (Go/JS/Rust)"]
D -->|"no, Rust only"| F{"Need large-scale DHT routing?"}
F -->|"yes, public scale"| G["libp2p Kad"]
F -->|"no, device-to-device"| H{"Main scenario?"}
H -->|"file/content transfer"| I["iroh-blobs"]
H -->|"multi-device state sync"| J["iroh-docs"]
H -->|"custom protocol direct"| K["iroh core"]
H -->|"group message broadcast"| L["iroh-gossip<br/>or libp2p Gossipsub"]
classDef pick fill:#E8F5E9,stroke:#4CAF50,color:#1B5E20
classDef alt fill:#E3F2FD,stroke:#2196F3,color:#0D47A1
class C,E,G alt
class I,J,K,L pickThis tree isn’t absolute — iroh-gossip and libp2p Gossipsub overlap heavily in the “group broadcast” scenario. If the team is already on libp2p, there’s no need to switch to iroh-gossip for a group or two.
Self-Hosting and Production Considerations
After iroh 1.0, n0 commercialized its public relay service: a free tier with connection caps and a commercial tier priced by volume. If your application does not depend on n0’s public service, self-hosting has several things to consider.
Self-Hosting Relay
iroh-relay lives in the n0-computer/iroh repo with a server implementation you can deploy standalone. Its stateless nature means:
- It can sit behind any load balancer (Cloudflare, nginx, HAProxy) without sticky sessions.
- Horizontal scaling is just adding machines; no inter-node state sharing.
- A single-instance failure does not affect established direct connections (only relayed traffic); clients automatically switch to another relay or retry.
Compared with self-hosting TURN (see the TURN section in “P2P Signaling and Relay Server Survey”), iroh relay’s ops cost is noticeably lower.
Self-Hosting pkarr Discovery
iroh does pkarr discovery by default through the BitTorrent mainline DHT (nodes sign their NodeId + current address and publish it to the DHT). This discovery mechanism is decentralized in principle and theoretically needs no self-hosting. But in corporate intranets or restricted networks where the mainline DHT is unreachable, you need:
- a mainline DHT bootstrap node;
- or iroh’s DNS discovery (pkarr records encoded as DNS TXT records via a self-hosted DNS server).
The iroh repo ships iroh-dns-server for this.
Licensing and Supply Chain
iroh is Apache-2.0 OR MIT across the stack — commercial-friendly dual licensing. But a few supply-chain risks are worth noting:
- Before iroh 1.0 the API changed significantly multiple times (crate renames, module reorganizations), and migrating old code is non-trivial. 1.0 promises API stability, but surrounding crates like
iroh-blobs/iroh-docsare still iterating fast. - n0 itself is a commercial company; the public relay service has an EOL policy (relays for 0.x versions are already deprecated). If your application depends on n0’s public relay, watch their terms-of-service changes.
- iroh’s team is smaller than the libp2p protocol ecosystem (libp2p is maintained by Protocol Labs, backed by the whole IPFS/Filecoin ecosystem). iroh’s long-term sustainability depends on whether n0’s commercialization works out.
Relationship to Other Posts in the Series
This post is the entry point for the iroh line in the “P2P Application Surveys” chapter. For related topics in depth, see other posts in the series:
- Transport and protocol fundamentals: “P2P Core Principles”, “The libp2p Stack and BitTorrent”, “Kademlia DHT Protocol Deep Dive”, “Gossip Protocol Core Principles” — the classical algorithms behind iroh’s layers.
- Code in practice: “Rust P2P Development in Practice: From Ping to Gossipsub” — the libp2p counterpart code referenced by the side-by-side comparison here.
- Engineering practice: “P2P Signaling and Relay Server Survey” — the full landscape of STUN/TURN/ICE/DERP; iroh’s relay design fits into this lineage.
- Application scenarios: “NVR Remote Access P2P Technical Survey” — P2P practice in surveillance/IoT; a potential landing zone for iroh-blobs/iroh-docs.
References
Official
- iroh GitHub (core repo). https://github.com/n0-computer/iroh
- Iroh 1.0 — Dial Keys, not IPs (1.0 release blog). https://www.iroh.computer/blog/v1
- What is Iroh? (official docs). https://docs.iroh.computer/what-is-iroh
- Comparing Iroh & Libp2p: Simplifying P2P Connectivity (official comparison). https://www.iroh.computer/blog/comparing-iroh-and-libp2p
- Documents Protocol. https://docs.iroh.computer/protocols/documents
- Tickets concept. https://docs.iroh.computer/concepts/tickets
- iroh crate (docs.rs). https://docs.rs/iroh
- iroh::endpoint::Builder. https://docs.rs/iroh/latest/iroh/endpoint/struct.Builder.html
Surrounding Repos
- iroh-blobs. https://github.com/n0-computer/iroh-blobs
- iroh-gossip. https://github.com/n0-computer/iroh-gossip
- iroh-docs. https://github.com/n0-computer/iroh-docs
- iroh-examples. https://github.com/n0-computer/iroh-examples
- iroh-workshop (39c3 workshop). https://github.com/n0-computer/iroh-workshop-39c3
Deep Dives
- Deep dive into iroh: A replacement for WireGuard or a P2P framework for your applications? (kerkour.com). https://kerkour.com/iroh-v1-p2p
- The Wisdom of Iroh (LambdaClass). https://blog.lambdaclass.com/the-wisdom-of-iroh/
Protocol Papers and RFCs
- Leitão, J., Pereira, J., Rodrigues, L. Epidemic Broadcast Trees (Plumtree, the basis of iroh-gossip). 2007 IEEE International Symposium on Reliable Distributed Systems.
- RFC 9000 — QUIC: A UDP-Based Multiplexed and Secure Transport. https://datatracker.ietf.org/doc/rfc9000/
- RFC 7301 — TLS Application-Layer Protocol Negotiation Extension (the basis for iroh’s ALPN multiplexing). https://datatracker.ietf.org/doc/rfc7301/
- pkarr (signed records over the mainline DHT). https://pkarr.org
Community Discussion
- Iroh: A library to establish direct connection between peers (HN thread). https://news.ycombinator.com/item?id=44379173
- Iroh 1.0 HN thread. https://news.ycombinator.com/item?id=48542480
- libp2p-iroh: PeerId based Dialing (rust-libp2p bridging iroh transport). https://discuss.libp2p.io/t/libp2p-iroh-peerid-based-dialing-behind-any-nat/3672