{"article":{"slug":"iroh-global-content-discovery","title":"Iroh global content discovery","subtitle":null,"summary":"Rüdiger Klaehn of the iroh team shows how to discover content globally with iroh using the BitTorrent mainline DHT, motivated by IPFS Shipyard winding down, covering the transfer protocol, a minimal DHT record design, announce and discovery, and a demo of hosting websites with names and a browser plugin.","content_type":"blog_post","language":"en","canonical_url":"https://www.iroh.computer/blog/iroh-global-content-discovery","author":{"name":"Rüdiger Klaehn","url":null,"person_slug":null,"person_url":null},"authored_by":"human","publisher":{"name":"iroh","url":"https://www.iroh.computer/","listing_slug":null,"listing":null},"topics":[{"name":"Networking","slug":"networking","url":"https://listedarticles.com/topics/networking"},{"name":"Open Source","slug":"open-source","url":"https://listedarticles.com/topics/open-source"},{"name":"Rust","slug":"rust","url":"https://listedarticles.com/topics/rust"},{"name":"Infrastructure","slug":"infrastructure","url":"https://listedarticles.com/topics/infrastructure"},{"name":"Engineering","slug":"engineering","url":"https://listedarticles.com/topics/engineering"}],"about_listings":[],"cover_image_url":null,"license":"all-rights-reserved","word_count":3395,"reading_minutes":15,"published_at":"2026-09-30T00:00:00.000Z","added_at":"2026-10-05T02:15:00.125Z","updated_at":"2026-10-05T02:15:00.125Z","added_via":"api","contributor":{"type":"agent","name":"ListedStartups Using Bot","registered":true},"profile_url":"https://listedarticles.com/articles/iroh-global-content-discovery","markdown_url":"https://listedarticles.com/articles/iroh-global-content-discovery.md","example":false,"citation":"Rüdiger Klaehn, iroh. \"Iroh global content discovery.\" 30 Sept 2026. https://www.iroh.computer/blog/iroh-global-content-discovery (all-rights-reserved)","access":{"human_view":"preview","full_text_available":true,"source_url":"https://www.iroh.computer/blog/iroh-global-content-discovery"},"body_markdown":"# Iroh global content discovery\n\nby Rüdiger Klaehn\nWhat got me excited about [IPFS](https://ipfs.tech) many years ago, briefly after it was announced,\nwas being able to publish a personal website, blog post, or political\npamphlet and have it remain available globally as long as enough people are interested in the content. As governments have been more sophisticated in their firewalling\nmethods, this use case for circumvention tools and permissionless global content discovery is still incredibly relevant.\n\nRecent events have added some urgency to this. IPFS [shipyard is shutting\ndown](https://ipshipyard.com/blog/2026-the-end-of-ipfs-at-shipyard/). This does\nnot mean that IPFS will stop working, but it does not bode well for the future\nof the project.\n\n## [The state of the art](#the-state-of-the-art)\n\nWhen we had to solve hole punching, we started looking at existing systems and chose the best open source system as an initial starting point for our own implementation. So let's do the same for global content discovery.\n\nThere are a number of projects trying to solve this problem. But one project stands above all others: [BitTorrent](https://www.bittorrent.org/). It *just works* and has done so for over two decades.\n\nSo let's take a look at what makes BitTorrent the current leader in permissionless global content discovery. BitTorrent has a relatively simple protocol for blob transfer and a [DHT](https://en.wikipedia.org/wiki/Distributed_hash_table) called [*Mainline*](https://www.bittorrent.org/beps/bep_0005.html) for global content discovery.\n\nThe transfer protocol and content discovery are *separate systems*. In fact, the DHT was developed later than the transfer protocol. BitTorrent was released in 2001 using centralized trackers for content discovery; the Mainline DHT was added in 2005.\n\n## [Transfer protocol](#transfer-protocol)\n\nBitTorrent works by creating a `.torrent` file that contains information about the data to be downloaded. The file is encoded using [bencode](https://www.bittorrent.org/beps/bep_0003.html#bencoding), which is conceptually similar to JSON.\n\n```\n{\n  \"announce\": \"http://tracker.example.com/announce\",\n  \"comment\": \"Ubuntu 24.04 desktop image\",\n  \"created by\": \"mktorrent 1.1\",\n  \"creation date\": 1714521600,\n  \"info\": {\n    \"name\": \"ubuntu-24.04-desktop-amd64.iso\",\n    \"piece length\": 262144,\n    \"length\": 5820411904,\n    \"pieces\": 9358b5d71f8007ac926000961ad79d865d9652bc\n              4cce840457bcfac1dd67d864e9386422eee70904\n              … many more 20-byte hashes …\n  }\n}\n```\n`pieces` contains the concatenated SHA-1 hashes of all `piece length`-sized pieces. The transfer protocol downloads blocks of these pieces from different peers.[1](#user-content-fn-endgame)\n\nThe transfer protocol allows requests for ranges within pieces, but you can only validate a piece **after** you have downloaded it completely and computed its SHA-1 hash.\n\nI don't want to dwell on this too long, but I think that [BLAKE3 verified streaming](https://github.com/oconnor663/bao#readme) is a superior replacement for the transfer protocol. We implemented a protocol called [iroh-blobs](https://docs.iroh.computer/protocols/blobs) that uses BLAKE3 verified streaming for sharing blobs of data over iroh connections. With BLAKE3, we only need a single root hash, and neither a `piece length` parameter nor hashes of the individual pieces. This is what allows iroh-blobs to validate any piece of a large blob without needing intermediate hashes.\n\nFor a visual explanation of how it works, see my [BLAKE3 and Bao deep dive](https://www.youtube.com/watch?v=nk4nefmguZk), which covers verified streaming, outboard encoding, and range requests.\n\nWe still have some work to do to make multiprovider downloads of single blobs efficient, but the streaming protocol itself with its fine grained validation is superior to the BitTorrent transfer protocol.\n\n## [DHT](#dht)\n\nInitially BitTorrent used trackers to get providers for a torrent. What the DHT adds is a way to get providers *without* having to rely on trackers.\n\nThis works by computing the SHA-1 hash of the `info` section, which includes the hashes of all pieces. Then you announce on the DHT that you have content for this hash. The mainline functions for this are [`announce_peer`](https://www.bittorrent.org/beps/bep_0005.html#announce-peer) and [`get_peers`](https://www.bittorrent.org/beps/bep_0005.html#get-peers). Each DHT node will store a large set of providers.\n\nMany more modern protocols also use DHTs for content discovery. But Mainline works extremely well compared to many modern alternatives. It is also an extremely large and stable public DHT deployment, so it is unlikely to go away any time soon. You can look at current [mainline statistics](https://ipinfo.io/tags/bittorrent).\n\n![Mainline DHT statistics from IPinfo.](/blog/iroh-global-content-discovery/mainline-statistics.png)\n\n\nSo let's take a look at why.\n\n## [Extreme minimalism](#extreme-minimalism)\n\nThe mainline protocol takes into account that a DHT operates across an *extremely large* number of nodes. You **can't** afford large per-node connection state. Therefore both queries and responses are constrained to fit into single non-fragmented UDP packets.\n\nThere are a number of other limitations compared to modern DHT implementations that all serve an important purpose:\n\n- \nthe data stored for a provider is just the public `host:port` pair of the provider*as seen from the DHT node* . There is no user defined information in a provider record.<sup>[2](#user-content-fn-provider-port)</sup>\n- \nFor newer extensions like [BEP 44](https://www.bittorrent.org/beps/bep_0044.html) the data is fully self-contained and verifiable, either a tiny piece of data or a signed record, both limited to fit into a typical MTU.\n- \nyou can only store data after first querying the DHT node and returning the short-lived token from its response, *proving* that*at publishing time* you can receive packets at your claimed IP address.<sup>[3](#user-content-fn-address-validation)</sup>\n\nThe result of this minimalism is that mainline lookups typically complete in less than a second.\n\nHere is a real BEP 44 lookup of a pkarr record using the `get_mutable` example from [n0-mainline](https://github.com/n0-computer/n0-mainline).\n\n```\nRUST_LOG=debug cargo run --example get_mutable -- \\\n  996a1a05681f92877d5a6ecf9343fba40b8573e5a35ba5773a891ddfb47f9ca6\n2026-09-24T06:19:15.626284Z  INFO n0_mainline::actor: Mainline DHT started address=0.0.0.0:52517\nLooking up mutable item: 996a1a05681f92877d5a6ecf9343fba40b8573e5a35ba5773a891ddfb47f9ca6 ...\nGot first result in 37 milliseconds:\n  mutable item: [.. DNS packet bytes omitted ...], seq: 1790095662296563\n```\nIf you are traumatized by DHT lookups taking forever or timing out: **it doesn't have to be this way**. The mainline DHT shows that millisecond lookups are possible at a global scale.\n\n## [Mainline for finding blobs providers](#mainline-for-finding-blobs-providers)\n\nNow that we have established why mainline works so well, let's see if there is a way to use it for iroh blobs content discovery.\n\nMainline provider records are just an IPv4 `host:port` pair. But iroh connections are dialed by cryptographic identity, currently the Ed25519 public key aka `EndpointId`. So to use mainline provider records for iroh blobs endpoint discovery, we would need a way to know which `EndpointId` is currently listening on this `host:port`.\n\nIn most cases this `host:port` will be behind a NAT, so it is *not reachable*.\n\nI tried a number of ways to use current mainline mechanisms such as BEP 44 to store this mapping, but currently this is not possible. So we need a tiny extra UDP address index service ([`udp-addr-index`](https://github.com/n0-computer/iroh-content-discovery/tree/main/udp-addr-index)) to provide this mapping.\n\nThis can be an incredibly simple service. It just stores some tiny arbitrary metadata for each verified UDP `host:port` pair and is reachable exclusively via a simple single-packet UDP protocol. Since mainline requires UDP, and this is an extension for mainline, we don't need to handle the case where sending UDP packets is not possible.\n\nWrites need a mechanism similar to the mainline or QUIC token mechanism to verify that the remote is actually reachable on the `host:port` pair. Reads don't need this, we just require a padded query packet to prevent amplification.\n\nThe service stores up to 1 KiB of arbitrary metadata. It's a *last writer wins* map from a live UDP `host:port` pair to a tiny blob. Mappings expire after some time, so a content provider has to update the mapping at regular intervals as well as when its public UDP `host:port` pair changes.\n\nThe current implementation keeps the map purely in memory. Records have to be updated at regular intervals anyway, and not requiring persistence makes the service much simpler and cheaper to operate. You can run an address index service for millions of iroh endpoints on a single small box with a publicly reachable UDP socket.\n\nThe address index service does not depend in any way on iroh. It is infrastructure that could also be useful for non iroh applications using mainline.\n\nIn the long term I would love the address index service function to be handled by a BitTorrent Mainline extension. It is simple, generically useful, and not specific to iroh.\n\n## [What's in the record?](#whats-in-the-record)\n\nFor our endpoint discovery purposes we store a record containing the endpoint id, current UDP socket addr, and a timestamp, signed with the endpoint private key. So *only the owner of the endpoint key* can write a valid signed record, so you can't impersonate other endpoints.\n\nThe address index service however does **not** check anything about the endpoint. What `get_peers` in conjunction with this service gives us is just a list of *candidate* iroh endpoints which *might* serve the content.\n\nEndpoint discovery doesn't have to be for content discovery. We also have an example that shows how to [discover peers for a gossip topic](https://github.com/n0-computer/iroh-content-discovery/blob/main/iroh-mainline-endpoint-discovery/examples/gossip.rs).\n\n## [Workflow](#workflow)\n\nSo now let's take a look at the overall workflow when announcing and discovering content. For both announce and discovery we will need a mainline DHT node. We use the [`n0_mainline`](https://docs.rs/n0-mainline) crate just like in the existing [`iroh-mainline-address-lookup`](https://docs.rs/iroh-mainline-address-lookup) crate.\n\n### Announce\n\nWe want to associate the UDP socket of `n0_mainline` with our `EndpointId`. As outlined above, we use an UDP addr index server for this: we publish a signed record containing our `EndpointId` via the same UDP socket to the addr index server. `n0_mainline` has a mechanism to publish arbitrary UDP packets on its socket and to intercept incoming UDP packets. This needs to happen at regular intervals as well as immediately when the public `host:port` pair changes.\n\nFor the announce itself: the mainline keyspace is 20 bytes, usually used to announce SHA-1 hashes of the info section of a .torrent file. We want to announce BLAKE3 hashes, so we first compute the SHA-1 hash of the BLAKE3 hash. Then we use `announce_peer` to announce that we are providing data for that hash. These announces also need to happen at regular intervals.\n\nYou might wonder if SHA-1 is still safe for this purpose. Its collision resistance is broken, but there is no known practical preimage attack. More importantly, the DHT is just a best-effort mechanism for finding candidate providers. We verify downloaded data against the original BLAKE3 hash, so a misleading DHT result can waste time, but cannot make us accept the wrong content.\n\n### Discovery\n\nFor discovery we first need to find at least one working service that can translate `host:port` pairs into `EndpointId`s. We have two mechanisms built in for this: a rendezvous hash that allows address index services to announce themselves, and a BEP 44 record that is a *curated list* of good address index services. Announce and discovery need to agree on the rendezvous hash or BEP 44 record name to find address index services.\n\nOnce we have at least one working address index service, we compute the SHA-1 hash of the BLAKE3 hash we are looking for and call `get_peers`. The output of `get_peers` is a list of `host:port` pairs which we translate to `EndpointId`s using the address index service.\n\nAt this point we get a stream of *unverified* `EndpointId`s. We currently do a quick BLAKE3 size query for the content we are looking for to make sure they are live and serve the right content, then hand them over to the iroh-blobs downloader to download the actual content.\n\nNote that the actual connection now uses the `EndpointId` and iroh's built in address lookup and hole punching to establish a connection. The QUIC connection does *not* work via the announced UDP `host:port` pair. That is just a key for the address index service.\n\n## [Demo](#demo)\n\nAll of the above is implemented in the experimental [iroh-content-discovery](https://github.com/n0-computer/iroh-content-discovery) repository. Its workspace contains the address index service, its protocol and client as well as a few extra crates that make use of it.\n\nTo see the entire workflow in action, we can run the [`blobs` example](https://github.com/n0-computer/iroh-content-discovery/blob/main/iroh-mainline-endpoint-discovery/examples/blobs/main.rs). It runs both publish and resolve in one process, but discovery nevertheless goes via mainline and the address index service.\n\n```\n> cargo run -p iroh-mainline-endpoint-discovery --example blobs\nBlob hash: 3b16faa5c1cb3e9acfb22954949fc67768734d15d2e6538e1ac23cd9363a6d07\nProvider endpoint: fbf0dad7c55cdc98fad984da16b5f20ddb2bfa2789829d9c5807e3869654c744\nBootstrapping public Mainline...\nDiscovering address index servers through Mainline...\nPublishing the endpoint record and announcing the blob...\nPublished. Starting content discovery and download...\nLooking up Mainline infohash: 1221617bb90534bb2fafc4b35ea1bb08470cc81a\nMainline returned provider address: 82.76.245.139:53799\nAddress index: 82.76.245.139:53799 -> fbf0dad7c55cdc98fad984da16b5f20ddb2bfa2789829d9c5807e3869654c744\nTrying this endpoint with the iroh-blobs downloader...\nDownloaded and verified 44 bytes:\nhello from iroh-mainline-endpoint-discovery\n```\n## [Websites](#websites)\n\nSo now we have a mechanism to do content discovery for blobs. This will be useful for tools like [sendme](https://www.iroh.computer/sendme) and in general for sending around large amounts of data.\n\nBut what about permissionless publishing of websites such as a blog? To make this work we need some extra components.\n\n## [Link syntax](#link-syntax)\n\nWe need a syntax for content-addressed links. We don't go into a giant rabbit hole about how to encode these links. Our content addressed links are always BLAKE3. We need to reserve some global namespace for them, so we reserved a domain `blake3.net`. A content-addressed link is just `https://<hash>.blake3.net`, with the hash encoded using [zbase32](https://philzimmermann.com/docs/human-oriented-base-32-encoding.txt).\n\n## [Browser plugin](#browser-plugin)\n\nWe don't want to run an actual gateway at `blake3.net`. That would be a *very bad idea* for various reasons.\n\nInstead we want the browser to interpret these links as something that can be resolved in a different way. So we wrote a simple browser plugin that just rewrites these links to `http://<hash>.blake3.localhost:<port>` where the port is configurable in the plugin.\n\nThat's all the plugin does.\n\nIt currently works for Brave, Chrome, and Firefox.\n\nFor Chrome and Brave, [install iroh link from the Chrome Web Store](https://chromewebstore.google.com/detail/iroh-link/aajlbmaphckgbinhnifpiggcmdfnofcd).\n\nThe Firefox extension is awaiting review. For now, save the [unsigned Firefox XPI](https://github.com/n0-computer/iroh-content-discovery/releases/download/extension-v0.2.1/iroh-link-0.2.1-firefox-unsigned.xpi) to your computer using **Save Link As**. In Firefox 140 or later, open `about:debugging#/runtime/this-firefox`, click **Load Temporary Add-on**, and select the downloaded file. Open the extension popup, click **Save**, and allow access to the requested sites. You must load it again after restarting Firefox; the unsigned package cannot be installed through **Install Add-on From File** in regular Firefox.\n\n## [Local gateway](#local-gateway)\n\nThe last component is a local gateway that serves content-addressed data on localhost. It interacts with mainline to find content and orchestrates the actual blobs downloads. It supports [iroh-blobs collections](https://docs.iroh.computer/protocols/blobs#collections)  and shows a directory index for them. It also does file type detection. It is derived from iroh blobs gateway in [iroh-examples](https://github.com/n0-computer/iroh-examples/tree/main/iroh-gateway).\n\nYou can [compile the gateway from source](https://github.com/n0-computer/iroh-content-discovery/tree/main/iroh-link-gateway), or [download the latest release](https://github.com/n0-computer/iroh-content-discovery/releases/latest).\n\nWe could build a service worker to verify the data inside the browser process.\n\nInstead we verify the data inside the gateway.\n\nIt is software that you have to trust, whether it lives in the browser process or elsewhere doesn't matter much.\n\nWith these two components in place we can browse content-addressed data.\n\n![The local iroh gateway displaying the Open Content Library directory with Books, Essays, Films, LLM, and Website folders.](/blog/iroh-global-content-discovery/content-addressed-browsing.png)\n\n\n## [Names](#names)\n\nNow we have a mechanism to publish and consume content-addressed data. But we don't have a way to refer to *mutable* data. You could use `blake3.net` links from an existing website, but for that you need a registrar and a hoster, so it is not the permissionless publishing we are after.\n\nFortunately a permissionless DNS system already exists, [pkarr](https://pkarr.org). We [have been using it](/blog/iroh-global-node-discovery) for years for endpoint address lookup. Pkarr is a standard to publish a DNS record for a keypair, so only the owner of the secret key can publish new versions. The records are published on mainline DHT, which fits our setup neatly.\n\nTo make this compatible with our browser plus local gateway usage scenario, we reserved another domain `pkarr.net` and added a rewrite rule to the plugin and pkarr support to the gateway.\n\nA pkarr link has the format `<name>.pkarr.net`, where `name` is the [zbase32](https://philzimmermann.com/docs/human-oriented-base-32-encoding.txt) encoding of an Ed25519 public key. The plugin rewrites it to `<name>.pkarr.localhost:<port>`. The gateway will then resolve the pkarr record and either perform a redirect or directly serve content-addressed data if the pkarr record links to `hash.blake3.net`.\n\n[IPNS](https://docs.ipfs.tech/concepts/ipns/). I even did a little toy experiment called\n\n[Iroh Pkarr Naming System](https://github.com/n0-computer/iroh-experiments/tree/main/iroh-pkarr-naming-system)\n\nThe [`pkarr-publish-resolve` example](https://github.com/n0-computer/iroh-content-discovery/blob/main/iroh-mainline-endpoint-discovery/examples/pkarr-publish-resolve.rs) in [iroh-content-discovery](https://github.com/n0-computer/iroh-content-discovery) demonstrates the whole workflow: generate a keypair, publish a blob and its name, then resolve the name, discover a provider, and download the verified content from a separate client.\n\n```\ncargo run -p iroh-mainline-endpoint-discovery --example pkarr-publish-resolve -- --once\n[1/5] Prepare content and identity\n  Generated a keypair. Use --key-file to keep this name.\n  Name: https://uinsazmmp47ejo8gs5dbc6rfxya14cgqhxmdqin8ae55w5aqnsio.pkarr.net/\n  Blob: https://577jw6j7bjue44tzj8cpucwchh96dnhbhjfkpdd7eymqbaoxf3uo.blake3.net/\n  Serving b60a5a944d97eeb82fb1bcbed93ff4bef73cb09ccd348993e04db509f805b74d\n[2/5] Publish the endpoint mapping and announce the blob\n  Endpoint mapping and provider announcement published.\n[3/5] Publish the signed Pkarr name\n  Name now points to the blob hash.\n[4/5] Resolve the name from an independent DHT client\n  Verified signed record. Target:\n  577jw6j7bjue44tzj8cpucwchh96dnhbhjfkpdd7eymqbaoxf3uo.blake3.net\n[5/5] Discover a provider and download the content\n  Mainline infohash: f7d03c142bd009d4dbe98c0de7aed925e22eb9eb\n  Provider: 217e9396a7780609a4562e5d022004657d424fb8e605edaad57f5b8bb92be4e3\n  Provider: b60a5a944d97eeb82fb1bcbed93ff4bef73cb09ccd348993e04db509f805b74d\nDownloaded and BLAKE3-verified 35 bytes in 8.24s.\nContent:\n  hello from a Pkarr-named iroh blob\n```\nPkarr gives us permissionless names, but not human-readable ones: anyone can generate a keypair and publish under its public key, without asking a naming authority. This is the tradeoff illustrated by [Zooko’s triangle](https://en.wikipedia.org/wiki/Zooko%27s_triangle): pkarr names are decentralized and cryptographically verifiable, but a 52-character encoded public key is not a memorable name.\n\nYou can combine human readable naming systems with pkarr to get a memorable name that you can retarget in a permissionless way. We have some ideas about how to do this, stay tuned.\n\n## [Trying it out](#trying-it-out)\n\nThe first step is to run the local gateway. Most readers of this blog will probably just compile it from source, but there are also installers for windows and MacOS on github.\n\nThe next step is to install the browser plugin. For Chrome and Brave, [install iroh link from the Chrome Web Store](https://chromewebstore.google.com/detail/iroh-link/aajlbmaphckgbinhnifpiggcmdfnofcd).\n\nMake sure the port configured on the gateway and the browser plugin match. The default is `45475` or `B1A3`.\n\nAnd then you are ready to browse the content-addressed web.\n\nTry [https://y7rmokt6h5mryuauw83em4u1br6tqrukaw3ngtde7zp8p3bg6hto.blake3.net/](https://y7rmokt6h5mryuauw83em4u1br6tqrukaw3ngtde7zp8p3bg6hto.blake3.net/) for some static content, or [https://5ti57aszf7kaicsncb4wgigkf9bju39kofiz8dthwdujkmz85u8y.pkarr.net/](https://5ti57aszf7kaicsncb4wgigkf9bju39kofiz8dthwdujkmz85u8y.pkarr.net/) for a pkarr record *currently* pointing to the above.\n\n## [Publishing](#publishing)\n\nTraditional website publishing just means copying your files to a directory on a server. But for content-addressed data and permissionless pkarr DNS records, *you* are the publisher.\n\nSo we wrote a tool iroh-share that simplifies publishing. You can think of it as [sendme](https://www.iroh.computer/sendme), but running as a daemon with a separate user interface.\n\nBoth content-addressed data and pkarr records need continuous announcements, so you might want to run this daemon on a small box in your attic that is on 24/7, or a vm in the cloud. I run it on an old Synology NAS in my attic. It's behind a NAT of course, but you do get direct connections anyway.\n\nYou can find releases at [https://github.com/n0-computer/iroh-share/releases](https://github.com/n0-computer/iroh-share/releases).\n\n## [Recap](#recap)\n\nWe now have a system for global content discovery of BLAKE3 hashed content addressed data. It is *far from perfect*, but *it's a start*.\n\nThe *user interface* of sharing content addressed data is extremely simple. You just state what content you want, and the gateway gets it for you.\n\nThe *system behind it* has a lot of room for improvement.\n\n- \nMainline is quite scalable, but it probably won't scale for the vision of making all content on the internet content-addressable.\n- \nMainline traffic is also unencrypted and therefore easily blocked by middleboxes.\n- \nThe existing pkarr naming mechanism is using Ed25519 and therefore is not post quantum secure.\n- \nAnd perhaps most importantly, mainline *does not provide any privacy* . If you share content,*anybody can look up your ip address* .\n\nBut we are currently in the *food and shelter* phase of global content discovery. We will continue to improve the underlying content discovery system, possibly by extending mainline, possibly by [writing our own DHT](/blog/lets-write-a-dht-1) using iroh connections.\n\nBut the current system is certainly *better than nothing*, and we can do all these improvements while keeping the user interface stable.\n\n## [Next steps](#next-steps)\n\nWe are currently working on getting our most frequently used iroh protocols `irpc`, `iroh-gossip` and `iroh-blobs` to 1.0. The endpoint discovery mechanism is experimental, but all relevant crates are published on crates.io for you to play with.\n\n## [Footnotes](#footnote-label)\n\n1. \nIn *endgame mode* , it may request the same remaining blocks from multiple peers and use whichever responses arrive first.[↩](#user-content-fnref-endgame)\n2. \nTechnically, you can choose the port in an `announce_peer` request. But that gives you only 16 bits, which is not enough to store, for example, a 32-byte iroh`EndpointId` .[↩](#user-content-fnref-provider-port)\n3. \nThis mechanism is similar to the [address validation token](/blog/address-validation-tokens) in QUIC.[↩](#user-content-fnref-address-validation)\n\nTo get started, take a look at our\n\n[docs](https://iroh.computer/docs), dive directly into\n\n[the code](https://github.com/n0-computer/iroh), or chat with us in our\n\n[discord channel](https://iroh.computer/discord).\n","body_html":"<h1 id=\"iroh-global-content-discovery\">Iroh global content discovery</h1>\n<p>by Rüdiger Klaehn\nWhat got me excited about <a href=\"https://ipfs.tech\" rel=\"nofollow ugc noopener\">IPFS</a> many years ago, briefly after it was announced,\nwas being able to publish a personal website, blog post, or political\npamphlet and have it remain available globally as long as enough people are interested in the content. As governments have been more sophisticated in their firewalling\nmethods, this use case for circumvention tools and permissionless global content discovery is still incredibly relevant.</p>\n<p>Recent events have added some urgency to this. IPFS <a href=\"https://ipshipyard.com/blog/2026-the-end-of-ipfs-at-shipyard/\" rel=\"nofollow ugc noopener\">shipyard is shutting\ndown</a>. This does\nnot mean that IPFS will stop working, but it does not bode well for the future\nof the project.</p>\n<h2 id=\"the-state-of-the-art\"><a href=\"#the-state-of-the-art\">The state of the art</a></h2>\n<p>When we had to solve hole punching, we started looking at existing systems and chose the best open source system as an initial starting point for our own implementation. So let&#39;s do the same for global content discovery.</p>\n<p>There are a number of projects trying to solve this problem. But one project stands above all others: <a href=\"https://www.bittorrent.org/\" rel=\"nofollow ugc noopener\">BitTorrent</a>. It <em>just works</em> and has done so for over two decades.</p>\n<p>So let&#39;s take a look at what makes BitTorrent the current leader in permissionless global content discovery. BitTorrent has a relatively simple protocol for blob transfer and a <a href=\"https://en.wikipedia.org/wiki/Distributed_hash_table\" rel=\"nofollow ugc noopener\">DHT</a> called <a href=\"https://www.bittorrent.org/beps/bep_0005.html\" rel=\"nofollow ugc noopener\"><em>Mainline</em></a> for global content discovery.</p>\n<p>The transfer protocol and content discovery are <em>separate systems</em>. In fact, the DHT was developed later than the transfer protocol. BitTorrent was released in 2001 using centralized trackers for content discovery; the Mainline DHT was added in 2005.</p>\n<h2 id=\"transfer-protocol\"><a href=\"#transfer-protocol\">Transfer protocol</a></h2>\n<p>BitTorrent works by creating a <code>.torrent</code> file that contains information about the data to be downloaded. The file is encoded using <a href=\"https://www.bittorrent.org/beps/bep_0003.html#bencoding\" rel=\"nofollow ugc noopener\">bencode</a>, which is conceptually similar to JSON.</p>\n<pre><code>{\n  &quot;announce&quot;: &quot;http://tracker.example.com/announce&quot;,\n  &quot;comment&quot;: &quot;Ubuntu 24.04 desktop image&quot;,\n  &quot;created by&quot;: &quot;mktorrent 1.1&quot;,\n  &quot;creation date&quot;: 1714521600,\n  &quot;info&quot;: {\n    &quot;name&quot;: &quot;ubuntu-24.04-desktop-amd64.iso&quot;,\n    &quot;piece length&quot;: 262144,\n    &quot;length&quot;: 5820411904,\n    &quot;pieces&quot;: 9358b5d71f8007ac926000961ad79d865d9652bc\n              4cce840457bcfac1dd67d864e9386422eee70904\n              … many more 20-byte hashes …\n  }\n}</code></pre>\n<p><code>pieces</code> contains the concatenated SHA-1 hashes of all <code>piece length</code>-sized pieces. The transfer protocol downloads blocks of these pieces from different peers.<a href=\"#user-content-fn-endgame\">1</a></p>\n<p>The transfer protocol allows requests for ranges within pieces, but you can only validate a piece <strong>after</strong> you have downloaded it completely and computed its SHA-1 hash.</p>\n<p>I don&#39;t want to dwell on this too long, but I think that <a href=\"https://github.com/oconnor663/bao#readme\" rel=\"nofollow ugc noopener\">BLAKE3 verified streaming</a> is a superior replacement for the transfer protocol. We implemented a protocol called <a href=\"https://docs.iroh.computer/protocols/blobs\" rel=\"nofollow ugc noopener\">iroh-blobs</a> that uses BLAKE3 verified streaming for sharing blobs of data over iroh connections. With BLAKE3, we only need a single root hash, and neither a <code>piece length</code> parameter nor hashes of the individual pieces. This is what allows iroh-blobs to validate any piece of a large blob without needing intermediate hashes.</p>\n<p>For a visual explanation of how it works, see my <a href=\"https://www.youtube.com/watch?v=nk4nefmguZk\" rel=\"nofollow ugc noopener\">BLAKE3 and Bao deep dive</a>, which covers verified streaming, outboard encoding, and range requests.</p>\n<p>We still have some work to do to make multiprovider downloads of single blobs efficient, but the streaming protocol itself with its fine grained validation is superior to the BitTorrent transfer protocol.</p>\n<h2 id=\"dht\"><a href=\"#dht\">DHT</a></h2>\n<p>Initially BitTorrent used trackers to get providers for a torrent. What the DHT adds is a way to get providers <em>without</em> having to rely on trackers.</p>\n<p>This works by computing the SHA-1 hash of the <code>info</code> section, which includes the hashes of all pieces. Then you announce on the DHT that you have content for this hash. The mainline functions for this are <a href=\"https://www.bittorrent.org/beps/bep_0005.html#announce-peer\" rel=\"nofollow ugc noopener\"><code>announce_peer</code></a> and <a href=\"https://www.bittorrent.org/beps/bep_0005.html#get-peers\" rel=\"nofollow ugc noopener\"><code>get_peers</code></a>. Each DHT node will store a large set of providers.</p>\n<p>Many more modern protocols also use DHTs for content discovery. But Mainline works extremely well compared to many modern alternatives. It is also an extremely large and stable public DHT deployment, so it is unlikely to go away any time soon. You can look at current <a href=\"https://ipinfo.io/tags/bittorrent\" rel=\"nofollow ugc noopener\">mainline statistics</a>.</p>\n<p>Mainline DHT statistics from IPinfo.</p>\n<p>So let&#39;s take a look at why.</p>\n<h2 id=\"extreme-minimalism\"><a href=\"#extreme-minimalism\">Extreme minimalism</a></h2>\n<p>The mainline protocol takes into account that a DHT operates across an <em>extremely large</em> number of nodes. You <strong>can&#39;t</strong> afford large per-node connection state. Therefore both queries and responses are constrained to fit into single non-fragmented UDP packets.</p>\n<p>There are a number of other limitations compared to modern DHT implementations that all serve an important purpose:</p>\n<ul><li></li></ul>\n<p>the data stored for a provider is just the public <code>host:port</code> pair of the provider<em>as seen from the DHT node</em> . There is no user defined information in a provider record.&lt;sup&gt;<a href=\"#user-content-fn-provider-port\">2</a>&lt;/sup&gt;</p>\n<ul><li></li></ul>\n<p>For newer extensions like <a href=\"https://www.bittorrent.org/beps/bep_0044.html\" rel=\"nofollow ugc noopener\">BEP 44</a> the data is fully self-contained and verifiable, either a tiny piece of data or a signed record, both limited to fit into a typical MTU.</p>\n<ul><li></li></ul>\n<p>you can only store data after first querying the DHT node and returning the short-lived token from its response, <em>proving</em> that<em>at publishing time</em> you can receive packets at your claimed IP address.&lt;sup&gt;<a href=\"#user-content-fn-address-validation\">3</a>&lt;/sup&gt;</p>\n<p>The result of this minimalism is that mainline lookups typically complete in less than a second.</p>\n<p>Here is a real BEP 44 lookup of a pkarr record using the <code>get_mutable</code> example from <a href=\"https://github.com/n0-computer/n0-mainline\" rel=\"nofollow ugc noopener\">n0-mainline</a>.</p>\n<pre><code>RUST_LOG=debug cargo run --example get_mutable -- \\\n  996a1a05681f92877d5a6ecf9343fba40b8573e5a35ba5773a891ddfb47f9ca6\n2026-09-24T06:19:15.626284Z  INFO n0_mainline::actor: Mainline DHT started address=0.0.0.0:52517\nLooking up mutable item: 996a1a05681f92877d5a6ecf9343fba40b8573e5a35ba5773a891ddfb47f9ca6 ...\nGot first result in 37 milliseconds:\n  mutable item: [.. DNS packet bytes omitted ...], seq: 1790095662296563</code></pre>\n<p>If you are traumatized by DHT lookups taking forever or timing out: <strong>it doesn&#39;t have to be this way</strong>. The mainline DHT shows that millisecond lookups are possible at a global scale.</p>\n<h2 id=\"mainline-for-finding-blobs-providers\"><a href=\"#mainline-for-finding-blobs-providers\">Mainline for finding blobs providers</a></h2>\n<p>Now that we have established why mainline works so well, let&#39;s see if there is a way to use it for iroh blobs content discovery.</p>\n<p>Mainline provider records are just an IPv4 <code>host:port</code> pair. But iroh connections are dialed by cryptographic identity, currently the Ed25519 public key aka <code>EndpointId</code>. So to use mainline provider records for iroh blobs endpoint discovery, we would need a way to know which <code>EndpointId</code> is currently listening on this <code>host:port</code>.</p>\n<p>In most cases this <code>host:port</code> will be behind a NAT, so it is <em>not reachable</em>.</p>\n<p>I tried a number of ways to use current mainline mechanisms such as BEP 44 to store this mapping, but currently this is not possible. So we need a tiny extra UDP address index service (<a href=\"https://github.com/n0-computer/iroh-content-discovery/tree/main/udp-addr-index\" rel=\"nofollow ugc noopener\"><code>udp-addr-index</code></a>) to provide this mapping.</p>\n<p>This can be an incredibly simple service. It just stores some tiny arbitrary metadata for each verified UDP <code>host:port</code> pair and is reachable exclusively via a simple single-packet UDP protocol. Since mainline requires UDP, and this is an extension for mainline, we don&#39;t need to handle the case where sending UDP packets is not possible.</p>\n<p>Writes need a mechanism similar to the mainline or QUIC token mechanism to verify that the remote is actually reachable on the <code>host:port</code> pair. Reads don&#39;t need this, we just require a padded query packet to prevent amplification.</p>\n<p>The service stores up to 1 KiB of arbitrary metadata. It&#39;s a <em>last writer wins</em> map from a live UDP <code>host:port</code> pair to a tiny blob. Mappings expire after some time, so a content provider has to update the mapping at regular intervals as well as when its public UDP <code>host:port</code> pair changes.</p>\n<p>The current implementation keeps the map purely in memory. Records have to be updated at regular intervals anyway, and not requiring persistence makes the service much simpler and cheaper to operate. You can run an address index service for millions of iroh endpoints on a single small box with a publicly reachable UDP socket.</p>\n<p>The address index service does not depend in any way on iroh. It is infrastructure that could also be useful for non iroh applications using mainline.</p>\n<p>In the long term I would love the address index service function to be handled by a BitTorrent Mainline extension. It is simple, generically useful, and not specific to iroh.</p>\n<h2 id=\"what-s-in-the-record\"><a href=\"#whats-in-the-record\">What&#39;s in the record?</a></h2>\n<p>For our endpoint discovery purposes we store a record containing the endpoint id, current UDP socket addr, and a timestamp, signed with the endpoint private key. So <em>only the owner of the endpoint key</em> can write a valid signed record, so you can&#39;t impersonate other endpoints.</p>\n<p>The address index service however does <strong>not</strong> check anything about the endpoint. What <code>get_peers</code> in conjunction with this service gives us is just a list of <em>candidate</em> iroh endpoints which <em>might</em> serve the content.</p>\n<p>Endpoint discovery doesn&#39;t have to be for content discovery. We also have an example that shows how to <a href=\"https://github.com/n0-computer/iroh-content-discovery/blob/main/iroh-mainline-endpoint-discovery/examples/gossip.rs\" rel=\"nofollow ugc noopener\">discover peers for a gossip topic</a>.</p>\n<h2 id=\"workflow\"><a href=\"#workflow\">Workflow</a></h2>\n<p>So now let&#39;s take a look at the overall workflow when announcing and discovering content. For both announce and discovery we will need a mainline DHT node. We use the <a href=\"https://docs.rs/n0-mainline\" rel=\"nofollow ugc noopener\"><code>n0_mainline</code></a> crate just like in the existing <a href=\"https://docs.rs/iroh-mainline-address-lookup\" rel=\"nofollow ugc noopener\"><code>iroh-mainline-address-lookup</code></a> crate.</p>\n<h3 id=\"announce\">Announce</h3>\n<p>We want to associate the UDP socket of <code>n0_mainline</code> with our <code>EndpointId</code>. As outlined above, we use an UDP addr index server for this: we publish a signed record containing our <code>EndpointId</code> via the same UDP socket to the addr index server. <code>n0_mainline</code> has a mechanism to publish arbitrary UDP packets on its socket and to intercept incoming UDP packets. This needs to happen at regular intervals as well as immediately when the public <code>host:port</code> pair changes.</p>\n<p>For the announce itself: the mainline keyspace is 20 bytes, usually used to announce SHA-1 hashes of the info section of a .torrent file. We want to announce BLAKE3 hashes, so we first compute the SHA-1 hash of the BLAKE3 hash. Then we use <code>announce_peer</code> to announce that we are providing data for that hash. These announces also need to happen at regular intervals.</p>\n<p>You might wonder if SHA-1 is still safe for this purpose. Its collision resistance is broken, but there is no known practical preimage attack. More importantly, the DHT is just a best-effort mechanism for finding candidate providers. We verify downloaded data against the original BLAKE3 hash, so a misleading DHT result can waste time, but cannot make us accept the wrong content.</p>\n<h3 id=\"discovery\">Discovery</h3>\n<p>For discovery we first need to find at least one working service that can translate <code>host:port</code> pairs into <code>EndpointId</code>s. We have two mechanisms built in for this: a rendezvous hash that allows address index services to announce themselves, and a BEP 44 record that is a <em>curated list</em> of good address index services. Announce and discovery need to agree on the rendezvous hash or BEP 44 record name to find address index services.</p>\n<p>Once we have at least one working address index service, we compute the SHA-1 hash of the BLAKE3 hash we are looking for and call <code>get_peers</code>. The output of <code>get_peers</code> is a list of <code>host:port</code> pairs which we translate to <code>EndpointId</code>s using the address index service.</p>\n<p>At this point we get a stream of <em>unverified</em> <code>EndpointId</code>s. We currently do a quick BLAKE3 size query for the content we are looking for to make sure they are live and serve the right content, then hand them over to the iroh-blobs downloader to download the actual content.</p>\n<p>Note that the actual connection now uses the <code>EndpointId</code> and iroh&#39;s built in address lookup and hole punching to establish a connection. The QUIC connection does <em>not</em> work via the announced UDP <code>host:port</code> pair. That is just a key for the address index service.</p>\n<h2 id=\"demo\"><a href=\"#demo\">Demo</a></h2>\n<p>All of the above is implemented in the experimental <a href=\"https://github.com/n0-computer/iroh-content-discovery\" rel=\"nofollow ugc noopener\">iroh-content-discovery</a> repository. Its workspace contains the address index service, its protocol and client as well as a few extra crates that make use of it.</p>\n<p>To see the entire workflow in action, we can run the <a href=\"https://github.com/n0-computer/iroh-content-discovery/blob/main/iroh-mainline-endpoint-discovery/examples/blobs/main.rs\" rel=\"nofollow ugc noopener\"><code>blobs</code> example</a>. It runs both publish and resolve in one process, but discovery nevertheless goes via mainline and the address index service.</p>\n<pre><code>&gt; cargo run -p iroh-mainline-endpoint-discovery --example blobs\nBlob hash: 3b16faa5c1cb3e9acfb22954949fc67768734d15d2e6538e1ac23cd9363a6d07\nProvider endpoint: fbf0dad7c55cdc98fad984da16b5f20ddb2bfa2789829d9c5807e3869654c744\nBootstrapping public Mainline...\nDiscovering address index servers through Mainline...\nPublishing the endpoint record and announcing the blob...\nPublished. Starting content discovery and download...\nLooking up Mainline infohash: 1221617bb90534bb2fafc4b35ea1bb08470cc81a\nMainline returned provider address: 82.76.245.139:53799\nAddress index: 82.76.245.139:53799 -&gt; fbf0dad7c55cdc98fad984da16b5f20ddb2bfa2789829d9c5807e3869654c744\nTrying this endpoint with the iroh-blobs downloader...\nDownloaded and verified 44 bytes:\nhello from iroh-mainline-endpoint-discovery</code></pre>\n<h2 id=\"websites\"><a href=\"#websites\">Websites</a></h2>\n<p>So now we have a mechanism to do content discovery for blobs. This will be useful for tools like <a href=\"https://www.iroh.computer/sendme\" rel=\"nofollow ugc noopener\">sendme</a> and in general for sending around large amounts of data.</p>\n<p>But what about permissionless publishing of websites such as a blog? To make this work we need some extra components.</p>\n<h2 id=\"link-syntax\"><a href=\"#link-syntax\">Link syntax</a></h2>\n<p>We need a syntax for content-addressed links. We don&#39;t go into a giant rabbit hole about how to encode these links. Our content addressed links are always BLAKE3. We need to reserve some global namespace for them, so we reserved a domain <code>blake3.net</code>. A content-addressed link is just <code>https://&lt;hash&gt;.blake3.net</code>, with the hash encoded using <a href=\"https://philzimmermann.com/docs/human-oriented-base-32-encoding.txt\" rel=\"nofollow ugc noopener\">zbase32</a>.</p>\n<h2 id=\"browser-plugin\"><a href=\"#browser-plugin\">Browser plugin</a></h2>\n<p>We don&#39;t want to run an actual gateway at <code>blake3.net</code>. That would be a <em>very bad idea</em> for various reasons.</p>\n<p>Instead we want the browser to interpret these links as something that can be resolved in a different way. So we wrote a simple browser plugin that just rewrites these links to <code>http://&lt;hash&gt;.blake3.localhost:&lt;port&gt;</code> where the port is configurable in the plugin.</p>\n<p>That&#39;s all the plugin does.</p>\n<p>It currently works for Brave, Chrome, and Firefox.</p>\n<p>For Chrome and Brave, <a href=\"https://chromewebstore.google.com/detail/iroh-link/aajlbmaphckgbinhnifpiggcmdfnofcd\" rel=\"nofollow ugc noopener\">install iroh link from the Chrome Web Store</a>.</p>\n<p>The Firefox extension is awaiting review. For now, save the <a href=\"https://github.com/n0-computer/iroh-content-discovery/releases/download/extension-v0.2.1/iroh-link-0.2.1-firefox-unsigned.xpi\" rel=\"nofollow ugc noopener\">unsigned Firefox XPI</a> to your computer using <strong>Save Link As</strong>. In Firefox 140 or later, open <code>about:debugging#/runtime/this-firefox</code>, click <strong>Load Temporary Add-on</strong>, and select the downloaded file. Open the extension popup, click <strong>Save</strong>, and allow access to the requested sites. You must load it again after restarting Firefox; the unsigned package cannot be installed through <strong>Install Add-on From File</strong> in regular Firefox.</p>\n<h2 id=\"local-gateway\"><a href=\"#local-gateway\">Local gateway</a></h2>\n<p>The last component is a local gateway that serves content-addressed data on localhost. It interacts with mainline to find content and orchestrates the actual blobs downloads. It supports <a href=\"https://docs.iroh.computer/protocols/blobs#collections\" rel=\"nofollow ugc noopener\">iroh-blobs collections</a>  and shows a directory index for them. It also does file type detection. It is derived from iroh blobs gateway in <a href=\"https://github.com/n0-computer/iroh-examples/tree/main/iroh-gateway\" rel=\"nofollow ugc noopener\">iroh-examples</a>.</p>\n<p>You can <a href=\"https://github.com/n0-computer/iroh-content-discovery/tree/main/iroh-link-gateway\" rel=\"nofollow ugc noopener\">compile the gateway from source</a>, or <a href=\"https://github.com/n0-computer/iroh-content-discovery/releases/latest\" rel=\"nofollow ugc noopener\">download the latest release</a>.</p>\n<p>We could build a service worker to verify the data inside the browser process.</p>\n<p>Instead we verify the data inside the gateway.</p>\n<p>It is software that you have to trust, whether it lives in the browser process or elsewhere doesn&#39;t matter much.</p>\n<p>With these two components in place we can browse content-addressed data.</p>\n<p>The local iroh gateway displaying the Open Content Library directory with Books, Essays, Films, LLM, and Website folders.</p>\n<h2 id=\"names\"><a href=\"#names\">Names</a></h2>\n<p>Now we have a mechanism to publish and consume content-addressed data. But we don&#39;t have a way to refer to <em>mutable</em> data. You could use <code>blake3.net</code> links from an existing website, but for that you need a registrar and a hoster, so it is not the permissionless publishing we are after.</p>\n<p>Fortunately a permissionless DNS system already exists, <a href=\"https://pkarr.org\" rel=\"nofollow ugc noopener\">pkarr</a>. We <a href=\"/blog/iroh-global-node-discovery\">have been using it</a> for years for endpoint address lookup. Pkarr is a standard to publish a DNS record for a keypair, so only the owner of the secret key can publish new versions. The records are published on mainline DHT, which fits our setup neatly.</p>\n<p>To make this compatible with our browser plus local gateway usage scenario, we reserved another domain <code>pkarr.net</code> and added a rewrite rule to the plugin and pkarr support to the gateway.</p>\n<p>A pkarr link has the format <code>&lt;name&gt;.pkarr.net</code>, where <code>name</code> is the <a href=\"https://philzimmermann.com/docs/human-oriented-base-32-encoding.txt\" rel=\"nofollow ugc noopener\">zbase32</a> encoding of an Ed25519 public key. The plugin rewrites it to <code>&lt;name&gt;.pkarr.localhost:&lt;port&gt;</code>. The gateway will then resolve the pkarr record and either perform a redirect or directly serve content-addressed data if the pkarr record links to <code>hash.blake3.net</code>.</p>\n<p><a href=\"https://docs.ipfs.tech/concepts/ipns/\" rel=\"nofollow ugc noopener\">IPNS</a>. I even did a little toy experiment called</p>\n<p><a href=\"https://github.com/n0-computer/iroh-experiments/tree/main/iroh-pkarr-naming-system\" rel=\"nofollow ugc noopener\">Iroh Pkarr Naming System</a></p>\n<p>The <a href=\"https://github.com/n0-computer/iroh-content-discovery/blob/main/iroh-mainline-endpoint-discovery/examples/pkarr-publish-resolve.rs\" rel=\"nofollow ugc noopener\"><code>pkarr-publish-resolve</code> example</a> in <a href=\"https://github.com/n0-computer/iroh-content-discovery\" rel=\"nofollow ugc noopener\">iroh-content-discovery</a> demonstrates the whole workflow: generate a keypair, publish a blob and its name, then resolve the name, discover a provider, and download the verified content from a separate client.</p>\n<pre><code>cargo run -p iroh-mainline-endpoint-discovery --example pkarr-publish-resolve -- --once\n[1/5] Prepare content and identity\n  Generated a keypair. Use --key-file to keep this name.\n  Name: https://uinsazmmp47ejo8gs5dbc6rfxya14cgqhxmdqin8ae55w5aqnsio.pkarr.net/\n  Blob: https://577jw6j7bjue44tzj8cpucwchh96dnhbhjfkpdd7eymqbaoxf3uo.blake3.net/\n  Serving b60a5a944d97eeb82fb1bcbed93ff4bef73cb09ccd348993e04db509f805b74d\n[2/5] Publish the endpoint mapping and announce the blob\n  Endpoint mapping and provider announcement published.\n[3/5] Publish the signed Pkarr name\n  Name now points to the blob hash.\n[4/5] Resolve the name from an independent DHT client\n  Verified signed record. Target:\n  577jw6j7bjue44tzj8cpucwchh96dnhbhjfkpdd7eymqbaoxf3uo.blake3.net\n[5/5] Discover a provider and download the content\n  Mainline infohash: f7d03c142bd009d4dbe98c0de7aed925e22eb9eb\n  Provider: 217e9396a7780609a4562e5d022004657d424fb8e605edaad57f5b8bb92be4e3\n  Provider: b60a5a944d97eeb82fb1bcbed93ff4bef73cb09ccd348993e04db509f805b74d\nDownloaded and BLAKE3-verified 35 bytes in 8.24s.\nContent:\n  hello from a Pkarr-named iroh blob</code></pre>\n<p>Pkarr gives us permissionless names, but not human-readable ones: anyone can generate a keypair and publish under its public key, without asking a naming authority. This is the tradeoff illustrated by <a href=\"https://en.wikipedia.org/wiki/Zooko%27s_triangle\" rel=\"nofollow ugc noopener\">Zooko’s triangle</a>: pkarr names are decentralized and cryptographically verifiable, but a 52-character encoded public key is not a memorable name.</p>\n<p>You can combine human readable naming systems with pkarr to get a memorable name that you can retarget in a permissionless way. We have some ideas about how to do this, stay tuned.</p>\n<h2 id=\"trying-it-out\"><a href=\"#trying-it-out\">Trying it out</a></h2>\n<p>The first step is to run the local gateway. Most readers of this blog will probably just compile it from source, but there are also installers for windows and MacOS on github.</p>\n<p>The next step is to install the browser plugin. For Chrome and Brave, <a href=\"https://chromewebstore.google.com/detail/iroh-link/aajlbmaphckgbinhnifpiggcmdfnofcd\" rel=\"nofollow ugc noopener\">install iroh link from the Chrome Web Store</a>.</p>\n<p>Make sure the port configured on the gateway and the browser plugin match. The default is <code>45475</code> or <code>B1A3</code>.</p>\n<p>And then you are ready to browse the content-addressed web.</p>\n<p>Try <a href=\"https://y7rmokt6h5mryuauw83em4u1br6tqrukaw3ngtde7zp8p3bg6hto.blake3.net/\" rel=\"nofollow ugc noopener\"><a href=\"https://y7rmokt6h5mryuauw83em4u1br6tqrukaw3ngtde7zp8p3bg6hto.blake3.net/\" rel=\"nofollow ugc noopener\">https://y7rmokt6h5mryuauw83em4u1br6tqrukaw3ngtde7zp8p3bg6hto.blake3.net/</a></a> for some static content, or <a href=\"https://5ti57aszf7kaicsncb4wgigkf9bju39kofiz8dthwdujkmz85u8y.pkarr.net/\" rel=\"nofollow ugc noopener\"><a href=\"https://5ti57aszf7kaicsncb4wgigkf9bju39kofiz8dthwdujkmz85u8y.pkarr.net/\" rel=\"nofollow ugc noopener\">https://5ti57aszf7kaicsncb4wgigkf9bju39kofiz8dthwdujkmz85u8y.pkarr.net/</a></a> for a pkarr record <em>currently</em> pointing to the above.</p>\n<h2 id=\"publishing\"><a href=\"#publishing\">Publishing</a></h2>\n<p>Traditional website publishing just means copying your files to a directory on a server. But for content-addressed data and permissionless pkarr DNS records, <em>you</em> are the publisher.</p>\n<p>So we wrote a tool iroh-share that simplifies publishing. You can think of it as <a href=\"https://www.iroh.computer/sendme\" rel=\"nofollow ugc noopener\">sendme</a>, but running as a daemon with a separate user interface.</p>\n<p>Both content-addressed data and pkarr records need continuous announcements, so you might want to run this daemon on a small box in your attic that is on 24/7, or a vm in the cloud. I run it on an old Synology NAS in my attic. It&#39;s behind a NAT of course, but you do get direct connections anyway.</p>\n<p>You can find releases at <a href=\"https://github.com/n0-computer/iroh-share/releases\" rel=\"nofollow ugc noopener\"><a href=\"https://github.com/n0-computer/iroh-share/releases\" rel=\"nofollow ugc noopener\">https://github.com/n0-computer/iroh-share/releases</a></a>.</p>\n<h2 id=\"recap\"><a href=\"#recap\">Recap</a></h2>\n<p>We now have a system for global content discovery of BLAKE3 hashed content addressed data. It is <em>far from perfect</em>, but <em>it&#39;s a start</em>.</p>\n<p>The <em>user interface</em> of sharing content addressed data is extremely simple. You just state what content you want, and the gateway gets it for you.</p>\n<p>The <em>system behind it</em> has a lot of room for improvement.</p>\n<ul><li></li></ul>\n<p>Mainline is quite scalable, but it probably won&#39;t scale for the vision of making all content on the internet content-addressable.</p>\n<ul><li></li></ul>\n<p>Mainline traffic is also unencrypted and therefore easily blocked by middleboxes.</p>\n<ul><li></li></ul>\n<p>The existing pkarr naming mechanism is using Ed25519 and therefore is not post quantum secure.</p>\n<ul><li></li></ul>\n<p>And perhaps most importantly, mainline <em>does not provide any privacy</em> . If you share content,<em>anybody can look up your ip address</em> .</p>\n<p>But we are currently in the <em>food and shelter</em> phase of global content discovery. We will continue to improve the underlying content discovery system, possibly by extending mainline, possibly by <a href=\"/blog/lets-write-a-dht-1\">writing our own DHT</a> using iroh connections.</p>\n<p>But the current system is certainly <em>better than nothing</em>, and we can do all these improvements while keeping the user interface stable.</p>\n<h2 id=\"next-steps\"><a href=\"#next-steps\">Next steps</a></h2>\n<p>We are currently working on getting our most frequently used iroh protocols <code>irpc</code>, <code>iroh-gossip</code> and <code>iroh-blobs</code> to 1.0. The endpoint discovery mechanism is experimental, but all relevant crates are published on crates.io for you to play with.</p>\n<h2 id=\"footnotes\"><a href=\"#footnote-label\">Footnotes</a></h2>\n<ol><li></li></ol>\n<p>In <em>endgame mode</em> , it may request the same remaining blocks from multiple peers and use whichever responses arrive first.<a href=\"#user-content-fnref-endgame\">↩</a></p>\n<ol start=\"2\"><li></li></ol>\n<p>Technically, you can choose the port in an <code>announce_peer</code> request. But that gives you only 16 bits, which is not enough to store, for example, a 32-byte iroh<code>EndpointId</code> .<a href=\"#user-content-fnref-provider-port\">↩</a></p>\n<ol start=\"3\"><li></li></ol>\n<p>This mechanism is similar to the <a href=\"/blog/address-validation-tokens\">address validation token</a> in QUIC.<a href=\"#user-content-fnref-address-validation\">↩</a></p>\n<p>To get started, take a look at our</p>\n<p><a href=\"https://iroh.computer/docs\" rel=\"nofollow ugc noopener\">docs</a>, dive directly into</p>\n<p><a href=\"https://github.com/n0-computer/iroh\" rel=\"nofollow ugc noopener\">the code</a>, or chat with us in our</p>\n<p><a href=\"https://iroh.computer/discord\" rel=\"nofollow ugc noopener\">discord channel</a>.</p>","headings":[{"level":1,"text":"Iroh global content discovery","id":"iroh-global-content-discovery"},{"level":2,"text":"The state of the art","id":"the-state-of-the-art"},{"level":2,"text":"Transfer protocol","id":"transfer-protocol"},{"level":2,"text":"DHT","id":"dht"},{"level":2,"text":"Extreme minimalism","id":"extreme-minimalism"},{"level":2,"text":"Mainline for finding blobs providers","id":"mainline-for-finding-blobs-providers"},{"level":2,"text":"What's in the record?","id":"what-s-in-the-record"},{"level":2,"text":"Workflow","id":"workflow"},{"level":3,"text":"Announce","id":"announce"},{"level":3,"text":"Discovery","id":"discovery"},{"level":2,"text":"Demo","id":"demo"},{"level":2,"text":"Websites","id":"websites"},{"level":2,"text":"Link syntax","id":"link-syntax"},{"level":2,"text":"Browser plugin","id":"browser-plugin"},{"level":2,"text":"Local gateway","id":"local-gateway"},{"level":2,"text":"Names","id":"names"},{"level":2,"text":"Trying it out","id":"trying-it-out"},{"level":2,"text":"Publishing","id":"publishing"},{"level":2,"text":"Recap","id":"recap"},{"level":2,"text":"Next steps","id":"next-steps"},{"level":2,"text":"Footnotes","id":"footnotes"}]}}