# How iroh's Address Lookup Service Connects Peers by Public Key

> Learn how iroh's address lookup service connects peers using only public keys. Discover how iroh maps public keys to network locations for seamless P2P communication.

- Repository: [number zero/iroh](https://github.com/n0-computer/iroh)
- Tags: deep-dive
- Published: 2026-07-11

---

**TLDR:** iroh enables peer-to-peer connections using only cryptographic public keys as stable identifiers, resolving current network locations through pluggable lookup services that map **EndpointIds** (public keys) to reachable IP addresses and relay URLs.

The iroh networking stack from n0-computer treats public keys as the primary peer identity. When an application needs to establish a connection, the iroh address lookup service connects peers by public key through a distributed resolution process that translates static cryptographic identifiers into dynamic network locations. This architecture eliminates the need for out-of-band IP address exchange while maintaining strong authentication guarantees.

## The 5-Step Resolution Flow

When connecting to a remote peer using only its public key, iroh executes a multi-phase resolution process that bridges the gap between cryptographic identity and network reachability.

### Step 1: Publishing Address Information

When an endpoint starts, it creates an **EndpointData** structure containing its relay URL and any direct IP addresses. It then calls `AddressLookup::publish` on every configured lookup service (such as pkarr, DNS, or memory backends). Each service stores this data keyed by the endpoint’s public key, creating a distributed directory of peer locations.

According to the source code in [`iroh/src/address_lookup.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/address_lookup.rs) (lines 33-41), the `publish` method takes the endpoint's **EndpointId** (derived from its secret key) and registers the current network coordinates.

### Step 2: Resolving the Remote Public Key

When a caller invokes `Endpoint::connect(..., alpn)` with an **EndpointId** (or an **EndpointAddr** containing only the ID), the method triggers `self.inner.resolve_remote(endpoint_addr)`. This async call queries the **Remote Map** to look up the address for the given public key.

This resolution chain is implemented in [`iroh/src/endpoint.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint.rs) (lines 27-34) and delegates to the low-level networking layer in [`iroh/src/socket.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket.rs) (lines 1320-1335).

### Step 3: Querying Services in Parallel

The `AddressLookupServices::resolve` method in [`iroh/src/address_lookup.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/address_lookup.rs) (lines 54-66) creates a resolution stream for each registered lookup service. The `AddressLookupStream` merges these streams and yields the first successful **Item** (containing **EndpointInfo**) while continuing to listen to slower services.

Errors from individual services are buffered and logged via `debug!("address lookup error…")`. Only if *all* services fail does the stream emit an `AddressLookupFailed::NoResults` error, as implemented in the `poll_next` method (lines 26-48).

### Step 4: Selecting the Best Path

The returned `Item` converts to an `EndpointAddr` via `Item::to_endpoint_addr()` (located in [`iroh/src/address_lookup.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/address_lookup.rs), lines 24-27). This address contains either a relay URL, direct IP sockets, or both. The endpoint’s path selector—by default a biased-RTT selector—picks the optimal path, preferring direct addresses over relayed connections.

### Step 5: Dialing the Remote Peer

With the concrete `MappedAddr` obtained from `resolve_remote`, the endpoint builds a QUIC client configuration and calls the underlying NOQ transport to open a connection. As implemented in [`iroh/src/endpoint.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint.rs) (lines 87-95), the connection succeeds as soon as a reachable address is found; if the direct address works, the relay is never used.

## Why Public Keys Are Sufficient for Peer Discovery

All address lookup services store location data under the peer’s **public key** (`EndpointId`). Because the public key is globally unique and cryptographically unforgeable—it serves as the authentication credential for the TLS handshake—any peer retrieving the address record can verify that the discovered addresses belong to the intended remote. The lookup services function as a trustless directory where the identifier itself provides the security guarantee.

## Configuring Address Lookup Services

iroh ships with multiple implementations of the `AddressLookup` trait. You can combine them to create a robust resolution strategy:

```rust
use iroh::{
    address_lookup::{AddrFilter, PkarrPublisher},
    endpoint::{presets, Builder},
    Endpoint,
};

// Build an endpoint that publishes via public pkarr server
let ep = Builder::new(presets::Minimal)
    .address_lookup(PkarrPublisher::n0_dns())
    .address_lookup(address_lookup::DnsAddressLookup::n0_dns())
    .bind()
    .await?;

// Connect using only the remote public key
let remote_id = /* the other peer's public key */ ;
let conn = ep.connect(remote_id, b"my-alpn").await?;

```

For testing or isolated networks, use the in-memory lookup:

```rust
use iroh::address_lookup::memory::MemoryLookup;

let mem = MemoryLookup::new();
mem.add(remote_id, EndpointAddr::new(remote_id).with_relay_url("https://my.relay".parse()?));

let ep = Builder::empty()
    .address_lookup(mem)
    .bind()
    .await?;

```

## Key Implementation Details

### Pluggable Lookup Back-ends

The `AddressLookup` trait in [`iroh/src/address_lookup.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/address_lookup.rs) defines the interface for custom resolution strategies. The codebase includes `MemoryLookup` for local testing, `PkarrResolver` for DHT-based lookups, and `DnsAddressLookup` for DNS-based resolution using PKARR packets stored in DNS records.

### Concurrent Resolution Strategy

Multiple lookup services execute simultaneously in separate async streams. The `AddressLookupStream` uses a biased merge strategy that returns the first successful result, reducing connection latency while maintaining fallback options.

### Error Handling and Fallbacks

Individual service failures do not abort the resolution process. The system aggregates errors and only returns `NoResults` when every configured service fails to provide an address for the given public key.

## Summary

- iroh uses **EndpointId** (public keys) as canonical peer identifiers, storing address records in distributed lookup services.
- The resolution process runs through five phases: publishing local addresses, resolving remote keys, parallel service queries, path selection, and QUIC connection establishment.
- Multiple lookup services run concurrently in [`iroh/src/address_lookup.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/address_lookup.rs), with the first successful result establishing the connection.
- Public keys provide both identification and authentication, ensuring that resolved addresses cryptographically belong to the intended peer.
- The system is configurable via the `AddressLookup` trait, supporting memory, DNS, and pkarr backends.

## Frequently Asked Questions

### What happens if all address lookup services fail to resolve a public key?

The `AddressLookupStream` buffers errors from individual services and only emits `AddressLookupFailed::NoResults` if every configured service returns no results. This error propagates through `resolve_remote` in [`iroh/src/socket.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/socket.rs) and causes the `Endpoint::connect` call to fail, requiring the application to retry or verify the remote public key.

### Can I use custom address lookup services beyond the built-in DNS and pkarr options?

Yes. You can implement the `AddressLookup` trait for your own backend by defining the `publish` and `resolve` methods. Inject your implementation into the endpoint builder using `.address_lookup(my_custom_service)`, and iroh will include it in the parallel resolution process alongside other services.

### How does iroh handle address changes when a peer moves between networks?

When a peer's network interfaces change, the endpoint updates its `EndpointData` and calls `AddressLookup::publish` again to propagate new addresses to all configured lookup services. Peers resolving the public key receive the updated location information on their next connection attempt, allowing the system to handle NAT changes and mobile network transitions transparently.

### Is the public key used for both identification and encryption during the connection?

Yes. The **EndpointId** (public key) serves as the stable identifier for lookup purposes, but also acts as the cryptographic identity for the TLS handshake during the QUIC connection establishment. This ensures that the resolved address actually belongs to the peer holding the corresponding private key, preventing man-in-the-middle attacks during the connection process.