Change:
As discussed (too ambiguously) at IETF 96, this refactor uses two nested JWS objects instead of one parallel JWS object. This preserves the pattern that every ACME message sent by the client is signed by exactly one account key, and saves clients from having to search through multiple signatures in the verification process.
Looking at the current mechanism in a little more detail, I have a few concerns with the parallel-signature approach that's there now: It's not clear which signature is by the old key and which is by the new key, and the new key doesn't sign over the old key. Both signatures have to include the parameter, so this request burns two nonces unnecessarily The server's JWS checking code now has to switch between validating one signature and two Lots of JWS implementations don't support multiple signatures If not for this, we could require the flattened JSON serialization You need a lot of duplication to deal with the copy/paste attacks (cf. ). (On the other hand, I like that both signatures cover the parameter; reduces replayability.) I would propose that we change the signatures from parallel back to serial, as illustrated below. It would cost a little bit of payload bloat (due to another layer of base64url encoding), but I think the benefits in terms of conceptual simplicity would be worth it.
Just noticed that if we keep to one signature per object, you can do nice stuff like this on the server:

Discussion:
5.3. <ins> At several points in the protocol, it is necessary for the server to determine whether two JWK objects represent the same key.  In performing these checks, the server MUST consider two JWKs to match if and only if they have the identical values in all fields included in the computation of a JWK thumbprint for that key.  That is, the keys must have the same "kty" value and contain identical values in the fields used in the computation of a JWK thumbprint for that key type: "RSA": "n", "e" "EC": "crv", "x", "y" Note that this comparison is equivalent to computing the JWK thumbprints of the two keys and comparing thumbprints.  The only difference is that there is no requirement for a hash computation (and thus it is independent of the choice of hash function) and no risk of hash collision. 5.4. </ins> It is common in deployment for the entity terminating TLS for HTTPS to be different from the entity operating the logical HTTPS server, with a "request routing" layer in the middle.  For example, an ACME

having the resource check that requests have the same string in their "url" fields. <del> 5.3.1. </del> <ins> 5.4.1. </ins> The "url" header parameter specifies the URL to which this JWS object is directed RFC3986.  The "url" parameter MUST be carried in the protected header of the JWS.  The value of the "url" header MUST be a JSON string representing the URL. <del> 5.4. </del> <ins> 5.5. </ins> In order to protect ACME resources from any possible replay attacks, ACME requests have a mandatory anti-replay mechanism.  This mechanism

value for each response, keep a list of issued nonces, and strike nonces from this list as they are used. <del> 5.4.1. </del> <ins> 5.5.1. </ins> The "Replay-Nonce" header field includes a server-generated value that the server can use to detect unauthorized replay in future

The Replay-Nonce header field SHOULD NOT be included in HTTP request messages. <del> 5.4.2. </del> <ins> 5.5.2. </ins> The "nonce" header parameter provides a unique value that enables the verifier of a JWS to recognize when replay has occurred.  The "nonce"

according to this encoding, then the verifier MUST reject the JWS as malformed. <del> 5.5. </del> <ins> 5.6. </ins> Creation of resources can be rate limited to ensure fair usage and prevent abuse.  Once the rate limit is exceeded, the server MUST

header pointing to documentation about the specific hit rate limits using the "rate-limit" relation. <del> 5.6. </del> <ins> 5.7. </ins> Errors can be reported in ACME both at the HTTP layer and within ACME payloads.  ACME servers can return responses with an HTTP error

registration in order to recover from a key compromise or proactively mitigate the impact of an unnoticed key compromise. <del> To change the key associated with an account, the client sends a POST request containing a key-change object with the following fields: </del> <ins> To change the key associated with an account, the client first constructs a key-change object describing the change that it would like the server to make: The URL for account being modified.  The content of this field MUST be the exact string provided in the Location header field in response to the new-registration request that created the account. </ins> The JWK representation of the original key (i.e., the client's current account key) The JWK representation of the new key <del> The JWS of this POST must have two signatures: one signature from the existing key on the account, and one signature from the new key that the client proposes to use.  This demonstrates that the client actually has control of the private key corresponding to the new public key.  The protected header must contain a JWK field containing the current account key. </del> <ins> Both of these thumbprints MUST be computed as specified in RFC7638, using the SHA-256 digest.  The values in the "oldKey" and "newKey" fields MUST be the base64url encodings of the thumbprints. The client then encapsulates the key-change object in a JWS, signed with the client's current account key (i.e., the key matching the "oldKey" value). This inner JWS then become the payload of the JWS that the client sends to the server.  The outer JWS is signed with the key pair that the the client wishes to have the server use as its account key (i.e., the key pair matching the "newKey" value).  The client sends this outer JWS in a POST request to the server's "key-change" resource. The outer JWS MUST meet the normal requirements for an ACME JWS (see request-authentication).  The inner JWS MUST meet the normal requirements, with the following exceptions: The inner JWS MUST have the same "url" parameter as the outer JWS. The inner JWS is NOT REQUIRED to have a "nonce" parameter.  The server MUST ignore any value provided for the "nonce" header parameter. This transaction has signatures from both the old and new keys so that the server can verify that the holders of the two keys both agree to the change.  The signatures are nested to preserve the property that all signatures on POST messages are signed by exactly one key. </ins> On receiving key-change request, the server MUST perform the following steps in addition to the typical JWS validation: <del> Check that the JWS protected header contains a "jwk" field containing a key that matches a currently active account. </del> <ins> Check that the payload of the JWS is a well-formed JWS object (the "inner JWS") Check that the JWS protected header of the inner JWS has a "jwk" field containing a key that matches a currently active account Check that the inner JWS verifies using the key in its "jwk" field </ins> <del> Check that there are exactly two signatures on the JWS. </del> <ins> Check that the payload of the inner JWS is a well-formed key- change object (as described above) </ins> <del> Check that one of the signatures validates using the account key from (1). </del> <ins> Check that the "url" parameters of the inner and outer JWSs are the same </ins> <del> Check that the "key" field contains a well-formed JWK that meets key strength requirements. </del> <ins> Check that the "account" field of the key-change object contains the URL for the registration matching the old key </ins> <del> Check that the "key" field is not equivalent to the current account key or any other currently active account key. </del> <ins> Check that the "oldKey" field of the key-change object contains the thumbprint of the key used to sign the inner JWS </ins> <del> Check that one of the two signatures on the JWS validates using the JWK from the "key" field. </del> <ins> Check that the "newKey" field of the key-change object contains the thumbprint of the key used to sign the outer JWS </ins> If all of these checks pass, then the server updates the corresponding registration by replacing the old account key with the