EIP-1271 isValidSignature
isValidSignature(bytes32 hash, bytes signature) returns (bytes4 magicValue) is the single interface method defined by EIP-1271. A contract that implements it is declaring itself willing to be asked “is this signature valid on your behalf?” — and to answer in a way that is strict, side-effect-free, and distinguishable from an unimplemented method.
The magic value
The success return is the constant bytes4(0x1626ba7e), computed as:
bytes4(keccak256("isValidSignature(bytes32,bytes)"))
This is the standard Solidity function selector for isValidSignature(bytes32,bytes) — the same four bytes a caller would use in a low-level call to invoke the method. EIP-1271 reuses the selector as the success marker for the return value. The choice is ergonomic: any Solidity codebase can compute it trivially, and any other value — including bytes4(0), bytes4(0xffffffff), bytes4(keccak256("something else")), or arbitrary garbage from a misimplemented contract — fails the check.
Why a magic value and not a boolean
A bool return would seem simpler, but it leaks validity in two dangerous ways:
- Unimplemented methods: If the signing contract does not implement
isValidSignatureat all, a call may return empty bytes that decode asfalse— but it may also, depending on fallback handler behavior, return arbitrary data. A boolean check against truthy garbage could accidentally pass. - Buggy implementations: A contract that returns a hand-rolled “valid” value from the wrong path — for instance, returning
truefrom a default branch — could validate signatures it should have rejected.
A four-byte magic value cannot be produced accidentally. Only a contract whose author knew the standard and deliberately wrote return 0x1626ba7e; from a successful code path will pass the check. Every other outcome — silence, revert, zero bytes, wrong bytes — is unambiguously a rejection.
Verifiers must use strict equality against the magic value. “Did not revert” and “returned non-zero” are both wrong.
State purity
isValidSignature MUST NOT modify state. In modern Solidity this is enforced with the view modifier; older code used STATICCALL for the same guarantee. Three reasons matter:
- Off-chain queries: A client can call
isValidSignatureviaeth_callto probe whether a signature is acceptable without paying gas or broadcasting a transaction. Stateful validation would make this dishonest — the off-chain probe would see a different world than the on-chain call. - Gas-refund attacks: A stateful validator could be tricked into performing gas-refundable work (e.g.
GasToken-style minting) on every verification, turning signature checks into a gas oracle attack vector. - Implementation simplicity: The contract does not have to answer “what if validation succeeds but then the caller reverts?” or “what if two concurrent callers race on the same signature?”. Stateless validation is stateless.
Note that “state-dependent” is allowed — a view function can read any storage it wants, so an implementation can consult nonce mappings, role tables, or authorized-signer sets. It just cannot write.
Hash, not message
The first argument is bytes32 hash — a pre-computed digest — rather than a raw message plus a hashing scheme. The standard is deliberately agnostic about how the hash was produced. A contract could expect:
- An EIP-712 digest with a specific domain separator
- An Ethereum Signed Message hash (
"\x19Ethereum Signed Message:\n32" ‖ message) - A raw
keccak256of arbitrary bytes - A Safe-wrapped version of another EIP-712 digest
- Anything else the contract’s author decided on
The verifier and the signing contract must agree out of band on which hashing scheme applies. In practice almost everyone uses EIP-712, because it provides the domain separation, replay protection, and human-readable signing UX that raw hashes do not.
This design moves the hashing responsibility to the caller, which is the right place: the caller already has to hash the message to match whatever the signer signed, so it might as well pass the hash directly.
Signature as opaque bytes
The second argument is bytes signature — explicitly opaque. The contract interprets the bytes however it wants:
- ECDSA: 65 bytes of
(r, s, v), recovered viaecrecoverand compared against a stored owner. - Multisig: A concatenation of
Nowner signatures, each checked against the owner set with a threshold comparison. - BLS: An aggregated signature over a set of public keys.
- Empty bytes: A pre-approved-hash lookup — the contract checks its own storage for a flag saying “yes, this digest was already authorized on-chain”.
- Role tokens: A structured blob encoding “which role signed this” plus the recovered signer; validity depends on the role’s permissions at the current block.
- Time-locked: A signature that’s only valid inside a window encoded in the bytes themselves.
The isValidSignature interface does not care which of these the contract chose. It only cares that the contract returns the magic value when — and only when — the answer is yes.
Two modes of Safe verification
Safe (Gnosis) is the reference example of a contract that uses the signature argument in two different ways:
Non-empty signature: The bytes are parsed as a concatenation of owner signatures. Safe’s checkSignatures function verifies each one against the current owner set and confirms the threshold is reached. The Safe does not need any prior on-chain state — the signatures themselves carry the authorization.
Empty signature (0x): The bytes are empty. The Safe’s CompatibilityFallbackHandler wraps the supplied hash in a SafeMessage(bytes message) struct under a Safe-specific EIP-712 domain, computes the wrapped hash, and looks it up in the Safe’s signedMessages storage mapping. If it finds a non-zero entry, it returns the magic value. The mapping is populated by an earlier on-chain transaction — a delegatecall to SignMessageLib.signMessage(digest) — which writes signedMessages[wrappedHash] = 1 inside the Safe’s own storage context.
The empty-signature mode is the one used in Creating a Farcaster Account by Hand. It trades one on-chain transaction (the pre-approval) for “no runtime signature required at verification time”, which is necessary when the verifier is a contract the Safe cannot interact with live.
Connections
- ERC-1271: Standard Signature Validation Method for Contracts: the full standard
- Smart Account Signatures: how verifiers route between
ecrecoverandisValidSignature - Off-Chain Signatures: the broader pattern
- EIP-712: Typed structured data hashing and signing: the hashing standard almost always used to produce the
hashargument - Creating a Farcaster Account by Hand: applied example of the empty-signature Safe mode