# How pkarr Publishing Works with iroh: Signed Packets to DNS Resolution

> Discover how pkarr publishing uses iroh for signed DNS packets via HTTP PUT to a DNS server, enabling efficient resolution with caching and DHT fallback.

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

---

**pkarr publishing with iroh works by creating cryptographically signed DNS packets containing TXT records, transmitting them via HTTP PUT requests to an iroh-dns-server, and resolving them through a persistent store with LRU caching and DHT fallback.**

The iroh networking library implements pkarr (Public Key Addressable Resource Records) to enable decentralized, self-certified DNS publishing without centralized authorities. This guide explains how pkarr publishing works with iroh, covering the wire format, HTTP API endpoints, and storage mechanisms implemented in the n0-computer/iroh repository.

## The pkarr Signed Packet Format

iroh uses the standard pkarr signed-packet wire format defined in [`iroh-dns/src/pkarr.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-dns/src/pkarr.rs). The binary structure consists of a 32-byte public key, 64-byte signature, 8-byte timestamp, and the DNS packet payload.

This format ensures cryptographic integrity: the signature covers the timestamp and DNS packet, proving the publisher controls the private key associated with the public-key label. The specific constants and struct definitions are located at lines 17-30 of [`iroh-dns/src/pkarr.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-dns/src/pkarr.rs).

## Creating and Signing Packets

Applications construct publishable packets using the `SignedPacket::from_txt_strings` method. This helper takes a secret key, DNS name, TXT values, and TTL, then builds a DNS packet, signs it, and returns the serialized bytes.

### Building a Signed Packet in Rust

```rust
use iroh_base::SecretKey;
use iroh_dns::pkarr::SignedPacket;

// Generate or load a secret key
let sk = SecretKey::generate();

// Publish two TXT records under "_iroh.example."
let packet = SignedPacket::from_txt_strings(
    &sk,
    "_iroh.example.",
    ["node=12345", "proto=quic"],
    300, // TTL in seconds
)?;

// The bytes to send over HTTP (omit the leading public-key)
let payload = packet.to_relay_payload();

```

The `from_txt_strings` implementation (lines 41-88 in [`iroh-dns/src/pkarr.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-dns/src/pkarr.rs)) handles DNS packet construction, Ed25519 signing, and timestamp injection automatically.

## Publishing via the HTTP API

The iroh-dns-server exposes a `PUT /pkarr/:key` endpoint for publishing. The URL parameter `:key` represents the publisher's public key in z-base-32 encoding.

### Request Structure

The HTTP request body must contain the pkarr signed-packet **without** the leading 32 public-key bytes. The server reconstructs the full packet using `SignedPacket::from_relay_payload` (lines 14-28 in [`iroh-dns-server/src/http/pkarr.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-dns-server/src/http/pkarr.rs)), then verifies the signature and timestamp before storage.

### Publishing via cURL

```bash

# Public key in z-base-32 (derived from the secret key)

PUBKEY=$(echo $SK | iroh key to-z32)

curl -X PUT "http://localhost:8080/pkarr/$PUBKEY" \
     -H "Content-Type: application/octet-stream" \
     --data-binary "@payload.bin"

```

## Server-Side Storage and Caching

Published packets are managed by `ZoneStore`, which maintains both a persistent `SignedPacketStore` and an in-memory LRU cache.

### Insertion Process

When a packet arrives, `ZoneStore::insert` performs an upsert operation: it validates the packet, updates the persistent store, clears any cached entry for that public key, and increments metrics. This logic is implemented in lines 75-95 of [`iroh-dns-server/src/store.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-dns-server/src/store.rs).

### Query Resolution

For DNS queries, the server checks the LRU cache first. If the packet is missing, it queries the persistent store, and finally attempts a mainline DHT fallback for global resolution. This three-tier lookup is defined in lines 97-124 of [`iroh-dns-server/src/store.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-dns-server/src/store.rs).

## Timestamp Monotonicity Requirements

