ERC-1271: Standard Signature Validation Method for Contracts
ERC-1271 is the Ethereum standard for letting smart contracts verify signatures on their own behalf. It defines a single interface method, isValidSignature(bytes32 hash, bytes signature) returns (bytes4 magicValue), that any contract can implement. Authored by Francisco Giordano, Matt Condon, Philippe Castonguay, Amir Bandeali, Jorge Izquierdo, and Bertrand Masius; created 2018-07-25. Standards Track, ERC (application-level) category.
Motivation
Externally Owned Accounts (EOAs) sign messages with their private keys and verifiers call ecrecover to confirm the signer. Smart contracts have no private key, so ecrecover is not an option for them. Without a standard, any dapp that accepted off-chain signatures implicitly required an EOA signer, and every contract-owned identity — smart wallets, DAOs, multisigs, escrows — was locked out of the signature layer.
The canonical motivating example is a decentralized exchange with an off-chain orderbook. Buyers and sellers sign orders off-chain; the exchange contract verifies the signatures when settling. An EOA signs with its private key; a Safe multisig owned by five people cannot. EIP-1271 gives the Safe a way to answer “yes, that order is valid on my behalf” without ever possessing a private key.
Specification
pragma solidity ^0.5.0;
contract ERC1271 {
// bytes4(keccak256("isValidSignature(bytes32,bytes)")
bytes4 constant internal MAGICVALUE = 0x1626ba7e;
/**
* @dev Should return whether the signature provided is valid for the provided hash
* @param _hash Hash of the data to be signed
* @param _signature Signature byte array associated with _hash
*
* MUST return the bytes4 magic value 0x1626ba7e when function passes.
* MUST NOT modify state (using STATICCALL for solc < 0.5, view modifier for solc > 0.5)
* MUST allow external calls
*/
function isValidSignature(
bytes32 _hash,
bytes memory _signature)
public
view
returns (bytes4 magicValue);
}
Three requirements:
- Returns the magic value on success:
bytes4(0x1626ba7e) = bytes4(keccak256("isValidSignature(bytes32,bytes)")). Anything else — different bytes, a revert, no implementation at all — is a rejection. - Must not modify state: Enforced via
viewin Solidity 0.5+,STATICCALLin older versions. See isValidSignature for why this matters. - Must allow external calls: The method is
public/externalbecause verifiers are always external to the signing contract.
The isValidSignature method can call arbitrary internal logic to determine validity — ECDSA recovery, multisig threshold checking, BLS aggregation, pre-approved hash lookup, role-based permissions, time windows, state-based rules. The standard is agnostic about what “valid” means; it only standardizes the interface.
Who implements it
Any contract that wants to be treated as a signer:
- Smart contract wallets — Safe (Gnosis), Argent, Ambire, Sequence, Biconomy
- DAOs — governance contracts that authorize actions via proposal passage
- Multisignature wallets — owner set plus threshold
- ERC-4337 smart accounts — account abstraction implementations
- Escrows and time-locks — contracts that hold assets on behalf of users
- DEX aggregators — protocols that sign orders on behalf of pooled users
Who calls it
Any verifier that needs to check whether a signature is valid for a contract-owned address. The typical pattern is:
function verify(address signer, bytes32 hash, bytes calldata signature) internal view {
if (signer.code.length > 0) {
// Contract signer: use EIP-1271
bytes4 result = IERC1271(signer).isValidSignature(hash, signature);
require(result == 0x1626ba7e, "INVALID_SIGNATURE");
} else {
// EOA signer: use ecrecover
require(ECDSA.recover(hash, signature) == signer, "INVALID_SIGNATURE");
}
}
OpenZeppelin’s SignatureChecker.isValidSignatureNow(signer, hash, signature) implements exactly this fallback: probe ecrecover first, fall back to isValidSignature if the signer has code. See Smart Account Signatures for the full verification pattern and the branching conditions verifiers need.
Rationale
Why bytes4 instead of bool: A contract that does not implement isValidSignature at all — or that implements it incorrectly and returns arbitrary data — must not accidentally satisfy a validity check. A boolean return type would let truthy garbage pass. A specific four-byte magic value cannot be accidentally produced; only an implementation that knew the standard and intended to return success can do so.
Why two arguments: A single “raw message” argument would force every contract to agree on a hashing scheme. EIP-1271 instead takes a pre-hashed bytes32, leaving the hashing scheme out of scope. Callers and contracts must agree on the scheme — typically EIP-712 with a domain separator — but the standard does not mandate it.
Why state cannot be modified: Two reasons. First, it simplifies the implementation surface — there is no “what if the validation has side effects” question. Second, it rules out GasToken-style attacks where an attacker forces a contract to perform gas-refundable work by calling a signature check. Third, it makes off-chain queries cheap: a client can call isValidSignature via eth_call to probe whether a signature is acceptable without paying gas or broadcasting a transaction.
Reference implementation
A minimal example from the EIP — a contract that validates signatures by recovering the signer and checking it matches an owner:
function isValidSignature(
bytes32 _hash,
bytes calldata _signature
) external override view returns (bytes4) {
if (recoverSigner(_hash, _signature) == owner) {
return 0x1626ba7e;
} else {
return 0xffffffff;
}
}
And a verifier calling it:
function callERC1271isValidSignature(
address _addr,
bytes32 _hash,
bytes calldata _signature
) external view {
bytes4 result = IERC1271Wallet(_addr).isValidSignature(_hash, _signature);
require(result == 0x1626ba7e, "INVALID_SIGNATURE");
}
The reference implementation also includes recoverSigner, an ECDSA recovery helper with explicit signature-malleability mitigations (rejecting high-s values per EIP-2, rejecting v values outside {27, 28}, rejecting the zero address recovery). These mitigations are not part of EIP-1271 itself — they are general ECDSA best practice that any isValidSignature implementation should inherit if it uses ecrecover internally.
Security considerations
- No gas limit: The signing contract may legitimately consume a large amount of gas — multisig checks across 20 owners, BLS aggregation, storage reads, nested calls. Callers MUST NOT hardcode a gas cap when invoking
isValidSignatureexternally, or they will reject valid signatures whose verification happens to be expensive. - Contract responsibility: Each signing contract is fully responsible for the correctness of its own validation logic. A buggy implementation that returns the magic value for signatures it should reject is catastrophic — attackers gain arbitrary write access via forged signatures.
- Verifier responsibility: Verifiers must refuse to accept anything other than the exact magic value. “Any non-zero return” is not a valid check. Neither is “did not revert”.
Applied example: Safe + Farcaster
The Creating a Farcaster Account by Hand walkthrough is a concrete EIP-1271 flow end to end:
- A Safe multisig is the intended Farcaster account custody. Farcaster’s
SignedKeyRequestValidatorexpects an EIP-712 signature on theSignedKeyRequeststruct from the custody address. - The Safe cannot produce an ECDSA signature. Instead, the Safe pre-approves the Farcaster EIP-712 digest on-chain via a
SignMessageLib.signMessagedelegatecall, which writes into the Safe’ssignedMessagesstorage mapping. - When Farcaster later calls
Safe.isValidSignature(digest, "")(empty signature), the Safe’sCompatibilityFallbackHandlerwraps the digest in aSafeMessage(bytes)struct under a Safe-specific EIP-712 domain, looks up the wrapped hash insignedMessages, finds it, and returns0x1626ba7e. - Farcaster’s validator sees the magic value and accepts. The ed25519 signer goes live.
This is EIP-1271 in its pre-approved-hash form — a pure on-chain attestation standing in for a cryptographic signature. The same contract also supports the runtime-verified form where signature is non-empty and the Safe checks it against its current owner set and threshold.
Connections
- EIP-1271 isValidSignature: the method signature, magic value, and state-purity requirement in detail
- Smart Account Signatures: how EIP-1271 closes the gap between EOA and contract signers
- Off-Chain Signatures: the broader pattern that EIP-1271 extends to smart accounts
- EIP-712: Typed structured data hashing and signing: the hashing standard most commonly used with EIP-1271
- Ethereum Signed Message Encoding: the leading-byte disambiguation that distinguishes the signed digest from transactions and raw messages
- Creating a Farcaster Account by Hand: applied example routing an EIP-712 digest through a Safe via EIP-1271