# How 0-RTT Connection Establishment Works in iroh: A Complete Guide

> Explore how 0-RTT connection establishment works in iroh. Learn about session resumption and status outcomes with this comprehensive guide.

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

---

**iroh implements 0-RTT connection establishment by wrapping the noq QUIC stack and exposing `Connecting::into_0rtt()`, which attempts to resume previous TLS sessions and returns an `OutgoingZeroRttConnection` that resolves to either `ZeroRttStatus::Accepted` or `ZeroRttStatus::Rejected` after the handshake completes.**

Understanding **0-RTT connection establishment in iroh** is critical for building low-latency applications that need to send data immediately without waiting for a full TLS handshake. The iroh library, developed by n0-computer, leverages the *noq* QUIC implementation to provide zero-round-trip-time resumption while exposing a safe Rust API for handling session acceptance and rejection.

## The QUIC Stack: noq and Session Resumption

iroh delegates its transport layer to the **noq** crate, which manages the underlying QUIC protocol and TLS 1.3 session tickets. When a client has previously connected to a server and obtained session resumption tickets, noq stores these credentials to enable subsequent 0-RTT attempts. The iroh crate wraps noq’s low-level primitives and provides the high-level `Connecting` and `OutgoingZeroRttConnection` types that application code interacts with.

The 0-RTT capability is exposed through the `iroh::endpoint::Connecting` struct. According to the source code in [`iroh/src/endpoint/connection.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint/connection.rs), the conversion to 0-RTT mode happens through the `into_0rtt()` method, which internally bridges to `noq::Connecting::into_0rtt()` at lines 92-102.

## Step-by-Step 0-RTT Flow

### 1. Create the Client Endpoint

Begin by initializing an `iroh::Endpoint` bound to a local UDP socket. This endpoint manages the underlying QUIC state and connection pool.

```rust
use iroh::Endpoint;
use std::net::SocketAddr;

let client = Endpoint::client("0.0.0.0:0".parse::<SocketAddr>()?).await?;

```

### 2. Initiate the Connection

Call `Endpoint::connect()` with the remote address and server name. This returns a `Connecting` future that represents the in-progress handshake. At this stage, the QUIC stack checks its session cache for resumption tickets.

```rust
let connecting = client.connect("198.51.100.42:443".parse()?, "example.com").await?;

```

### 3. Attempt 0-RTT Conversion

Invoke `into_0rtt()` on the `Connecting` object. As implemented in [`iroh/src/endpoint/connection.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint/connection.rs) (lines 92-102), this method attempts to resume the TLS session. It returns:

- `Ok(OutgoingZeroRttConnection)` if 0-RTT is possible
- `Err(Connecting)` if no valid session ticket exists, falling back to standard 1-RTT

```rust
let outgoing = match connecting.into_0rtt() {
    Ok(zrtt) => zrtt,     // Proceed with 0-RTT
    Err(conn) => conn,    // Fallback to normal handshake
};

```

### 4. Wait for Remote Acceptance

The `OutgoingZeroRttConnection` contains an internal future `zrtt_accepted` that resolves when the remote peer validates the session ticket. In [`iroh/src/endpoint/connection.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint/connection.rs) (lines 129-140), this maps to `noq` returning a tuple `(Connection, zrtt_accepted)`. The future yields a `bool` indicating whether the server accepted the 0-RTT data.

You await the final handshake status via `handshake_completed()` (lines 240-260), which returns a `ZeroRttStatus` enum.

### 5. Handle ZeroRttStatus

The `ZeroRttStatus` enum distinguishes between accepted and rejected scenarios:

- **`ZeroRttStatus::Accepted(conn)`**: The server validated the session. Streams opened before the handshake remain usable, and your early data was transmitted successfully.
- **`ZeroRttStatus::Rejected(conn)`**: The server rejected the session resumption. Any streams opened during the 0-RTT phase will error, and you must reopen them after the handshake completes.

```rust
match outgoing.handshake_completed().await? {
    iroh::endpoint::ZeroRttStatus::Accepted(conn) => {
        // 0-RTT data accepted, use existing streams
        let (mut send, recv) = conn.open_bi().await?;
    }
    iroh::endpoint::ZeroRttStatus::Rejected(conn) => {
        // Must retry streams after full handshake
        let (mut send, recv) = conn.open_bi().await?;
    }
}

```

## Handling Acceptance and Rejection: ZeroRttStatus

The `ZeroRttStatus` enum defined in [`iroh/src/endpoint/connection.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint/connection.rs) (lines 92-101) is the primary mechanism for handling the dual-outcome nature of 0-RTT. Because 0-RTT data is sent before cryptographic parameters are fully verified, applications must treat rejection gracefully.

When rejected, the underlying `noq` connection upgrades from 0-RTT to 1-RTT mode, but stream IDs allocated during the 0-RTT phase become invalid. The iroh API abstracts this complexity by ensuring that when `ZeroRttStatus::Rejected` is returned, the provided `Connection` object is fresh and ready for new streams, while any previous stream attempts receive explicit errors indicating they should be retried.

