Proposed PQ hotfix for ecrecover
Many EVM contracts deployed today use ecrecover to verify ECDSA signatures and are thus vulnerable to quantum attacks. Rather than completely disabling the ecrecover opcode, we can update ecrecover to read post-quantum signatures from EIP-8141 frame transactions. The 65-byte signature (v, r, s) can be used to encode lookup values. A sentinel value would trigger the new lookup path instead of the original ECDSA recovery. This would create a graceful migration path for immutable deployed contracts. This proposal does not aim to fix EOA accounts themselves.
Backwards compatibility constraints
-
The EVM defines
ecrecoveras apurefunction. Any lookup of public key, claimed account, signature, and verification function must not break deployed bytecode or the compiler. -
The EVM checks that
bytes32 r < n; any value encoded inrshould start with a0x00byte. -
OpenZeppelinâs
ECDSA.tryRecoverin version 4.7 or earlier also checks thatv â {27, 28}ands < n/2. -
EIP-7702 delegates can send transactions on behalf of an EOA, but they cannot sign gasless permits and authorizations such as EIP-3009. The update to
ecrecovershould not unexpectedly allow this.
Proposed solution
This post is a high-level proposal that leverages EIP-8141, with the understanding that EIP-8141 will undergo significant change. The final version of EIP-8141 will likely include a pure verification function with a canonical authenticator set.
Sentinel values. Propose v = 27, s = 0 as the sentinel value to trigger the new execution path. The combination v = 27, s = 0 respects early OpenZeppelin-based contracts that enforce v â {27, 28} while clearly identifying a new execution path, since a valid ECDSA signature will never have s = 0.
Lookup. The lookup information can be encoded as:
r = 0x00 || signatureIndex (11-byte) || verificationFunctionId (20-byte)
The leading 0x00 byte ensures r < n. The bytes11 signatureIndex allows for 2^88 signatures â more than can possibly be included in a frame transaction due to the gas ceiling. The bytes20 verificationFunctionId can be either a fixed enum used to call a pure signature verification function, or a lookup into a hash table inside a fixed key-registration precompile (which is treated as pure).
Claimed address. The claimed address must authorize the public key. A few options:
-
EIP-8164 (where account code becomes
0xef0101 || pubkey). -
EIP-7932 that defines valid EVM addresses as hash of public key.
-
EIP-8130, which creates a designated Keystore contract.
The challenge here is to preserve pure verification. The design would depend on the final form EIP-8141 takes and/or adoption of above EIPs.
Signature verification. The EIP-8141 tx.signature[] is an array, and each entry contains a scheme, signer, msg and signature (which may contain public key). The expected invariant is that each signature is valid on msg.
scheme = ARBITRARY
signer = (empty)
msg = h
signature = pk || signature
EIP-8141 expects that len(signer)=0 for ARBITRARY schemes. There is an expected invariant that signature is valid on msg, hence ecrecover(h,...) must check that msg==h and the that verifySignature(pk, msg)==true. Signature verification will also need to check that claimed authorizes pk (using a TBD mechanism above).
Output. The function ecrecover would return claimed on success or address(0) on failure.
Alternative approaches
-
Use a sentinel value such as
v = 29that is not a valid ECDSA value. This is cleaner thanv = 27,s = 0, allows data to be encoded inside bothr < nands < n/2, and creates a path for future upgrades with different sentinel values ofv. However, it breaks older OpenZeppelin-based smart contracts. -
Encode a
claimedaddress in(v, r, s)ortx.signatureand leverage ERC-1271 toSTATICCALLintoclaimed.isValidAuthorization(). This creates a way for an existing ECDSA address to authorize a public key. However, ERC-1271isValidAuthorization()is aviewfunction that may break the expected behavior ofecrecover. It would also allow an EIP-7702 delegate to sign EIP-3009transferWithAuthorizationrequests. Finally, ifisValidAuthorizationcallsAPPROVE, it can result in approving the entire frame transaction when the intent was to check a specific signature. -
Include the public key inside the frame-transaction signature and compute the
claimedaddress as a hash of the post-quantum public key. This would create apuremechanism forclaimedto authorize the public key and pass theecrecovercheck. However, any ETH or tokens accidentally sent to such an address may become unrecoverable sinceclaimedwould not be a valid ECDSA address.