My question has to do with "stateless" Hello Retry Requests between a Client and Server doing a TLS 1.3 Handshake.

First, some context:

Relevent portion of the TLS 1.3 RFC: https://www.rfc-editor.org/rfc/rfc8446#section-4.4.1

4.4.1.  The Transcript Hash

Many of the cryptographic computations in TLS make use of a
transcript hash.  This value is computed by hashing the concatenation
of each included handshake message, ...  I.e.,

Transcript-Hash(M1, M2, ... Mn) = Hash(M1 || M2 || ... || Mn)


For concreteness, the transcript hash is always taken from the
following sequence of handshake messages, starting at the first
ClientHello and including only those messages that were sent:
ClientHello, HelloRetryRequest, ClientHello, ServerHello,
EncryptedExtensions, server CertificateRequest, server Certificate,
server CertificateVerify, server Finished, EndOfEarlyData, client
Certificate, client CertificateVerify, client Finished

M1, M2, ... MN are the messages sent/received on the wire (minus record header). So the transcript hash of the first N messages would be a concatenation of these messages:

Transcript-Hash(Client Hello || Server Hello || Encrypted Extensions, ... )

If the Client's initial Client Hello "guessed" the wrong ECDH protocol, the Server can respond with a HelloRetryRequest, correcting the Client's guess, and asking the Client to try again with a new Client Hello. Both the original Client Hello (CH1), and the HelloRetryRequest (HRR), and the new ClientHello (CH2) are included in the transcript hash.

If the same Client is connecting to the same Server, then this all makes sense... both parties see the same CH1, HRR, and CH2.

But TLS 1.3 allows this process to be done "statelessly" such that the subsequence CH2 does not necessarily land on the same original server, or that the server doesn't need to "remember" anything from the original CH1 and HRR.

This is done with the original server including a "Cookie" in the HRR, which the client will provide in the CH2 in the next request to the server. The content of this Cookie allow the server to "rebuild" the content from CH1/HRR that it may not have received (or remembered)

To accommodate this, CH1 is not concatenated with the rest of the handshake records to create the transcript hash, instead a hash of CH1 is concatenated with the rest of the handshake records to create the transcript hash.

Details here (also from Section 4.4.1):

As an exception to this general rule, when the server responds to a
ClientHello with a HelloRetryRequest, the value of ClientHello1 is
replaced with a special synthetic handshake message of handshake type
"message_hash" containing Hash(ClientHello1).  I.e.,

Transcript-Hash(ClientHello1, HelloRetryRequest, ... Mn) =
   Hash(message_hash ||        /* Handshake type */
        00 00 Hash.length  ||  /* Handshake message length (bytes) */
        Hash(ClientHello1) ||  /* Hash of ClientHello1 */
        HelloRetryRequest  || ... || Mn) 

The reason for this construction is to allow the server to do a
stateless HelloRetryRequest by storing just the hash of ClientHello1
in the cookie, rather than requiring it to export the entire
intermediate hash state (see Section 4.2.2).

Which brings me to my question:

Since only CH1 is included in the exception above, and not HRR... how would a server statelessly put together the concatenation of HHR with the rest of Handshake Records in the Transcript Hash?

I see an accommodation being made to "bounce" the CH1 hash off the Client using the Cookie... but I'm not seeing anywhere in the RFC where the same accommodation is made for the Hello Retry Request.

1 Answer 1


Regarding your last point, it is impossible to compute a hash of the HRR and to store that inside a cookie, as changing the cookie would change the HRR and the client would compute a different HRR hash than the one stored inside the cookie. Leaving the client and server with different transcripts...

Like you, I don't understand how that construction makes it possible for the HRR to be stateless. I don't even understand what the added security of keeping the ClientHello1 and HelloRetryRequest inside the transcript-hash instead of "simply" starting anew with ClientHello2.

The RFC is explicit that the transcript-hash shouldn't be reset but there is no explanation why

From section 2.1

Note: The handshake transcript incorporates the initial ClientHello/HelloRetryRequest exchange; it is not reset with the new ClientHello.

I guess we have to implement that silly construction and get none of its advertised benefits...

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .