A Tor onion service solves two problems that public web infrastructure normally hands off to a certificate authority and a DNS registrar, and it solves both with one mechanism: an address that is nothing but a public key. This article dissects how onion services work under the current v3 protocol — Ed25519 identity keys, the descriptor-publication hash ring, key blinding, the introduction/rendezvous handshake, and the six-hop mutual-anonymity path that falls out of it — with the actual consensus parameters and cell types the spec defines, not the browser-tab version of the story. The payoff is transferable: v3 shows exactly what it costs, in hops, latency, and operational assumptions, to make a service reachable without a public IP, a DNS record, a port forward, or a CA-issued certificate.

The Address Is the Public Key
A v3 onion address is 56 characters of base32: base32(PUBKEY | CHECKSUM | VERSION), where PUBKEY is a 32-byte Ed25519 public identity key, followed by a 2-byte checksum and a 1-byte version field. There is no certificate authority in this picture and no separate binding step between “name” and “key” — the address is the key. Possession of it is sufficient to verify every signed descriptor the service subsequently publishes, which is the Tor Rendezvous Specification’s own definition of a self-authenticating name (rend-spec-v3).
Compare that to how identity normally works on the web: a name (a domain) gets bound to a key (a TLS certificate’s public key) by a third party the client has to trust in advance — a CA, plus the root store that ships that CA’s trust anchor. Onion addressing collapses naming and authentication into a single artifact instead of two coordinated ones. It’s the same move, structurally, as passkeys replacing a password you type with a key you hold — identity anchored to possession of a key rather than a secret vouched for by an intermediary.
Publishing a Descriptor Without a CA or a DNS Record
An onion service doesn’t “listen” for the internet at large to find it. It builds a signed descriptor — a small document listing its current introduction points and their per-relay ntor keys — and publishes that descriptor to a specific set of relays so a client can retrieve it later. Nothing about that process touches a registrar, and nothing about retrieving it touches a resolver; there’s no analog to the Domain Name System’s hierarchy of root, TLD, and authoritative servers. The descriptor is fetched from whichever relays the protocol’s own math says should be holding it — which is where the hash ring comes in.
The HSDir Hash Ring: Consistent Hashing, Not a DHT
The relays responsible for storing a given service’s descriptor are called HSDirs (hidden-service directories), and which HSDirs those are is not a fixed table anywhere — it’s computed. This is worth being precise about, because it’s routinely described wrong: the HSDir ring is consistent hashing over the relay set published in Tor’s hourly consensus document, not a Kademlia-style distributed hash table. There’s no gossip-based peer discovery and no routing through intermediate nodes to find who holds what; every participant — client or service — derives the same ring positions from the same consensus, deterministically.
Two formulas do the placement work. A relay’s position on the ring is hs_relay_index = H("node-idx" | node_identity | shared_random_value | period_num | period_length). A descriptor’s target position is hs_service_index = H("store-at-idx" | blinded_pubkey | replica_num | period_length | period_num). The shared_random_value (SRV) is generated collaboratively by Tor’s directory authorities and rotates every time period, so no relay operator can predict today which HSDirs will be responsible for a given service tomorrow (rend-spec-v3 §2.2.3–2.2.4). It’s the same data structure engineers reach for when building a distributed cache cluster, aimed at the opposite goal — a cache ring minimizes reshuffling on churn, while this ring deliberately reshuffles on a schedule so no HSDir stays positioned to watch one service’s descriptor traffic indefinitely.
The Ring’s Consensus Parameters
Redundancy on the ring is a tunable replica/spread setting, not a fixed number of copies:
| Parameter | Meaning | Default |
|---|---|---|
hsdir_n_replicas | Descriptor replicas the service maintains | 2 |
hsdir_spread_store | HSDirs each replica is uploaded to | 4 (8 upload targets total) |
hsdir_spread_fetch | HSDirs a client tries per replica before giving up | 3 |
| Time period length | How often ring positions and the blinded key rotate | 1,440 minutes (24 hours) |
A service uploads each of its 2 replicas to the first 4 relays following its computed ring position — 8 upload targets — while a client fetching the descriptor tries only the first 3 candidates per replica. spread_fetch being deliberately smaller than spread_store means the ring tolerates some of those 8 HSDirs being offline without the client needing to know in advance which ones (Tor network parameters spec). These are consensus values the directory authorities can and do change — treat them as current defaults, not protocol constants, and re-check param-spec.html before citing them elsewhere.

