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

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, 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, 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 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) 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
// 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 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) 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

#[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

#[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

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 and 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 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. These verifiers check that the peer’s presented raw public key matches the expected EndpointId. The ResolveRawPublicKeyCert resolver in 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) 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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →