Change:
This just expands the text and tries to be explicit about everything. The one change I've included here is a change from 32 to Nsk for the exported secret. I think that's in line with the HPKE intent. Constants make me nervous.
I'm going to merge, and then apply my suggestions and the nonce change in a followup PR.
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 .

Discussion:
responses and so might be ignorant of the use of oblivious HTTP to reach it. <ins> This draft includes pseudocode that uses the functions and conventions defined in HPKE. This draft uses the variable-length integer encoding from Section 16 of QUIC.  Encoding and decoding variable-length integers to a sequence of bytes are described using the functions "vencode()" and "vdecode()".  The function "len()" takes the length of a sequence of bytes. Formats are described using notation from Section 1.3 of QUIC. </ins> 3. A client learns the following:

encryption.  An encapsulated HTTP message includes the following values: <del> A binary-encoded HTTP message [CITEME]. </del> <ins> A binary-encoded HTTP message; see BINARY. </ins> Padding of arbitrary length which MUST contain all zeroes. The encoding of an HTTP message is as follows: <del> This structure is then encrypted under a key with a specific identity, forming an encapsulated HTTP message, with the following structure: </del> <ins> An Encapsulated Request is comprised of a length-prefixed key identifier and a HPKE-protected request message.  HPKE protection includes an encapsulated KEM shared secret (or "enc"), plus the AEAD- protected request message.  An Encapsulated Request is shown in fig- enc-request. request describes the process for constructing and processing an Encapsulated Request. </ins> <del> The encapsulated message Key ID, as well as the encryption mechanics, are different for requests and responses, as described below. </del> <ins> Responses are bound to responses and so consist only of AEAD- protected content.  response describes the process for constructing and processing an Encapsulated Response. </ins> 4.1. <del> Clients encapsulate a request Plaintext Message "msg" with an HPKE public key "pkR", whose Key Identifier is "keyID" as follows: </del> <ins> Clients encapsulate a request "request" with an HPKE public key "pkR", whose Key Identifier is "keyID" as follows: </ins> Compute an HPKE context using "pkR", yielding "context" and <del> encapsulation key "enc" </del> <ins> encapsulation key "enc". </ins> <del> Encrypt (seal) "msg" with "keyID" as associated data using "context", yielding ciphertext "ct" </del> <ins> 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. </ins> <del> Concatenate "enc" and "ct", yielding an Encrypted Message "encrypted_msg" </del> <ins> Encrypt (seal) "request" with "keyID" as associated data using "context", yielding ciphertext "ct". Concatenate the length of "keyID" as a variable-length integer, "keyID", "enc" and "ct", yielding an Encapsulated Request "enc_request". </ins> In pseudocode, this procedure is as follows: <del> Clients construct the Encapsulated Message "req" using "keyID" and "encrypted_msg". </del> <ins> Servers decrypt an Encapsulated Request by reversing this process. Given an Encapsulated Request "enc_request", a server: </ins> <del> Servers decrypt an Encapsulated Message by reversing this process. Given an Encapsulated Message "req" request with Key Identifier "keyID" corresponding to an existing HPKE private key "skR", servers decapsulate the Message as follows: </del> <ins> 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 "keyID". </ins> <del> Parse the "req" Encrypted Message as the concatenation of "enc" and "encrypted_message" </del> <ins> Compute an HPKE context using "skR" and the encapsulated key "enc", yielding "context". </ins> <del> Compute an HPKE context using "skR" and the encapsulated key "enc" from "req", yielding "context" </del> <ins> Construct additional associated data, "aad", by prepending a single byte with a value of 0x01 to the key identifier. </ins> <del> Decrypt "encrypted_message" with "keyID" as associated data, yielding "msg" or an error on failure </del> <ins> Decrypt "ct" using "aad" as associated data, yielding "request" or an error on failure. </ins> In pseudocode, this procedure is as follows: <del> Servers MUST verify that the Plaintext Message padding consists of all zeroes before processing the corresponding Message. </del> <ins> Servers MUST verify that the request padding consists of all zeroes before processing the corresponding Message. </ins> 4.2. <del> Given an HPKE context "context" and a response Plaintext Message "resp" sent in response to a Plaintext Message "req", servers encrypt the data as follows: </del> <ins> Given an HPKE context "context", a request message "request", and a response "response", servers generate an Encapsulated Response "enc_response" as follows: </ins> <del> Derive a symmetric key and nonce from "context" </del> <ins> Export a secret "secret" from "context", using the string "response" as a label.  The length of this secret is "Nsk" - the length of the secret assocated with "context". </ins> <del> Encrypt "resp" with empty Key Identifier "emptyKeyID" as associated data, yielding "encrypted_msg" </del> <ins> Extract a pseudorandom key "prk" using the "Extract" function provided by the KDF algorithm associated with "context".  The "ikm" input to this function is "secret"; the "salt" input is "request". </ins> <del> In pseudocode, this procedure is as follows: </del> <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 the AEAD associated with "context".  Generating "key" uses a label of "key". </ins> <del> Extract and Expand are functions corresponding to the HPKE context's KDF algorithm, and Seal, Nk, and Nn correspond to the HPKE context's AEAD algorithm. </del> <ins> Use the same "Expand" function to extract a nonce "nonce" of length "Nn" - the length of the nonce used by the AEAD. Generating "nonce" uses a label of "nonce". Construct additional associated data "aad", that consists of a single byte with a value of 0x02. Encrypt "response", passing the AEAD function Seal the values of "key", "nonce", "aad", and a "pt" input of "request", which yields "enc_response". In pseudocode, this procedure is as follows: </ins> <del> Clients decrypt an Encapsulated Message by reversing this process. Namely, they derive the necessary AEAD parameters from an existing HPKE context and then decrypt (open) the Encapsulated Message encrypted message. </del> <ins> Clients decrypt an Encapsulated Request by reversing this process. 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. </ins> 4.3.