# How iroh's 0-RTT Connection Works: Zero-Round-Trip QUIC Establishment

> Discover how iroh achieves 0-RTT connections. Learn how to immediately transmit data before the QUIC handshake completes and resume cached TLS sessions efficiently.

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

---

**iroh enables zero-round-trip-time (0-RTT) connections by allowing clients to resume cached TLS sessions through the `into_0rtt()` method on a `Connecting` object, which returns an `OutgoingZeroRttConnection` that permits immediate data transmission before the QUIC handshake completes.**

iroh implements 0-RTT connection establishment using the QUIC stack provided by the *noq* crate. This mechanism eliminates the initial handshake latency for returning clients by leveraging TLS session resumption, allowing application data to flow immediately while the cryptographic handshake finishes in the background.

## The QUIC Foundation for 0-RTT

At the transport layer, iroh relies on the *noq* crate to handle the underlying QUIC protocol implementation. When a client has previously communicated with a server, it can store the TLS session parameters and attempt to resume them on subsequent connections. This resumption capability is the foundation of 0-RTT: the client sends data alongside the handshake rather than waiting for the server to confirm the connection.

## The 0-RTT Connection Flow

The establishment process follows a specific sequence that attempts to convert a standard connection into a 0-RTT variant, then handles the server's acceptance or rejection of the early data.

### Creating the Client Endpoint

The process begins with initializing an iroh endpoint that wraps the underlying QUIC transport. You create a client endpoint by binding to a local address and specifying the remote peer's credentials.

```rust
let client = iroh::Endpoint::client("0.0.0.0:0".parse::<SocketAddr>()?).await?;
let connecting = client.connect(remote_addr, remote_name);

```

### Converting to 0-RTT Mode

Once you have a `Connecting` object, you attempt to convert it to 0-RTT mode using the `into_0rtt()` method. According to the source code in [`iroh/src/endpoint/connection.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint/connection.rs) (lines 92–102), this method internally calls `noq::Connecting::into_0rtt()` to request TLS session resumption.

The method returns a `Result<OutgoingZeroRttConnection, Connecting>`:
- **Success**: Returns `OutgoingZeroRttConnection` if the session can be resumed
- **Failure**: Returns the original `Connecting` if 0-RTT is not possible

```rust
let outgoing = match connecting.into_0rtt() {
    Ok(zrt) => zrt,      // 0-RTT possible, early data allowed
    Err(conn) => conn,   // Fall back to normal handshake
};

```

### Handling Acceptance or Rejection

The `OutgoingZeroRttConnection` contains a future that resolves when the handshake completes and indicates whether the server accepted the 0-RTT data. As implemented in [`iroh/src/endpoint/connection.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint/connection.rs) (lines 131–148), the internal `zrtt_accepted` future resolves to a boolean indicating server acceptance.

You must await `handshake_completed()` (lines 240–260 in the same file), which returns a `ZeroRttStatus` enum:
- **`ZeroRttStatus::Accepted(conn)`**: The server accepted early data; streams opened before the handshake remain valid
- **`ZeroRttStatus::Rejected(conn)`**: The server rejected early data; pre-handshake streams will error and must be reopened

```rust
match outgoing.handshake_completed().await? {
    iroh::endpoint::ZeroRttStatus::Accepted(conn) => {
        // Early data succeeded, use existing streams
        let mut stream = conn.open_bi().await?;
        stream.send_data(b"early data".into()).await?;
    }
    iroh::endpoint::ZeroRttStatus::Rejected(conn) => {
        // Must resend data after handshake completes
    }
}

```

## Complete Implementation Example

The following example demonstrates the full client-side flow, including binding, connecting, attempting 0-RTT conversion, and handling the result. This pattern mirrors the runnable demonstration found in [`iroh/examples/0rtt.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/examples/0rtt.rs).

```rust
use iroh::{Endpoint, Result};
use std::net::SocketAddr;

#[tokio::main]
async fn main() -> Result<()> {
    // Create a client endpoint (bind to an OS-chosen UDP port)
    let client = Endpoint::client("0.0.0.0:0".parse::<SocketAddr>()?).await?;

    // Initiate a connection to a peer
    let connecting = client.connect("198.51.100.42:443".parse()?, "example.com");

    // Attempt 0-RTT conversion
    let outgoing = match connecting.into_0rtt() {
        Ok(zrt) => zrt,
        Err(conn) => conn,
    };

    // Wait for handshake completion and check status
    match outgoing.handshake_completed().await? {
        iroh::endpoint::ZeroRttStatus::Accepted(conn) => {
            println!("0-RTT accepted! Streams opened earlier are usable.");
            let mut stream = conn.open_bi().await?;
            stream.send_data(b"early data".into()).await?;
        }
        iroh::endpoint::ZeroRttStatus::Rejected(conn) => {
            println!("0-RTT rejected – resend data after handshake.");
        }
    }

    Ok(())
}

```

## Key Source Files

The implementation spans several critical files in the iroh repository:

- **[`iroh/src/endpoint/connection.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint/connection.rs)**: Contains the `Connecting::into_0rtt()` method (lines 92–102), the `ZeroRttStatus` enum definition, and the `handshake_completed()` future logic (lines 129–148 and 240–260)
- **[`iroh/examples/0rtt.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/examples/0rtt.rs)**: Provides a minimal runnable example demonstrating the complete 0-RTT client flow
- **[`iroh/src/endpoint/quic.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint/quic.rs)**: Configures QUIC transport parameters that affect connection establishment timing

## Summary

- **iroh uses the *noq* crate** to provide QUIC transport capabilities including TLS session resumption
- **`Connecting::into_0rtt()`** in [`iroh/src/endpoint/connection.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint/connection.rs) attempts to resume sessions and enable immediate data transmission
- **The conversion returns `OutgoingZeroRttConnection`** when successful, or falls back to standard `Connecting` if resumption fails
- **`handshake_completed()`** resolves to `ZeroRttStatus::Accepted` (early data valid) or `Rejected` (must resend after handshake)
- **Application code must handle rejection** by retrying stream operations after the handshake finishes

## Frequently Asked Questions

### What happens if the server rejects 0-RTT data?

If the server rejects the 0-RTT data, the `handshake_completed()` future resolves to `ZeroRttStatus::Rejected(conn)`. Any streams opened before the handshake completed will error, and the application must reopen them after the handshake finishes to ensure data delivery. The connection itself remains valid for use after the rejection.

### How does iroh store TLS session tickets for 0-RTT?

The iroh source code delegates TLS session management to the underlying *noq* QUIC implementation. The `into_0rtt()` method in [`iroh/src/endpoint/connection.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint/connection.rs) simply requests resumption via `noq::Connecting::into_0rtt()`, which handles the cryptographic state caching required for session resumption according to QUIC-TLS specifications.

### Is 0-RTT secure for all types of data?

0-RTT data is encrypted and integrity-protected, but it lacks forward secrecy guarantees because it is sent before the complete handshake exchange. According to the iroh architecture, applications should treat 0-RTT data as potentially replayable and avoid sending sensitive idempotent operations or non-idempotent state-changing requests until after receiving `ZeroRttStatus::Accepted`.

### Where can I find a complete working example?

The repository includes a dedicated example at [`iroh/examples/0rtt.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/examples/0rtt.rs) that demonstrates the complete client workflow from endpoint creation through handshake completion. This file shows proper error handling for both accepted and rejected 0-RTT scenarios and illustrates how to fall back to standard connection establishment when resumption is unavailable.