## Practical Implementation Example

The repository includes a runnable demonstration in [`iroh/examples/0rtt.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/examples/0rtt.rs). Below is a condensed version showing the complete client-side flow:

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

#[tokio::main]
async fn main() -> Result<()> {
    // Bind to an OS-chosen UDP port
    let client = Endpoint::client("0.0.0.0:0".parse::<SocketAddr>()?).await?;
    
    // Initiate connection (must have connected previously for 0-RTT)
    let connecting = client.connect("198.51.100.42:443".parse()?, "example.com")?;
    
    // Attempt 0-RTT conversion
    let outgoing = match connecting.into_0rtt() {
        Ok(zrtt) => zrtt,
        Err(connecting) => {
            println!("No session ticket available, using 1-RTT");
            // Wait for standard handshake if needed, or handle accordingly
            todo!()
        }
    };
    
    // Await handshake completion and check acceptance status
    match outgoing.handshake_completed().await? {
        iroh::endpoint::ZeroRttStatus::Accepted(conn) => {
            println!("0-RTT accepted");
            let mut stream = conn.open_uni().await?;
            stream.write(b"early data").await?;
        }
        iroh::endpoint::ZeroRttStatus::Rejected(conn) => {
            println!("0-RTT rejected - resending after handshake");
            let mut stream = conn.open_uni().await?;
            stream.write(b"data after handshake").await?;
        }
    }
    
    Ok(())
}

```

## Key Source Files and Their Roles

| File | Purpose |
|------|---------|
| [`iroh/src/endpoint/connection.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint/connection.rs) | Contains the `Connecting::into_0rtt()` implementation (lines 92-102), `ZeroRttStatus` enum definition, and the `zrtt_accepted` future handling (lines 129-148). |
| [`iroh/src/endpoint/quic.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint/quic.rs) | Configures QUIC transport parameters and session ticket handling that enable 0-RTT resumption. |
| [`iroh/examples/0rtt.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/examples/0rtt.rs) | Provides a complete, runnable client example demonstrating successful 0-RTT session resumption. |
| [`iroh-relay/src/quic.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-relay/src/quic.rs) | Implements the server-side QUIC listener with ALPN identifier `/iroh-qad/0` and address discovery mechanisms relevant to session validation. |

## Summary

- **iroh uses the noq crate** for underlying QUIC and TLS 1.3 session management, exposing 0-RTT through the `Connecting::into_0rtt()` method.
- **Conversion happens in** [`iroh/src/endpoint/connection.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint/connection.rs) **lines 92-102**, where the library attempts to resume previous sessions.
- **Two outcomes exist**: `ZeroRttStatus::Accepted` allows immediate stream usage, while `ZeroRttStatus::Rejected` requires reopening streams after the full handshake.
- **Always await** `handshake_completed()` (lines 240-260) to determine whether early data was accepted before relying on it.
- **Reference the** [`iroh/examples/0rtt.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/examples/0rtt.rs) **file** for production-ready patterns handling both acceptance and rejection scenarios.

## Frequently Asked Questions

### What is the difference between 0-RTT and 1-RTT in iroh?

**0-RTT allows sending application data during the initial handshake using cached session keys, while 1-RTT completes the full TLS handshake before any data flows.** In iroh, 0-RTT is achieved by calling `into_0rtt()` on a `Connecting` object, which attempts to resume a previous session. If no ticket exists or the server rejects resumption, the connection falls back to 1-RTT mode, requiring a complete cryptographic negotiation before data transmission.

### How does iroh determine if 0-RTT data is accepted?

**The remote peer validates the TLS session ticket and ALPN identifier.** In [`iroh/src/endpoint/connection.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint/connection.rs) (lines 129-140), the library awaits a `zrtt_accepted` future returned by the underlying noq stack. This future resolves to a boolean indicating whether the server accepted the resumed session. The `handshake_completed()` method then wraps this result into the `ZeroRttStatus` enum, providing a clean API for the application to handle the outcome.

### What happens to streams opened before the handshake completes if 0-RTT is rejected?

**Streams opened during the 0-RTT phase error out and must be retried.** When `ZeroRttStatus::Rejected` is returned, the `Connection` object provided is valid and ready for new streams, but any streams initiated before the handshake are invalidated. According to the implementation in [`iroh/src/endpoint/connection.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint/connection.rs), applications must reopen streams after receiving the `Rejected` status to ensure data reaches the peer reliably.

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

**No, 0-RTT data is vulnerable to replay attacks and should not be used for state-changing operations without idempotency safeguards.** While iroh handles the cryptographic session resumption correctly via noq, the library documentation (see README.md) warns that applications must treat 0-RTT data as potentially replayable. Sensitive or non-idempotent requests should be delayed until the `ZeroRttStatus::Accepted` confirmation is received or sent over the post-handshake 1-RTT connection.