Original text:
protected content.  response describes the process for constructing and processing an Encapsulated Response.  4.1. Clients encapsulate a request "request" with an HPKE public key Compute an HPKE context using "pkR", yielding "context" and encapsulation key "enc". <del> Construct additional associated data, "aad", by prepending a single byte with a value of 0x01 to the key identifier.  The key identifier length is not included in the AAD. </del> Encrypt (seal) "request" with "keyID" as associated data using "context", yielding ciphertext "ct". Concatenate the length of "keyID" as a variable-length integer, <del> "keyID", "enc" and "ct", yielding an Encapsulated Request "enc_request". </del>  In pseudocode, this procedure is as follows: Parses "enc_request" into "keyID", "enc", and "ct" (indicated using the function "parse()" in pseudocode).  The server is then able to find the HPKE private key, "skR", corresponding to <del> "keyID". </del>  Compute an HPKE context using "skR" and the encapsulated key "enc", yielding "context". <del> Construct additional associated data, "aad", by prepending a single byte with a value of 0x01 to the key identifier. </del>  Decrypt "ct" using "aad" as associated data, yielding "request" or <del> an error on failure. </del>  In pseudocode, this procedure is as follows: "enc_response" as follows: Export a secret "secret" from "context", using the string <del> "response" as a label.  The length of this secret is "Nsk" - the length of the secret assocated with "context". </del>  Extract a pseudorandom key "prk" using the "Extract" function provided by the KDF algorithm associated with "context".  The <del> "ikm" input to this function is "secret"; the "salt" input is "request". </del>  Use the "Expand" function provided by the same KDF to extract an AEAD key "key", of length "Nk" - the length of the keys used by length "Nn" - the length of the nonce used by the AEAD. Generating "nonce" uses a label of "nonce". <del> Construct additional associated data "aad", that consists of a single byte with a value of 0x02. </del> Encrypt "response", passing the AEAD function Seal the values of <del> "key", "nonce", "aad", and a "pt" input of "request", which yields "enc_response". </del>  In pseudocode, this procedure is as follows: Clients decrypt an Encapsulated Request by reversing this process. <del> That is, clients follow the same process to derive values for "key", "nonce", and "aad".  The client then decrypts the Encapsulated Response using the Open function provided by the AEAD.  Decrypting might produce an error, as shown. </del>  4.3.

Reviewer comments:
Makes some of my suggestions from , and addresses in the process. For context, I consider the variable to be the wire-encoded (length prefixed) thing, and I tried to clarify that here. I don't feel strongly about the way that's written, but I do think it's important that everything sent on the wire is authenticated, including the keyID length.
Should the key ID length be explicitly authenticated, e.g., by including it in the AAD, or should the AEAD implicitly authenticate things? Unclear!
Should we even bother with an AAD at all? The best reason is that the keyID determines which key the server uses, which is information that factors into decisions. (A related question might be why doesn't HPKE take variables as input so that things like key identifiers can be integrated into the key schedule.)
My take: yes. I need to think through the implications of no additional authentication at all, and in the mean time, I'd be more comfortable with simply authenticating everything possible. I think it was discussed, but it makes the interface a mess. And since applications can accomplish the same thing by shoving what they want authenticated into the AAD, it was voted down.
Yeah, you and I both know that putting stuff in the AAD isn't a real substitute for having it in the key schedule. I completely understand how this would mess with the purity of the design, but TLS managed by wrapping its labels better (which also addresses a known problem with HKDF not being injective).
Fixed by virtue of making the key ID fixed length in .
In our discussion of , NAME and I explored the ways in which nonce reuse might occur. The problem arises if the client reuses . This leads to nonce reuse on the request for sure. However, if the request is the same, the problem shifts to the response. Unless the response is also identical, we get nonce reuse on the response instead. We then discussed options for safeguarding against this. As the client is expected to include randomness in its derivation of its key, it seems like the failing is not something that adding extra steps at the client will address. So I don't think that adding anything to the request really helps. Separately, HPKE code is supposed to be stateful. Reuse of a context should result in the sequence number increasing, leading to a nonce that the server cannot reconstruct (unless it also counts and does trial decryption, but that's two bad actors). That should ensure that the client doesn't use the same nonce twice. You really have to have bad randomness at the client to get into a deliberate nonce reuse scenario if you trust that the HPKE implementation is properly stateful. However, a response that is generated in response to a replayed request is something I am concerned about. We know that HTTP clients routinely do, even if the request is marked POST. So some protection here is warranted. For this, I'm going to suggest that we change from salting based on the request to salting based on . Hashing the entire message is likely to be very expensive, both in terms of cycles, but also in terms of state. The public key should be enough to ensure the binding in the case where a client doesn't reuse the context, and the new randomness should help in case of reuse. I think that it's OK to use 128 bits of extra randomness here, but we don't have a handy constant to hang that on. Nn is 96, Nk could be 128 or 256 depending on the AEAD. What we really want is the block size, but that isn't a property that is exposed anywhere.
I think this is the right design on balance. If we were more conservative, we could include the entire client request (including ), but that doesn't really hedge against the misuse scenario. I'll propose text in .
Fixed in .