Key Blinding: Why HSDirs Never See the Real Identity
The service’s long-term identity key never actually touches the wire. Descriptors are signed with a blinded key derived from the master identity key and the current time period, not the master key itself. The spec builds this from a credential value, N_hs_cred = H("credential" | identity_pk), with a per-period subcredential computed from that credential plus the blinded public key (rend-spec-v3, “Deriving blinded keys and subcredentials”). HSDirs and relays only ever observe the blinded key — never the identity key it was derived from.
Because the blinded key rotates every time period (the same 1,440-minute window the ring itself rotates on), a relay watching the ring over many periods sees a sequence of unlinkable, rotating identifiers rather than one static, permanently correlatable one. That’s a meaningfully different property from general-purpose data encryption, which protects content — key blinding protects the identity metadata the storage layer would otherwise leak just by existing. It’s also the specific mechanism that closes a hole v2 left wide open, covered below.

Two Circuits, One Meeting: Introduction Points and the Rendezvous Point
Three distinct relay roles show up in a single onion-service connection, and conflating any two of them misstates who can see what. The service pre-builds ordinary 3-hop circuits to relays it designates as introduction points, and lists them — with their per-relay ntor keys — in its published descriptor. Separately, and unrelated to those, a connecting client picks its own rendezvous point: any relay on the network, chosen by the client, unknown to the service in advance. The client builds its own 3-hop circuit to that rendezvous point and generates a one-time cookie to identify the meeting.
Only after both of those exist independently does the client open a third circuit — to one of the service’s introduction points — to actually request the meeting. The introduction point and the rendezvous point are never the same relay by design, and neither one is a guard in the client’s or service’s regular sense; they’re roles assigned per-connection, not persistent relationships.
Why the Path Is Six Hops, Not Three
An ordinary Tor circuit — guard, middle, exit — is 3 hops, and it solves one-sided anonymity: it hides the client from a destination that already knows it’s being contacted. Onion services need something stronger, mutual anonymity, because neither party is allowed to learn the other’s location. A single 3-hop circuit can’t provide that. Instead, the client’s independently built 3-hop circuit and the service’s independently built 3-hop circuit are joined at a rendezvous point neither side selected for the other — roughly 6 hops end to end (community.torproject.org, “How do Onion Services work?”). Any description of onion-service circuits that says “3 hops” without qualifying which circuit it means is describing half the picture.
The Handshake: INTRODUCE1/2 and RENDEZVOUS1/2
With both circuits and the rendezvous cookie in place, four relay-cell types carry the actual handshake:
- INTRODUCE1 (cell type 34) — the client sends this over its circuit to the introduction point. It carries the rendezvous point’s identity, the one-time cookie, and the client’s half of an
ntor-derived key exchange (rend-spec-v3 calls this variant “ntor with extra data,” §3.3.2). - INTRODUCE2 (cell type 35) — the introduction point forwards the same payload verbatim to the service over the service’s own pre-built circuit. The introduction point cannot read the contents; it’s relaying an opaque, already-encrypted request.
- RENDEZVOUS1 (cell type 36) — the service builds its own circuit to the client-chosen rendezvous point and sends its half of the handshake, tagged with the matching cookie.
- RENDEZVOUS2 (cell type 37) — the rendezvous point matches the cookie against the one it’s holding and relays the service’s handshake half onward to the client.
From that point, the rendezvous point’s job is done: it becomes a dumb splice, forwarding opaque RELAY cells between two circuits it can’t decrypt. Client and service now hold a shared key that neither the introduction point nor the rendezvous point ever possessed (rend-spec-v3 §3–§4). It’s a clean instance of a broker-that-can’t-read-anything pattern — a component structurally required to make the connection possible, deliberately given zero cryptographic capability, so its blast radius on compromise is “can disrupt the connection,” never “can read or impersonate either side.”

NAT Traversal for Free: A Service That Never Listens
None of this requires the service to accept an inbound connection at any point. It only ever dials out — to its own introduction points when publishing, and to whatever rendezvous point a client selects when connecting. “Reachable” here means “can dial Tor relays,” not “can accept inbound TCP” (Tor protocol overview). A host behind CGNAT, with no forwarded port and no static IP, is fully reachable as an onion service for exactly this reason — NAT traversal isn’t solved so much as made irrelevant, because the traversal problem only exists for services that need to accept inbound connections in the first place.

