Original text:
the client may use the "ech_outer_extensions" extension. OuterExtensions consists of one or more ExtensionType values, each of <del> which reference an extension in ClientHelloOuter. </del>  When sending ClientHello, the client first computes ClientHelloInner, including any PSK binders.  It then computes a new value, the the legacy_session_id field from ClientHelloOuter.  It then looks for an "ech_outer_extensions" extension.  If found, it replaces the extension with the corresponding sequence of extensions in the <del> ClientHelloOuter.  If any referenced extensions are missing or if "encrypted_client_hello" appears in the list, the server MUST abort the connection with an "illegal_parameter" alert. </del>  <del> The "ech_outer_extensions" extension is only used for compressing the ClientHelloInner.  It MUST NOT be sent in either ClientHelloOuter or ClientHelloInner. </del>  <del> Note that it is possible to implement decoding of the EncodedClientHelloInner in a way that creates a denial-of-service vulnerability.  Specifically, the server needs to check that each extension in the OuterExtensions list appears in the ClientHelloOuter.  The naive strategy would require O(N*M) time, where N is the number of extensions in the ClientHelloOuter and M is the number of extensions in the OuterExtensions list.  Malicious clients could exploit this behavior in order to cause excessive work for the server, possibly making it unavailable.  This problem can be mitigated by representing OuterExtensions in a way that allows it to be searched more quickly.  For example, the runtime can be improved to O(N*log(M)) by sorting the OuterExtensions and using binary search to access it. </del>  5.2. It MUST offer to negotiate TLS 1.3 or above. If it compressed any extensions in EncodedClientHelloInner, it <del> MUST copy the corresponding extensions from ClientHelloInner. </del>  It MUST copy the legacy_session_id field from ClientHelloInner. This allows the server to echo the correct session ID for TLS

