# ZeroRTT Connection Establishment in iroh: How It Works and How to Use It

> Learn how ZeroRTT connection establishment in iroh enables immediate data transfer during QUIC handshake. Explore the technical details and usage for faster communication.

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

---

**ZeroRTT connection establishment in iroh allows clients to send application data immediately without waiting for the TLS handshake to complete, using QUIC's 0-RTT mode via the underlying `noq` library.**

In peer-to-peer networking, reducing connection latency is critical for performance. The iroh library implements **ZeroRTT (0-RTT)** connection establishment on top of QUIC, enabling clients to transmit data in the first packet when session resumption state is available.

## How ZeroRTT Works in iroh

The implementation centers on the `Connecting` and `ZeroRttConnection` types defined in [`iroh/src/endpoint/connection.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint/connection.rs). When a client has previously connected to a remote endpoint, it can skip the round-trip handshake and begin sending data immediately.

### Initiating a ZeroRTT Connection

The process begins with `Endpoint::connect_with_opts`, which returns a `Connecting` future. To attempt 0-RTT, the caller invokes `Connecting::into_0rtt` at lines 92-100 of [`connection.rs`](https://github.com/n0-computer/iroh/blob/main/connection.rs):

```rust
let connecting = endpoint
    .connect_with_opts(remote_id, alpn, Default::default())
    .await?;

// Attempt to convert into 0-RTT mode
let zrtt_conn = match connecting.into_0rtt() {
    Ok(conn) => conn,  // 0-RTT available
    Err(conn) => {
        // Fallback to standard handshake
        let standard_conn = conn.await?;
        standard_conn
    }
};

```

If session resumption state exists on both sides, `into_0rtt()` returns an `OutgoingZeroRttConnection`. If not, it returns `Err(Connecting)`, forcing a standard handshake.

### Sending Early Data

Once obtained, the `OutgoingZeroRttConnection` allows immediate stream creation via `open_bi()` or `open_uni()` before the cryptographic handshake completes. As noted in lines 21-23 of [`connection.rs`](https://github.com/n0-computer/iroh/blob/main/connection.rs), streams expose an `is_0rtt()` method so applications can detect when they are operating in reduced-security mode.

```rust
// Open a bidirectional stream immediately
let (send, recv) = zrtt_conn.open_bi().await?;
assert!(send.is_0rtt());  // True until handshake completes

```

### Handling Handshake Completion

The critical component managing the transition is `OutgoingZeroRttConnection::handshake_completed()`, implemented at lines 124-130. This method awaits the internal `zrtt_accepted` future and returns a `ZeroRttStatus` enum:

- **`ZeroRttStatus::Accepted(conn)`**: The server accepted the early data; the connection upgrades to the standard `Connection` type.
- **`ZeroRttStatus::Rejected(conn)`**: The server discarded the 0-RTT data; the connection is valid but any streams opened during 0-RTT are aborted.

```rust
match zrtt_conn.handshake_completed().await? {
    ZeroRttStatus::Accepted(conn) => {
        // Early data kept; continue using `conn`
    }
    ZeroRttStatus::Rejected(conn) => {
        // Must re-send data; previous streams aborted
    }
}

```

### Server-Side ZeroRTT Acceptance

On the server side, incoming connections are wrapped in an `Accepting` struct. Calling `Accepting::into_0rtt` yields an `IncomingZeroRttConnection` that receives early data if the client sent any (lines 93-106). The server processes the data immediately but retains the ability to reject it later, which propagates to the client as described above.

## Security Considerations for ZeroRTT

According to the documentation in [`connection.rs`](https://github.com/n0-computer/iroh/blob/main/connection.rs) lines 16-20, **0-RTT data is vulnerable to replay attacks**. Because the data is sent before the TLS handshake completes, an attacker could replay these packets. Applications must ensure that any operations performed during 0-RTT are idempotent. Non-idempotent operations must wait for `handshake_completed()` to return `ZeroRttStatus::Accepted`.

## Implementation Example

The [`iroh/examples/0rtt.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/examples/0rtt.rs) file demonstrates a complete client-server implementation.

