# How Does iroh Handle Encryption? TLS 1.3 and Raw Public Key Authentication Explained

> Discover how iroh handles encryption with TLS 1.3 and raw public key authentication. Learn how Ed25519 keys replace traditional certificates for secure connections.

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

---

**iroh secures all connections using TLS 1.3 with a raw-public-key authentication scheme (RFC 7250), eliminating traditional X.509 certificates in favor of Ed25519 public keys that serve as both identity and authentication credentials.**

The n0-computer/iroh repository implements end-to-end encryption through a specialized TLS stack built on rustls. Every node generates a permanent Ed25519 key pair, where the public key acts as the `EndpointId`. This design removes certificate authority overhead while maintaining strong, cryptographic peer authentication for all QUIC traffic.

## The Foundation: TLS 1.3 with Raw Public Key Authentication

iroh’s encryption strategy departs from traditional web security models. Instead of relying on X.509 certificate chains and Certificate Authorities (CAs), the protocol uses **raw public keys** (RFC 7250) embedded directly into the TLS 1.3 handshake.

### Node Identity and Ed25519 Keys

Each iroh node generates a permanent `SecretKey` using the Ed25519 elliptic curve algorithm. The corresponding `PublicKey`—exposed as the `EndpointId`—uniquely identifies the node across the network. In [`iroh-base/src/key.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-base/src/key.rs), the `SecretKey` and `PublicKey` types provide the cryptographic primitives used throughout the TLS handshake.

- **Identity**: The `EndpointId` (public key) serves as the canonical node identifier
- **Authentication**: The private key signs TLS handshake messages to prove ownership
- **Verification**: Peers verify the signature against the expected `EndpointId`

### Why Raw Public Keys Instead of X.509?

Traditional TLS requires X.509 certificates bound to DNS names. For peer-to-peer networks like iroh, this creates unnecessary complexity. By implementing `ResolveRawPublicKeyCert` in [`src/tls/resolver.rs`](https://github.com/n0-computer/iroh/blob/main/src/tls/resolver.rs), iroh presents the node’s public key directly as a raw-public-key certificate. This approach eliminates certificate parsing overhead, removes CA dependencies, and enables peers to authenticate each other using only their cryptographic identities.

## How iroh Configures TLS for QUIC

The `TlsConfig` structure in [`src/tls.rs`](https://github.com/n0-computer/iroh/blob/main/src/tls.rs) orchestrates the encryption layer. When building an `Endpoint`, the configuration creates client and server TLS contexts specifically optimized for QUIC transport.

### The TlsConfig Builder

When you call `Endpoint::builder()`, the system initializes `TlsConfig::new`, which constructs:

1. A `QuicClientConfig` for outgoing connections
2. A `QuicServerConfig` for incoming connections
3. Session ticket caching (`DEFAULT_MAX_TLS_TICKETS`) for fast resumption
4. Early-data (0-RTT) support for zero-round-trip reconnections

The configuration ensures that only the AES-128-GCM-SHA256 cipher suite is available, which QUIC mandates for TLS 1.3. If the crypto provider lacks this suite, `TlsConfigError::CryptoProviderNoInitialCipherSuite` is raised.

### Custom Certificate Resolution

The `ResolveRawPublicKeyCert` resolver (defined in [`src/tls/resolver.rs`](https://github.com/n0-computer/iroh/blob/main/src/tls/resolver.rs)) implements `rustls::server::ResolvesServerCert`. When the TLS handshake begins, it:

- Constructs a `rustls::sign::CertifiedKey` from the node’s `IrohSecretKey`
- Advertises `only_raw_public_keys()` → `true` to indicate no certificate chain is present
- Formats the public key as SubjectPublicKeyInfo (SPKI) for the handshake

```rust
// Conceptual representation of the resolver logic
impl ResolvesServerCert for ResolveRawPublicKeyCert {
    fn resolve(&self, _client_hello: ClientHello) -> Option<CertifiedKey> {
        // Uses IrohSecretKey to create a signing key
        let signing_key = self.secret_key.into();
        // Advertises only raw public keys, no X.509 chain
        let certified_key = CertifiedKey::new(
            vec![], // empty certificate chain
            signing_key
        );
        Some(certified_key)
    }
}

```

### Custom Verification Logic

On the verification side, [`src/tls/verifier.rs`](https://github.com/n0-computer/iroh/blob/main/src/tls/verifier.rs) contains `ServerCertificateVerifier` and `ClientCertificateVerifier` implementations. These verify that the peer’s presented public key matches the expected `EndpointId`, ensuring that only the intended endpoint can complete the TLS session.

## The Encryption Handshake Process

When two iroh nodes establish a connection, the following cryptographic sequence occurs:

### Raw Public Key Resolution

During the TLS handshake, the server calls `ResolveRawPublicKeyCert` to present its public key. The client receives this raw public key and validates it against the `EndpointId` it intended to connect to. This happens without X.509 parsing, as the resolver explicitly sets `only_raw_public_keys()`.

### Cipher Suites and AEAD

Once the handshake completes, all QUIC packets—including data streams, bidirectional and unidirectional—are encrypted using the negotiated AEAD. iroh enforces **AES-128-GCM-SHA256**, the mandatory cipher suite for QUIC with TLS 1.3. This provides confidentiality and integrity for all traffic between endpoints.

### Session Resumption and 0-RTT

For performance, iroh enables **early-data (0-RTT)** and session ticket caching. This allows returning clients to send encrypted data immediately without completing a full handshake, while maintaining the same security guarantees as the initial connection.

## Relay Encryption

Relay servers in the iroh network (handled in [`iroh-relay/src/tls.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-relay/src/tls.rs)) accept connections over TLS-encrypted HTTP/1.1 before upgrading to the custom relay protocol. Because the underlying transport is already TLS-protected, relays can forward encrypted QUIC frames without ever accessing plaintext payloads. This ensures that even when traffic routes through intermediate relay servers, end-to-end encryption remains intact between the source and destination nodes.