Revised text:
protected content.  response describes the process for constructing and processing an Encapsulated Response. <ins> The size of the Nonce field in an Encapsulated Response corresponds to the size of an AEAD key for the corresponding HPKE ciphersuite. </ins> 4.1. Clients encapsulate a request "request" with an HPKE public key Compute an HPKE context using "pkR", yielding "context" and encapsulation key "enc".  Encrypt (seal) "request" with "keyID" as associated data using "context", yielding ciphertext "ct". Concatenate the length of "keyID" as a variable-length integer,  <ins> "keyID", "enc", and "ct", yielding an Encapsulated Request "enc_request".  Note that "enc" is of fixed-length, so there is no ambiguity in parsing "enc" and "ct". </ins> In pseudocode, this procedure is as follows: Parses "enc_request" into "keyID", "enc", and "ct" (indicated using the function "parse()" in pseudocode).  The server is then able to find the HPKE private key, "skR", corresponding to  <ins> "keyID".  If no such key exists, the server returns an error. </ins> Compute an HPKE context using "skR" and the encapsulated key "enc", yielding "context".  <ins> Construct additional associated data, "aad", as the Key Identifier "keyID" from "enc_request". </ins> Decrypt "ct" using "aad" as associated data, yielding "request" or  <ins> an error on failure.  If decryption fails, the server returns an error. </ins> In pseudocode, this procedure is as follows: "enc_response" as follows: Export a secret "secret" from "context", using the string  <ins> "response" as a label.  The length of this secret is "Nk" - the length of an AEAD key assocated with "context". Generate a random value of length "max(Nn, Nk)" bytes, called "response_nonce". </ins> Extract a pseudorandom key "prk" using the "Extract" function provided by the KDF algorithm associated with "context".  The  <ins> "ikm" input to this function is "secret"; the "salt" input is the concatenation of "enc" (from "enc_request") and "response_nonce" </ins> Use the "Expand" function provided by the same KDF to extract an AEAD key "key", of length "Nk" - the length of the keys used by length "Nn" - the length of the nonce used by the AEAD. Generating "nonce" uses a label of "nonce".  Encrypt "response", passing the AEAD function Seal the values of  <ins> "key", "nonce", empty "aad", and a "pt" input of "request", which yields "ct". Concatenate "response_nonce" and "ct", yielding an Encapsulated Response "enc_response".  Note that "response_nonce" is of fixed- length, so there is no ambiguity in parsing either "response_nonce" or "ct". </ins> In pseudocode, this procedure is as follows: Clients decrypt an Encapsulated Request by reversing this process.  <ins> That is, they first parse "enc_response" into "response_nonce" and "ct".  They then follow the same process to derive values for "aead_key" and "aead_nonce".  Finally, the client decrypts "ct" using the Open function provided by the AEAD.  Decrypting might produce an error, as shown. </ins> 4.3.