### Client-Side ZeroRTT Connection

This example shows the conditional logic for handling both accepted and rejected 0-RTT scenarios:

```rust
let connecting = endpoint
    .connect_with_opts(remote_id, PINGPONG_ALPN, Default::default())
    .await?;

let connection = match connecting.into_0rtt() {
    Ok(zrtt_conn) => {
        // Start work immediately
        let (send, recv) = zrtt_conn.open_bi().await?;
        let early_task = tokio::spawn(send_data(send));
        
        match zrtt_conn.handshake_completed().await? {
            ZeroRttStatus::Accepted(conn) => {
                early_task.await?;  // Data was kept
                conn
            }
            ZeroRttStatus::Rejected(conn) => {
                early_task.abort();  // Discard rejected work
                // Re-establish streams on validated connection
                let (s, r) = conn.open_bi().await?;
                send_data(s).await?;
                conn
            }
        }
    }
    Err(conn) => {
        // Standard handshake required
        conn.await?
    }
};

```

### Server-Side ZeroRTT Handling

Servers enable 0-RTT by calling `into_0rtt()` on the `Accepting` struct:

```rust
while let Some(incoming) = endpoint.accept().await {
    tokio::spawn(async move {
        let accepting = incoming.accept()?;
        let connection = accepting.into_0rtt();
        let (mut send, mut recv) = connection.accept_bi().await?;
        
        // Check if data arrived before handshake
        if recv.is_0rtt() {
            println!("Received early data");
        }
        
        let data = recv.read_to_end(1024).await?;
        send.write_all(&data).await?;
        send.finish().await?;
        Ok::<_, n0_error::Error>(())
    });
}

```

## Summary

- **ZeroRTT connection establishment** eliminates handshake latency by allowing immediate data transmission using cached TLS session state.
- **`Connecting::into_0rtt`** in [`iroh/src/endpoint/connection.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint/connection.rs) initiates the process, returning either an `OutgoingZeroRttConnection` or falling back to standard handshake.
- **Early streams** opened during 0-RTT must be considered insecure until `handshake_completed()` confirms acceptance via `ZeroRttStatus::Accepted`.
- **Replay protection** is the application's responsibility; only idempotent operations should occur before handshake validation.
- **Server implementation** uses `Accepting::into_0rtt` to receive early data, with rejection propagating to the client's status check.

## Frequently Asked Questions

### What is required for ZeroRTT to work in iroh?

ZeroRTT requires that the client has previously connected to the same remote endpoint and cached the TLS session resumption state. The server must also retain the corresponding state and be configured to allow session resumption. If either side lacks this state, as implemented in [`iroh/src/tls/resolver.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/tls/resolver.rs), `Connecting::into_0rtt` returns an error and the connection falls back to a standard handshake.

### What happens if the server rejects ZeroRTT data?

When the server rejects 0-RTT data, the client's `handshake_completed()` method returns `ZeroRttStatus::Rejected(conn)` where `conn` is a valid `Connection` object. According to the logic in [`iroh/src/endpoint/connection.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint/connection.rs) lines 124-130, any streams opened during the 0-RTT phase are automatically aborted, and subsequent operations on those streams return a `ZeroRttRejected` error. The application must re-send the data over the new, validated connection.

### How does iroh handle replay attacks in ZeroRTT mode?

Iroh does not provide automatic replay protection for 0-RTT data. As documented in [`connection.rs`](https://github.com/n0-computer/iroh/blob/main/connection.rs) lines 16-20, the library explicitly warns that 0-RTT data is vulnerable to replay attacks. The application layer must ensure that any operations performed during the 0-RTT phase are idempotent, or defer non-idempotent operations until after `handshake_completed()` confirms the connection is accepted.

### Can all types of streams use ZeroRTT in iroh?

Yes, both bidirectional (`open_bi`) and unidirectional (`open_uni`) streams support ZeroRTT mode. The `OutgoingZeroRttConnection` exposes these methods identically to a standard connection. The `is_0rtt()` method on the stream objects allows the application to distinguish between early data (sent before handshake completion) and data sent after the connection is fully validated.