What v2 Got Wrong, and Why v3 Fixed It
v2 addresses were 16 characters: a truncated, 80-bit SHA-1 hash of a 1024-bit RSA identity key. Both primitives were already dated by the time v2 was retired — RSA-1024 sits below the strength NIST’s own key-management guidance recommends for new use (NIST SP 800-57 Part 1, Rev. 5), and SHA-1 had a practical collision demonstrated in the wild (Stevens et al., “SHAttered,” Google Security Blog, 2017). The address length mattered too: 80 bits of hash, rendered as 16 base32 characters, was short enough that vanity or collision-adjacent address generation was a realistic undertaking, not a theoretical one.
The deeper problem was architectural, not just cryptographic. Because v2 descriptors published to HSDirs exposed each service’s full, permanent address to whichever relay happened to be hosting that descriptor, researchers showed that a party willing to run enough HSDirs could harvest the entire population of hosted v2 addresses — an enumeration capability, not merely a decryption one (Biryukov, Pustogarov & Weinmann, “Trawling for Tor Hidden Services: Detection, Measurement, Deanonymization,” IEEE S&P 2013). Tor’s own deprecation announcement is notably vague on these specifics — it describes v2’s foundation as having become “fragile” and “unsafe” without detailing RSA-1024, SHA-1, or the enumeration mechanism by name — so the crypto-specific claims above trace to independent academic and security analysis, not to that post. v3’s blinded, rotating identity key is a direct answer to the enumeration problem: there’s no longer one static address for a hostile HSDir to harvest and correlate over time.
Removal ran on a published, dated timeline: Tor 0.4.4.x (September 15, 2020) began warning operators running v2 services; Tor 0.4.6.x (July 15, 2021) removed v2 support from the codebase entirely; stable Tor Browser had it disabled by October 15, 2021 (Onion Services v2 deprecation timeline).
| Property | v2 (removed 2020–2021) | v3 (current, exclusive) |
|---|---|---|
| Address length | 16 characters | 56 characters |
| Address derivation | Truncated 80-bit SHA-1 hash of a 1024-bit RSA key | base32(PUBKEY | CHECKSUM | VERSION) over a 32-byte Ed25519 key |
| Identity seen by HSDirs | Full, permanent public-key hash | Blinded, time-period-rotating derivative |
| Enumeration risk | Demonstrated — hostile HSDirs could harvest the address population | Denied by design — rotating blinded keys break long-term correlation |
| Removal milestone | Warned in 0.4.4.x (Sept 2020); removed in 0.4.6.x (July 2021) | Sole supported version since v2’s removal |