pkarr requires strictly increasing timestamps for each publish to prevent replay attacks. iroh handles this through the `Timestamp::now` implementation, which guarantees monotonicity even if the system clock moves backwards.

The timestamp logic (lines 25-33 and 36-62 in [`iroh-dns/src/pkarr.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-dns/src/pkarr.rs)) atomically tracks the last returned value, ensuring that successive calls always return a higher timestamp than previous publications from the same key.

## DNS Resolution and Record Conversion

After storage, the server converts pkarr packets into standard DNS records using `signed_packet_to_hickory_records_without_origin`. This function (located in [`iroh-dns-server/src/util.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-dns-server/src/util.rs), lines 101-145) strips the zone-origin (the public-key label) from the embedded DNS packet, producing `hickory-server` record sets that can be looked up by name and type.

## Retrieving Published Packets

Clients can retrieve raw packets via the `GET /pkarr/:key` endpoint. The server returns the signed-packet payload (everything after the public-key bytes) with the MIME type `application/x-pkarr-signed-packet`.

### Retrieving and Verifying in Rust

```bash
curl -X GET "http://localhost:8080/pkarr/$PUBKEY" \
     -H "Accept: application/x-pkarr-signed-packet" \
     -o retrieved.bin

```

```rust
use iroh_dns::pkarr::SignedPacket;

let pkt = SignedPacket::from_bytes(&retrieved)?;
let txts = pkt.txt_records("_iroh.example."); // → Vec<String>
println!("{:?}", txts);

```

The retrieval endpoint is implemented in lines 37-50 of [`iroh-dns-server/src/http/pkarr.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-dns-server/src/http/pkarr.rs).

## Summary

- **pkarr publishing with iroh** uses a signed-packet format consisting of `<32 pubkey><64 sig><8 timestamp><DNS packet>` to cryptographically bind DNS records to public keys.
- The **`SignedPacket::from_txt_strings`** method in [`iroh-dns/src/pkarr.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-dns/src/pkarr.rs) constructs and signs packets, while **`to_relay_payload`** prepares them for HTTP transmission.
- The **iroh-dns-server** accepts packets via `PUT /pkarr/:key`, verifies them using **`from_relay_payload`**, and stores them in **`ZoneStore`** with persistent storage and LRU caching.
- **Monotonic timestamps** enforced by `Timestamp::now` prevent replay attacks by ensuring each publication has a higher timestamp than the last.
- **DNS resolution** converts stored packets into standard records via `signed_packet_to_hickory_records_without_origin`, with fallback to mainline DHT for global lookups.

## Frequently Asked Questions

### What is the exact pkarr wire format used by iroh?

The format is `<32 pubkey><64 sig><8 timestamp><DNS packet>`, as defined in [`iroh-dns/src/pkarr.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-dns/src/pkarr.rs). The 32-byte public key identifies the publisher, the 64-byte Ed25519 signature authenticates the content, and the 8-byte timestamp ensures freshness. This structure allows self-certified DNS records without certificate authorities.

### How does iroh prevent old pkarr packets from being replayed?

iroh enforces **strictly monotonic timestamps** using the `Timestamp::now` implementation. Even if the system clock moves backwards, the timestamp generator atomically tracks the last returned value and guarantees each new timestamp is higher than the previous one. The server rejects packets with timestamps equal to or lower than the stored version.

### What happens when a DNS query hits the iroh-dns-server?

The server performs a three-tier lookup: first checking the **in-memory LRU cache**, then the **persistent `SignedPacketStore`**, and finally falling back to the **mainline DHT** for globally distributed records. Once found, `signed_packet_to_hickory_records_without_origin` converts the packet into standard DNS records by stripping the public-key origin label.

### Can I retrieve a raw pkarr packet after publishing?

Yes. The `GET /pkarr/:key` endpoint returns the signed-packet payload (excluding the public-key bytes) with MIME type `application/x-pkarr-signed-packet`. Clients can then verify and parse this using `SignedPacket::from_bytes` to extract TXT records and other DNS data without trusting the server.