Skip to content

Commit 23f4ec5

Browse files
author
“ramfox”
committed
chore: punctuation and grammar nits
1 parent 54893ee commit 23f4ec5

File tree

1 file changed

+27
-27
lines changed

1 file changed

+27
-27
lines changed

src/app/blog/0rtt-api/page.mdx

Lines changed: 27 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -29,16 +29,16 @@ export const metadata = {
2929

3030
export default (props) => <BlogPostLayout article={post} {...props} />
3131

32-
Despite all the complexity required to enable dial by node id and direct connections, iroh has a very simple API. You create an endpoint, create connections, create streams, and then send data.
32+
Despite all the complexity required to enable dial-by-node-id and direct connections, iroh has a very simple API. You create an endpoint, create connections, create streams, and then send data.
3333

3434
There is some complexity involved in properly [closing QUIC connections](https://www.iroh.computer/blog/closing-a-quic-connection), but
35-
other than that everything is pretty straightforward.
35+
other than that, everything is pretty straightforward.
3636

3737
One aspect of iroh connections that is a bit more complex is 0-RTT. This blog post explains what 0-RTT is, when to use it, when **not** to use it, and will show a small example.
3838

3939
# What is 0-RTT
4040

41-
Iroh connections are just peer to peer QUIC connections, using a fork of the Quinn rust crate. QUIC is using TLS for encryption.
41+
Iroh connections are just peer-to-peer QUIC connections, using a fork of the Quinn rust crate. QUIC uses TLS for encryption.
4242

4343
So to explain what 0-RTT is, it is helpful to explain how a normal TLS handshake works in detail.
4444

@@ -54,7 +54,7 @@ We refer to logical TLS messages as just messages. Messages can be split into mu
5454

5555
### ClientHello
5656

57-
The connection is initiated from the client by sending a `ClientHello`. This message contains the TLS protocol version, random data, and the set of supported cipher suites. In addition iroh always includes a set of ALPN strings that identify the application level protocols that the client wishes to speak.
57+
The connection is initiated from the client by sending a `ClientHello`. This message contains the TLS protocol version, random data, and the set of supported cipher suites. In addition, iroh always includes a set of ALPN strings that identify the application-level protocols that the client wishes to speak.
5858

5959
When receiving the `ClientHello`, the server has information to narrow down the set of cryptographic primitives to use. The server endpoint also has a set of ALPNs it supports, so it can downselect those as well.
6060

@@ -74,61 +74,61 @@ The server will send the `ServerHello`, possibly additional encrypted messages f
7474

7575
### Session setup
7676

77-
Once the client has received the `ServerHello`, it has collected all the required information to set up the session for transporting user data. At this point the cipher suite and ALPN are fixed for the rest of the session.
77+
Once the client has received the `ServerHello`, it has collected all the required information to set up the session for transporting user-data. At this point the cipher suite and ALPN are fixed for the rest of the session.
7878

7979
<Note>
8080
In the case where there is no overlap between client and server ALPNs, in principle it is possible to fall back to a default ALPN, but this is not relevant for iroh connections.
8181
</Note>
8282

83-
Since the cipher suite is now fixed, the client can now use the client and server random to derive two symmetric keys, one for additional handshake messages and one for application data.
83+
Since the cipher suite is now fixed, the client can now use the client and server random data to derive two symmetric keys, one for additional handshake messages and one for application data.
8484

8585
Every message that the client receives after the `ServerHello` is encrypted with the symmetric keys that are now shared knowledge on both sides.
8686

87-
The client processes possible additional packages from the server until it receives the `Finished` message. At that point it validates that it has received all setup messages form the server using a secure checksum contained in the server's `Finished` message.
87+
The client processes possible additional packages from the server until it receives the `Finished` message. At that point it validates that it has received all setup messages from the server using a secure checksum contained in the server's `Finished` message.
8888

89-
### User data
89+
### User-data
9090

91-
Now the client has ingested and validated all the messages relevant for connection setup, and has derived the keys required for user data encryption. It can finally send a `Finished` message itself, finishing the handshake from its side.
91+
Now the client has ingested and validated all the messages relevant for connection setup, and has derived the keys required for user-data encryption. It can finally send a `Finished` message itself, finishing the handshake from its side.
9292

93-
Immediately after this, the client can send the first message of user data, encrypted with the derived application keys.
93+
Immediately after this, the client can send the first message of user-data, encrypted with the derived application keys.
9494

95-
After the server receives the `Finished` message from the client, it can forward user data to the application space. Also at this time it will send a number of `NewSessionTicket` messages to the client that contains a pre-shared key for session resumption or 0-RTT in subsequent connections.
95+
After the server receives the `Finished` message from the client, it can forward user-data to the application space. Also at this time it will send a number of `NewSessionTicket` messages to the client that contains a pre-shared key for session resumption or 0-RTT in subsequent connections.
9696

97-
As you can see, the handshake is somewhat expensive in terms of computation, but more importantly requires a roundtrip from client to server and back before the first bit of user data can flow, even in the case where both sides have talked recently.
97+
As you can see, the handshake is somewhat expensive in terms of computation, but more importantly, requires a roundtrip from client to server and back before the first bit of user-data can flow, even in the case where both sides have talked recently.
9898

9999
## 0-RTT handshake
100100

101-
In many cases, in particular for long lived connections, the overhead of the handshake is completely acceptable. But there are many protocols where there is just a very brief information exchange between a client and a server, and latency is critical. In these cases, it would be a big advantage for the client to optimistically send user data immediately after the `ClientHello`.
101+
In many cases, in particular for long lived connections, the overhead of the handshake is completely acceptable. But there are many protocols where there is just a very brief information exchange between a client and a server, and latency is critical. In these cases, it would be a big advantage for the client to optimistically send user-data immediately after the `ClientHello`.
102102

103-
For latency critical protocols, you simply get your answer faster. But even for non latency critical protocols, the overhead is reduced because the total duration of the interaction is reduced, so there are fewer requests in flight.
103+
For latency critical protocols, you simply get your answer faster. But even for non-latency critical protocols, the overhead is reduced because the total duration of the interaction is reduced, so there are fewer requests in flight.
104104

105-
0-RTT is only possible if a client has received pre shared keys from the server via `NewSessionTicket` messages, which is the case if they performed a full handshake in the recent past.
105+
0-RTT is only possible if a client has received pre-shared keys from the server via `NewSessionTicket` messages, which is the case if they performed a full handshake in the recent past.
106106

107107
<div className="not-prose">
108108
<img src="/blog/0rtt-api/0rtt-norenew.png" width={1200} height={754} alt="Normal TLS handshake" />
109109
</div>
110110

111111
### ClientHello
112112

113-
Like before, the client sends a `ClientHello` message. The `ClientHello` for a connection attempt using 0-RTT must contain a set of ids of pre shared keys, otherwise it is not possible for the server to decrypt subsequent user data before the full handshake is complete. In addition it has an `early data` flag set.
113+
Like before, the client sends a `ClientHello` message. The `ClientHello` for a connection attempt using 0-RTT must contain a set of ids of pre-shared keys, otherwise it is not possible for the server to decrypt subsequent user-data before the full handshake is complete. In addition it has an `early data` flag set.
114114

115-
Immediately after the `ClientHello`, the client will send a message containing user data. Since a full exchange has not happened yet, the client has to choose one of the previously used PSKs for encryption, typically the most recent one.
115+
Immediately after the `ClientHello`, the client will send a message containing user-data. Since a full exchange has not happened yet, the client has to choose one of the previously used PSKs for encryption, typically the most recent one.
116116

117117
### Server side
118118

119-
On the server side, the server receives the special `ClientHello`. In the happy case, it still has the key for the pre shared key used by the client and will use it to decrypt subsequent messages. It will proceed with sending a `ServerHello` immediately, as before. This message will also have the `early data` flag set to indicate that the server might also send data before the handshake is complete.
119+
On the server side, the server receives the special `ClientHello`. In the happy case, it still has the key for the pre-shared key used by the client and will use it to decrypt subsequent messages. It will proceed with sending a `ServerHello` immediately, as before. This message will also have the `early data` flag set to indicate that the server might also send data before the handshake is complete.
120120

121-
Once the server receives the early user data message, if it accepts 0-rtt requests, it will attempt to decrypt the message and forward it to the application space. The application space can then answer with an early data message on its own.
121+
Once the server receives the early user-data message, if it accepts 0-rtt requests, it will attempt to decrypt the message and forward it to the application space. The application space can then answer with an early data message on its own.
122122

123123
### Handshake completes
124124

125125
Regardless of early data in any direction, the normal handshake continues as before. Once the client receives a `Finished` message from the server, it will compute symmetric encryption keys as before and then switch to this set of keys for all subsequent messages. Likewise, once the server receives a `Finished` message from the client, it will compute keys and switch encryption.
126126

127127
### Replay attacks
128128

129-
By now we should have a pretty good idea what 0-RTT is and why it is useful. Being able to send user data in the first UDP message seems tremendously useful. So why isn't it the default?
129+
By now we should have a pretty good idea what 0-RTT is and why it is useful. Being able to send user-data in the first UDP message seems tremendously useful. So why isn't it the default?
130130

131-
The client message uses a previously received pre shared key for encryption. For as long as this PSK is valid on the server, you can just re-send the *exact same packet*, and the user data will be sent to the application on the server side. This could be used to create unwanted changes on the server in case of a non-idempotent request, or to create a large load by sending the same request millions of times.
131+
The client message uses a previously received pre-shared key for encryption. For as long as this PSK is valid on the server, you can just re-send the *exact same packet*, and the user-data will be sent to the application on the server side. This could be used to create unwanted changes on the server in case of a non-idempotent request, or to create a large load by sending the same request millions of times.
132132

133133
In addition, due to the fact that the process of computing the session keys from the PSK is - and has to be - fully deterministic, once an attacker has access to the key, they can decrypt all past or future 0-RTT data encrypted using that key. Since the session switches encryption after the handshake completes, data after handshake completion is not affected by this.
134134

@@ -146,7 +146,7 @@ Now let's implement a simple 0rtt service in iroh. It is going ot be a simple ec
146146

147147
On the accept side, this is relatively easy. We do have to opt in to allowing 0rtt requests, since as we have seen 0rtt has reduced security guarantees compared to normal requests with a full handshake before the data starts to flow.
148148

149-
When we get a `Connecting` by calling `incoming.accept()?`, we try to convert it into a 0rtt connection using `.into_0rtt()`. This will always succeed on the server side, but we have to handle the error anyway because we use the same types on the server and client side. Once that is done, the code is identical to a non 0rtt echo service.
149+
When we get a `Connecting` by calling `incoming.accept()?`, we try to convert it into a 0rtt connection using `.into_0rtt()`. This will always succeed on the server side, but we have to handle the error anyway because we use the same types on the server and client side. Once that is done, the code is identical to a non-0rtt echo service.
150150

151151
```rust
152152
let connecting = incoming.accept()?;
@@ -277,15 +277,15 @@ round 6: 510681 us
277277

278278
Now this is weird.
279279

280-
The initial connection is not 0-RTT, which is expected. The two nodes haven't ever talked, so there is no pre shared secret they can use to communicate.
280+
The initial connection is not 0-RTT, which is expected. The two nodes haven't ever talked, so there is no pre-shared secret they can use to communicate.
281281

282282
The next two rounds are 0-RTT, as expected, and have roughly the expected roundtrip times.
283283

284284
But then we get a non 0-RTT connection on the 4th round. What's going on?
285285

286286
The issue is that 0-RTT is done using *single-use* tickets. *After* each completed handshake, you get a number of these tickets, by default 2. And as soon as the tickets are used up, the client can not even *attempt* to do a 0-rtt request because it has run out of tickets to use.
287287

288-
In the code so far we close the connection immediately after receiving the user data response. So we don't wait for the full handshake to complete, and also don't get any additional tickets.
288+
In the code so far we close the connection immediately after receiving the user-data response. So we don't wait for the full handshake to complete, and also don't get any additional tickets.
289289

290290
```rust
291291
pingpong_0rtt(connecting, i).await?
@@ -300,7 +300,7 @@ The tickets for 0-RTT connections are single use in Quinn, as recommended in the
300300

301301
There is currently no API to wait for the handshake to complete or to wait for session tickets. But we can just sleep for two times the current round trip time before closing the connection. This is a crude way to allow for enough time for the two `NewSessionTicket`s to be received.
302302

303-
Since we don't want to hold up the user space just because we are waiting for an additional message, we do this in a tokio task.
303+
Since we don't want to hold up the user-space just because we are waiting for an additional message, we do this in a tokio task.
304304

305305
```rust
306306
tokio::spawn(async move {
@@ -329,7 +329,7 @@ So in theory you could modify the client to reuse the ticket. This is nothing we
329329

330330
Let's take a step back and think about when you would use 0-RTT. If you have two nodes that communicate frequently every few seconds or even minutes, it is probably best to just leave the connection open. A normal QUIC connection has very low overhead, and even for a hole-punched iroh QUIC connection the overhead of occasional messages to keep the connection open is very acceptable.
331331

332-
So you would use 0-RTT if communication is in small bursts spaced many minutes to hours apart, but nevertheless latency is important. In this case the most important property of 0-RTT is that the application gets a response after the minimal physically possible delay. Keeping the connection open for another small time period to refresh the pre shared keys is not a big deal.
332+
So you would use 0-RTT if communication is in small bursts spaced many minutes to hours apart, but nevertheless latency is important. In this case, the most important property of 0-RTT is that the application gets a response after the minimal physically possible delay. Keeping the connection open for another small time period to refresh the pre-shared keys is not a big deal.
333333

334334
So this is the option we ended up implementing. Here is the output after this modification. We now get a 0-rtt connection every time.
335335

@@ -434,7 +434,7 @@ cargo run --release --features=examples --example 0rtt
434434
For the connect side, you have to provide the ticket produced by the accept side.
435435

436436
```
437-
cargo run --release --features=examples --example 0rtt <ticket>
437+
cargo run --release --features=examples --example 0rtt TICKET
438438
```
439439

440440
You can also connect to our test node that should be far away from most readers of this blog post:

0 commit comments

Comments
 (0)