Onion Service vs. Public Web Service: NAT, DNS, TLS, Discovery
Laid side by side, the two models solve the same problems — reachability, naming, authentication, discovery — with almost nothing in common mechanically:
| Dimension | Public web service (clearnet) | Tor onion service (v3) |
|---|---|---|
| Reachability behind NAT/CGNAT | Needs a forwarded port, UPnP, or a public IP | Needs neither — the service only ever dials out |
| Naming | A DNS record you register, resolved through a trusted resolver hierarchy | The address is derived directly from the service’s own key — no registrar |
| Endpoint authentication | A CA-issued TLS certificate and a trusted root store | Self-authenticating — the address itself is the verification |
| Discovery path | DNS query → IP address → TCP connect | Descriptor fetch from the HSDir ring → introduction point → rendezvous |
| Hops to first byte | 1 (direct connection) | ~6 (two joined 3-hop circuits) |
The gap that table doesn’t fully convey is what happens when something goes wrong. A misissued or hijacked DNS record, or a compromised CA — problems covered in depth in DNS security’s rogues’ gallery of vulnerabilities and exploits — has no real analog here, because there’s no registrar or CA in the trust path to compromise. What v3 trades for that is the ~6-hop latency tax and a discovery path that depends on the HSDir ring being healthy rather than on a resolver you can query from anywhere.
Scaling and Discoverability: OnionBalance and Onion-Location
Two pieces of surrounding tooling turn a single onion service into something closer to production infrastructure. OnionBalance solves horizontal scaling without introducing a central load balancer: each backend is a fully independent onion-service instance with its own key and its own introduction points, running on its own machine. A frontend OnionBalance process fetches every backend’s descriptor, merges their introduction-point lists into one combined superdescriptor, and publishes that under a single public address. A client lands on a random introduction point from the merged set and — because each intro point belongs to exactly one backend — transparently reaches that backend, with no proxy process sitting in the data path and no new single point of failure introduced (OnionBalance design docs).
Onion-Location solves discoverability from the clearnet side: a normal HTTPS response can include an Onion-Location header pointing to the site’s .onion mirror, and Tor Browser (since version 9.5, 2020) surfaces a “.onion available” affordance rather than forcing a redirect (community.torproject.org, Onion-Location). Building the mirror that header points to is automated by EOTK (the Enterprise Onion Toolkit, Alec Muffett) — a reverse proxy that puts an existing public HTTPS site behind a working .onion address without modifying the origin application (EOTK on GitHub). Documented adopters include The Intercept’s mirror (2021) and Brave’s own (2020).
The Transferable Lesson: What “Reachable Without Any of That” Costs
Strip away the anonymity framing and what’s left is a reusable architecture: an identity that is a key rather than a name someone vouches for, a discovery layer built from deterministic hashing over a shared, versioned membership list rather than a queryable registry, and a rendezvous broker with no cryptographic capability at all. That combination buys a service NAT traversal, self-authenticating names, and no dependency on a CA or a DNS registrar — genuinely useful properties for any system where a public IP, a forwarded port, or a third-party-issued certificate is an operational liability.
None of it is free. The cost shows up as roughly double the circuit length of ordinary Tor traffic, a discovery path that depends on the HSDir ring’s own health rather than a resolver reachable from anywhere, and the absence of the familiar padlock-and-certificate UI that tells a user “this identity was checked by someone.” v3 onion services are the clearest working demonstration of that trade being made deliberately and explicitly — which is exactly why it’s worth studying the mechanism even for engineers who will never run a hidden service themselves.
❓ Frequently Asked Questions
What makes a v3 onion address “self-authenticating”? The 56-character address directly encodes the service’s Ed25519 public identity key (base32(PUBKEY | CHECKSUM | VERSION)). Possessing the address is enough to verify every descriptor and handshake message the service signs — no certificate authority vouches for the binding, because the name already is the key.
Is the HSDir hash ring a distributed hash table like Kademlia? No. It’s consistent hashing computed directly over the relay set published in Tor’s hourly consensus, using deterministic formulas (hs_relay_index, hs_service_index) seeded by a rotating shared random value. There’s no gossip-based routing — every participant computes the same ring from the same consensus document.
Why does connecting to an onion service take six hops instead of three? A default Tor circuit (guard → middle → exit) is 3 hops and only protects the client from a destination that already knows it’s being contacted. Onion services require mutual anonymity, so the client’s and the service’s independently built 3-hop circuits are joined at a rendezvous point neither side chose for the other, for roughly 6 hops total.
What specifically was wrong with v2 onion services? v2 addresses were a truncated, 80-bit SHA-1 hash of a 1024-bit RSA key — short enough to make vanity-address generation realistic — and because the full address was exposed to whichever relay hosted the descriptor, researchers demonstrated that instrumenting enough HSDirs could harvest the entire population of v2 addresses (Biryukov, Pustogarov & Weinmann, IEEE S&P 2013).
How does an onion service get NAT traversal without opening a port? It never accepts an inbound connection. It only dials out — to its own introduction points, then to whatever rendezvous point a client selects — so “reachable” means “can reach Tor relays,” not “can accept inbound TCP.” A host behind CGNAT with no forwarded port is fully reachable this way.
What does OnionBalance actually load-balance, since there’s no shared server? It merges metadata, not traffic. Each backend is an independent onion service with its own key and introduction points; OnionBalance fetches every backend’s descriptor and publishes a combined superdescriptor under one address. A client picks a random introduction point from that merged list, and since each belongs to exactly one backend, no proxy process ever sits in the data path.
📚 Further Reading
- What is the Domain Name System? — the resolver-and-registrar model onion addressing replaces entirely.
- DNS Security Issues, Vulnerabilities, and Exploits — the hijacking and cache-poisoning attack class a name that’s just a public key sidesteps by construction.
- Encrypt and Secure Your Data — the broader cryptographic toolkit key blinding and rotating identity draw from.
- The Best Books on Distributed Systems (Beginner to Expert) — for going deeper on consistent hashing, the technique behind the HSDir ring.
- Passkey vs Password Manager: Do You Need Both? — another system where the key itself, not a shared secret, is the identity.
📚 Recommended Resources
Disclosure: some links below are Amazon affiliate links. If you buy through them, I earn a small commission — at no extra cost to you — which helps keep this site running.
Designing Data-Intensive Applications
Martin Kleppmann & Chris Riccomini · Book · 2nd edition (2026)
The best available deep-dive on the distributed-systems ideas this article leans on hardest: consistent hashing, replication, and how a system decides who is responsible for this piece of data without a central coordinator. Read it to see the HSDir ring’s logic generalized far beyond Tor.
Cryptography Engineering: Design Principles and Practical Applications
Niels Ferguson, Bruce Schneier & Tadayoshi Kohno · Book
The applied-cryptography foundations underneath key blinding and self-authenticating identity — key derivation, why rotation matters, and how real protocols get these primitives wrong. The right background for the “why blind the key at all?” question this article answers.
Security Engineering: A Guide to Building Dependable Distributed Systems
Ross Anderson · Book · 3rd edition
Anderson is the reference on designing against an actual adversary — the discipline that produced v3’s answer to descriptor enumeration. Dense, but it is the book every other security book cites.
Also free, and the load-bearing citation for this article: the Tor Rendezvous Specification (rend-spec-v3) at spec.torproject.org.