## Code Examples

### Creating an Endpoint with Raw Public Key TLS

```rust
#[cfg(with_crypto_provider)]
async fn make_endpoint() -> n0_error::Result<iroh::Endpoint> {
    // Generate or load the node's Ed25519 secret key
    let secret = iroh_base::SecretKey::generate();
    
    // Build endpoint with TLS configuration automatically applied
    let ep = iroh::Endpoint::builder(iroh::presets::N0)
        .secret_key(secret)  // Injects the key into TlsConfig
        .bind()
        .await?;
    
    Ok(ep)
}

```

The `secret_key` method propagates the key material through `TlsConfig::new`, which initializes `ResolveRawPublicKeyCert` for incoming connections.

### Connecting to a Remote Endpoint

```rust
#[cfg(with_crypto_provider)]
async fn connect(
    ep: &iroh::Endpoint,
    remote_id: iroh::EndpointId,
    remote_addr: iroh::EndpointAddr,
) -> n0_error::Result<()> {
    // TLS handshake happens automatically; EndpointId is the expected public key
    let conn = ep.connect(remote_addr, b"my-alpn").await?;
    
    // All streams are encrypted via the negotiated TLS session
    let mut stream = conn.open_uni().await?;
    stream.write_all(b"hello encrypted world").await?;
    Ok(())
}

```

### Custom Raw Public Key Verification

```rust
use rustls::client::{ServerCertVerified, ServerCertVerifier};
use rustls::{Certificate, ServerName, Error as TlsError};

#[derive(Debug)]
struct RawPkVerifier;

impl ServerCertVerifier for RawPkVerifier {
    fn verify_server_cert(
        &self,
        _roots: &rustls::RootCertStore,
        presented: &[Certificate],
        _dns_name: &ServerName,
        _ocsp_response: &[u8],
        _now: std::time::SystemTime,
    ) -> Result<ServerCertVerified, TlsError> {
        // presented[0] contains the raw public key from ResolveRawPublicKeyCert
        // Verification logic compares against expected EndpointId
        Ok(ServerCertVerified::assertion())
    }
}

```

## Summary

- **iroh uses TLS 1.3** with raw-public-key authentication (RFC 7250) instead of X.509 certificates, implemented in [`src/tls.rs`](https://github.com/n0-computer/iroh/blob/main/src/tls.rs) and [`src/tls/resolver.rs`](https://github.com/n0-computer/iroh/blob/main/src/tls/resolver.rs).
- **Node identity** is based on Ed25519 keys, where the `PublicKey` serves as the `EndpointId` and the `SecretKey` provides signing capabilities via `IrohSecretKey`.
- **Custom certificate resolution** through `ResolveRawPublicKeyCert` presents the public key directly during the TLS handshake, advertising `only_raw_public_keys()`.
- **Traffic encryption** uses AES-128-GCM-SHA256, the mandatory QUIC cipher suite, ensuring confidentiality for all data streams.
- **Relay servers** protect traffic using TLS-encrypted HTTP connections, maintaining end-to-end encryption even when forwarding through intermediaries.

## Frequently Asked Questions

### What cryptographic algorithm does iroh use for node identity?

iroh uses **Ed25519** for node identity. Each node generates a permanent Ed25519 key pair where the secret key signs TLS handshake messages and the public key serves as the `EndpointId`. This implementation resides in [`iroh-base/src/key.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-base/src/key.rs) and provides the foundation for the raw-public-key authentication scheme.

### How does iroh verify peer identity without certificates?

Instead of X.509 certificates, iroh implements custom certificate verifiers in [`src/tls/verifier.rs`](https://github.com/n0-computer/iroh/blob/main/src/tls/verifier.rs). These verifiers check that the peer’s presented raw public key matches the expected `EndpointId`. The `ResolveRawPublicKeyCert` resolver in [`src/tls/resolver.rs`](https://github.com/n0-computer/iroh/blob/main/src/tls/resolver.rs) ensures that only raw public keys are exchanged during the TLS handshake, eliminating the need for certificate chains or Certificate Authorities.

### Does iroh support 0-RTT (zero round-trip) connections?

Yes. iroh configures TLS 1.3 with **early-data (0-RTT)** support enabled through `TlsConfig`. This allows clients that have previously connected to a node to resume encrypted sessions immediately without completing a full handshake, while session ticket caching (`DEFAULT_MAX_TLS_TICKETS`) stores the necessary state for these fast reconnections.

### Is traffic encrypted end-to-end when using relay servers?

Yes. Relay servers in iroh (configured in [`iroh-relay/src/tls.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-relay/src/tls.rs)) accept connections over TLS-encrypted HTTP/1.1 before upgrading to the relay protocol. Because the underlying transport is already TLS-protected, relays forward encrypted QUIC frames without decrypting them, ensuring that only the communicating endpoints can access the plaintext payload.