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:
- A
QuicClientConfigfor outgoing connections - A
QuicServerConfigfor incoming connections - Session ticket caching (
DEFAULT_MAX_TLS_TICKETS) for fast resumption - 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::CertifiedKeyfrom the node’sIrohSecretKey - Advertises
only_raw_public_keys()→trueto 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.rsandsrc/tls/resolver.rs. - Node identity is based on Ed25519 keys, where the
PublicKeyserves as theEndpointIdand theSecretKeyprovides signing capabilities viaIrohSecretKey. - Custom certificate resolution through
ResolveRawPublicKeyCertpresents the public key directly during the TLS handshake, advertisingonly_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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →