ERC-7913 Signature verifier for addressless keys (Signature Verifiers)

Final · Signatures & auth · created 2025-03-21

A standard that allows signatures made by keys without an Ethereum address (such as a mobile device's P256 key or an RSA key) to be verified through a shared verifier contract. There is no need to deploy a contract for each key.

Why it is needed

Until now, on chain signature verification has only handled cases where the signer has an Ethereum address. A regular account (EOA) signs with a private key, and ERC-1271 defines how smart accounts, such as multisigs, verify signatures, but in both cases the signer's identity is an Ethereum address. However, as account abstraction has advanced, keys other than the secp256k1 that Ethereum natively supports have become widely used: the secp256r1 curve supported by many mobile devices, RSA keys issued by traditional institutions, and zero knowledge (ZK) schemes that sign with JWTs from email or large web services. These keys have no Ethereum address to represent them. One could deploy an ERC-1271 compatible contract for each key, but that is cumbersome and expensive. Giving a key a fixed on chain address also risks assets being mistakenly sent to that address, which conflicts with account abstraction's direction of separating the account address that holds assets from the key that controls the account. This standard solves the problem by having a small number of verifier contracts per key type, and having accounts identify signers as (verifier, key) pairs.

What to implement

In depth

Definitions of key and verifier

A key is represented as a bytes object with no length restriction, for example a P256 or RSA public key. A key MUST NOT be empty. A verifier is a smart contract responsible for verifying signatures for a specific key type, and it has an Ethereum address. In other words, it is the key that lacks an address, and the verification responsibility falls on the verifier contract, which has one.

Return rules for the verify function

A verifier MUST implement the IERC7913SignatureVerifier interface. verify(bytes key, bytes32 hash, bytes signature) MUST return the bytes4 magic value 0x024ad318 (IERC7913SignatureVerifier.verify.selector) if the signature is valid, and SHOULD return 0xffffffff or revert if the signature is invalid or the key is empty. The verifier SHOULD also be stateless.

Verification procedure backward compatible with EOA and ERC-1271

The signer is a bytes object formed by concatenating a verifier address with an optional key (verifier || key), at least 20 bytes long. The verification procedure is as follows: if signer.length < 20, it fails. The first 20 bytes are split out as the verifier, and the rest as the key. If the key is empty, the verifier itself is treated as the signer, and verification uses ERC-1271's isValidSignature if that address has code, or ecrecover otherwise. If the key is not empty, verify is called, and the result is treated as success only if the return value is the magic value 0x024ad318. This way, existing EOAs and ERC-1271 contracts are accommodated as is, in the form of 'verifier address plus empty key.'

Why empty keys are disallowed

Deploying an ERC-1271 'identity contract' for every key is expensive, but in this model, once a verifier exists, new keys can be used at no deployment cost. Cases that need no key are already handled by ERC-1271, so to avoid ambiguity, a verifier must not support an empty key. If the signer is 20 bytes (an empty key), it must be handled via ERC-1271.

Why the message is a bytes32 hash

For consistency with the existing schemes ecrecover and ERC-1271, the message must be a bytes32 hash. This hash is usually produced following ERC-191 or ERC-712. A cryptographic scheme that uses a different hashing method SHOULD treat this hash as a single message and rehash it according to that standard if needed.

Reference implementation

The reference implementation is a SignatureCheckerExtended library that extends OpenZeppelin's SignatureChecker library. isValidSignatureNow(bytes signer, bytes32 hash, bytes signature) returns false if the signer length is under 20, processes it through the existing SignatureChecker.isValidSignatureNow if it is exactly 20, and if it exceeds 20, treats the first 20 bytes as the verifier address and calls verify via try/catch, then checks whether the return value matches verify.selector. If the call reverts, it returns false.

Things to watch out for

Original ERC · ← ERCs