Change:
Since the client's ESNI key share doesn't change across CHs in response to a HRR, the same AEAD IV would be used to encrypt/decrypt two different messages. This is an attempt to fix that, while also specifying the server-side HRR behavior. (Basically, check that the ESNI contents have not changed.)
Nice catch, Would it make more sense to instead use the KeyShare as a salt in the HKDF?
Or you could include the handshake transcript up to, but not including, the ClientHello. That is the empty string before HRR< but something else after.
That is a little iffy with cookie-only HelloRetryRequests. Those don't change the ClientHello key shares list, so the client would derive the same key + nonce pair. The client would also use the same plaintext so it's fine, but it seems an unnecessarily fragility.
Agreed. I think I prefer the "new DH" version.
Thank you for the changes! LGTM.

Discussion:
The ESNIKeys structure contains the following fields: The version of the structure.  For this specification, that value <del> SHALL be 0xff02.  Clients MUST ignore any ESNIKeys structure with </del> <ins> SHALL be 0xff03.  Clients MUST ignore any ESNIKeys structure with </ins> a version they do not understand.  [[NOTE: This means that the RFC will presumably have a nonzero value.]]

where ESNIContents is as specified below and Hash is the hash function associated with the HKDF instantiation.  The salt argument for HKDF-Extract is a string consisting of Hash.length bytes set to <del> zeros. </del> <ins> zeros.  For a client's first ClientHello, KeyLabel = "esni key" and IVLabel = "esni iv", whereas for a client's second ClientHello, sent in response to a HelloRetryRequest, KeyLabel = "hrr esni key" and IVLabel = "hrr esni iv".  (This label variance is done to prevent nonce re-use since the client's ESNI key share, and thus the value of Zx, does not change across ClientHello retries.) [[TODO: label swapping fixes a bug in the spec, though this may not be the best way to deal with HRR.  See https://github.com/tlswg/ draft-ietf-tls-esni/issues/121 and https://github.com/tlswg/draft- ietf-tls-esni/pull/170 for more details.]] </ins> The client then creates a ClientESNIInner structure:

which do not depend on the (possibly updated) KeyShareClientHello, i.e,, ClientEncryptedSNI.suite, ClientEncryptedSNI.key_share, and ClientEncryptedSNI.record_digest, MUST NOT change across ClientHello <del> messages.  Moreover, ClientESNIInner.nonce and ClientESNIInner.realSNI MUST not change across ClientHello messages. Informally, the values of all unencrypted extension information, as well as the inner extension plaintext, must be consistent between the first and second ClientHello messages. </del> <ins> messages.  Moreover, ClientESNIInner MUST not change across ClientHello messages.  Informally, the values of all unencrypted extension information, as well as the inner extension plaintext, must be consistent between the first and second ClientHello messages. </ins> 5.1.3.

If the decrypted value's length is different from the advertised ESNIKeys.padded_length or the padding consists of any value other than 0, then the server MUST abort the connection with an <del> illegal_parameter alert.  Otherwise, the server uses the </del> <ins> "illegal_parameter" alert.  Otherwise, the server uses the </ins> PaddedServerNameList.sni value as if it were the "server_name" extension.  Any actual "server_name" extension is ignored, which also means the server MUST NOT send the "server_name" extension to the

case, it does not make any changes to the TLS messages, but just blindly forwards them. <ins> If the ClientHello is the result of a HelloRetryRequest, servers MUST abort the connection with an "illegal_parameter" alert if any of the ClientEncryptedSNI.suite, ClientEncryptedSNI.key_share, ClientEncryptedSNI.record_digest, or decrypted ClientESNIInner values from the second ClientHello do not match that of the first ClientHello. </ins> 5.3. A server operating in Shared Mode uses PaddedServerNameList.sni as if