Reviewer comments:
This is an alternative to , which constrains the client such this suggestion is feasible: URL NAME thoughts? This does constrain the client behavior, though our draft implementation would not be affected by it. (We run through all the extensions in our usual order and compute the inner value + whether to compress it. If we intend to compress it, we write it to a separate buffer instead of the pending ClientHelloInner. Then we drop in all the buffered extensions, so they're contiguous and, in the EncodedClientHelloInner, write echouter_extensions instead of the buffer. This process keeps the relative order of compressed extensions, so we follow this rule anyway.)
Same here (wrt liking:-). Cheers, S.
Moved to appendix.
NAME can you please rebase?
Rebased
A naive implementation of decoding the inner ClientHello can easily go quadratic when processing outerextensions. Thus far, nothing in TLS has required searching for O(N) extensions, only O(implemented extensions). It's likely a lot of TLS implementations handle the extensions list linearly most of the time. outerextensions is now the first time you have to search for extensions you may not have implemented. A naive implementation would then go quadratic, which is a server DoS risk. It doesn't need to go quadratic (for instance, you can sort ClientHelloOuter's extensions and then binary search per lookup), but it's probably worth at least calling this out, if not switching to a simpler decoding scheme. (For a simpler decoder, we already require CHInner group the compressed extensions consecutively. We could do the same with CHOuter, at which point we only need to reference one byte slice, be it by offset/length or some goofier scheme.)
Switching to a simpler scheme: yes please. Just dropping the compression idea entirely: even better:-) Given this optimisation is really mostly for PQ schemes, I'd argue it'd be better to just drop the current idea and have a more constrained one, e.g. using a bit mask as the value of outer_extensions, so implementation know exactly which extensions can ever be copied from the outer CH. And I'd further argue we can leave defining all of that until later too. The current scheme also imposes quite a testing burden. At least with OpenSSL most extensions have some side-effect and testing that all combinations of those are handled correctly for the inner CH isn't trivial. (That's partly down to old code being a bit awful but I guess won't be simple for any code base.) Cheers, S.
Not sure I follow the concern with side effects. Are you processing the extensions as part of decompression? In order to make the handshake work, you'd have to construct the ClientHello by bytes anyway. Thus it seems the natural implementation would be to resolve ECH early, before ClientHello processing, then compute the inner ClientHello as bytes, then run the handshake with the resulting ClientHello. This aligns nicely with the model in split mode, etc. The issue I have in mind is simply that the decoding process, a pure function from a pair of byte strings to a byte string, is easy to accidentally make inefficient.
It occurs to me this might cause some conflicts between wanting ECH-ful and ECH-less ClientHellos to look similar (don't stick out) and pressures on ClientHello extension order. The order should be irrelevant, but compat concerns have cropped up before. (If only we required extensions be sorted...) Another possibility for a simpler decode would be if outer_extensions listed indexes into the extension list, rather than extension IDs. Though we would need to answer silly questions like whether it's an index into ClientHelloOuter or ClientHelloOuterAAD.
Since this is more of an implementation detail, let's just add a warning to handle this responsibly (i.e. the sort and binary search).
Yet another way to address this is with a simple hash map, e.g.: URL My vote is to leave the encoding scheme as-is and simply leave a warning.
NAME NAME could one of you please propose text for that warning?
Another DoS risk to be careful of: implementations typically enforce size limits on TLS messages so they aren't forced to buffer 2^24 bytes. Whatever size limit is enforced on ClientHello needs to also be enforced on the inner ClientHello before allocating memory for it. In particular, if ClientHelloOuter has a single giant extension, and it is referenced multiple times in OuterExtensions, you can blow up the server's resource usage. (Though I can't think of any case without repeating an extension where the resulting size grows dramatically.)
Is there any valid reason why an extension would appear multiple times in a ClientHello? If not, maybe we should specify that the server must abort if OuterExtensions references an extension more than once.
Seems reasonable. I think the combination of "no duplicate extensions" and "no referencing the ECH extension" imply that sizeof(ClientHelloInner) < sizeof(ClientHelloOuter).
Keeping this opening to track NAME and NAME comments on CH sizes. NAME could you please send a PR if you have cycles?
Discussed in 110: Will propose text to address the second vector and then close this issue.
I'm not going to nitpick this, but I like it. We currently have the same triply-nested loop here: the outer loop iterates over extensions in CHInner for most extensions we just copy them into a new structure for echouter we have two more layers of loop the middle loop iterates over the items in echouter the inner loop iterates over CHOuter, looking for a match This wouldn't really reduce code complexity, but it would mean that we wouldn't need to start over every time for that innermost loop. So we would be able to run in linear time as this says. Like you, as we iterate over extensions in the same order when constructing messages, this wouldn't have any effect on generating a ClientHello. My only concern with this is that it is somewhat more prescriptive than I would generally like for a specification. Moving this algorithmic stuff to an appendix might be a good balance. Either way, if you are adding this much text, it needs a section heading.LGTM pending suggestions!Works for me!

Revised text:
the client may use the "ech_outer_extensions" extension. OuterExtensions consists of one or more ExtensionType values, each of  <ins> which reference an extension in ClientHelloOuter.  The extensions in OuterExtensions MUST appear in ClientHelloOuter in the same relative order, however, there is no requirement that they be continguous. For example, OuterExtensions may contain extensions A, B, C, while ClientHelloOuter contains extensions A, D, B, C, E, F. The "ech_outer_extensions" extension is only used for compressing the ClientHelloInner.  It can only be included in EncodedClientHelloInner, and MUST NOT be sent in either ClientHelloOuter or ClientHelloInner. </ins> When sending ClientHello, the client first computes ClientHelloInner, including any PSK binders.  It then computes a new value, the the legacy_session_id field from ClientHelloOuter.  It then looks for an "ech_outer_extensions" extension.  If found, it replaces the extension with the corresponding sequence of extensions in the  <ins> ClientHelloOuter.  The server MUST abort the connection with an "illegal_parameter" alert if any of the following are true: </ins>  <ins> Any referenced extension is missing in ClientHelloOuter. "encrypted_client_hello" appears in OuterExtensions. OuterExtensions contains duplicate values. The extensions in ClientHelloOuter corresponding to those in OuterExtensions do not occur in the same order. </ins>  <ins> Implementations SHOULD bound the time to compute a ClientHelloInner proportionally to the ClientHelloOuter size.  If the cost are disproportionately large, a malicious client could exploit this in a denial of service attack.  linear-outer-extensions describes a linear-time procedure that may be used for this purpose. </ins> 5.2. It MUST offer to negotiate TLS 1.3 or above. If it compressed any extensions in EncodedClientHelloInner, it  <ins> MUST copy the corresponding extensions from ClientHelloInner.  The copied extensions additionally MUST be in the same relative order as in ClientHelloInner. </ins> It MUST copy the legacy_session_id field from ClientHelloInner. This allows the server to echo the correct session ID for TLS