ERC-1271 and ERC-7913 Signature Verification — OpenZeppelin, Solady, Coinbase Smart Wallet, and Solarity Solidity Library
- Scope and Versions
- 1. Standards Referenced in This Article
- ERC-191 — Signed Data Standard
- EIP-712 — Typed Structured Data Hashing and Signing
- ERC-1271 — Standard Signature Validation Method for Contracts
- ERC-2098 — Compact Signature Representation
- ERC-6492 — Signature Validation for Predeploy Contracts
- ERC-7739 — Readable Typed Signatures for Smart Accounts
- ERC-7913 — Signature Verifiers
- 2. OpenZeppelin — SignatureChecker + Signers + Verifiers
- 3. Solady — SignatureCheckerLib + ERC1271
- 4. Solarity Solidity-lib — Custom Cryptography Primitives
- 5. Case Study: Coinbase Smart Wallet ERC-1271 (v1.1.0)
- 6. Feature Comparison
- 7. Architecture Comparison
- 8. Gas Considerations
- 9. Security Considerations
- 10. When to Use Each
- 11. Conclusion
- References
ERC-1271 defines a standard for smart contract signature validation, enabling contract wallets to verify signatures the same way externally owned accounts (EOAs) do with ecrecover. ERC-7913 extends this model to keys that do not have an Ethereum address, such as P256, RSA, or WebAuthn keys. This article compares how three Solidity libraries and one production wallet implement and support these standards.
This article was produced with the assistance of Cursor and custom skills.
[TOC]
Scope and Versions
| Library | Version | Commit |
|---|---|---|
| OpenZeppelin Contracts | v5.5.0 | fcbae539 |
| Solady | v0.1.26 | acd959aa |
| Coinbase Smart Wallet | v1.1.0 | — |
| Solarity solidity-lib | v3.3.2 | 783a334b |
1. Standards Referenced in This Article
This section provides a summary of every ERC and EIP standard discussed throughout the article.
ERC-191 — Signed Data Standard
| Status | Final |
| Created | 2016-01-20 |
| Authors | Martin Holst Swende, Nick Johnson |
ERC-191 defines a general format for signed data in Ethereum: 0x19 <version byte> <version-specific data> <data to sign>. The leading 0x19 byte ensures signed data can never be confused with a valid RLP-encoded Ethereum transaction. Three version bytes are registered:
0x00— Data with an intended validator address (prevents cross-contract replay).0x01— EIP-712 structured data.0x45—personal_signmessages (the\x19Ethereum Signed Message:\nprefix).
ERC-191 is the foundational layer for all Ethereum message signing. Both OpenZeppelin’s MessageHashUtils and Solady’s SignatureCheckerLib.toEthSignedMessageHash implement the 0x45 variant.
EIP-712 — Typed Structured Data Hashing and Signing
| Status | Final |
| Created | 2017-09-12 |
| Authors | Remco Bloemen, Leonid Logvinov, Jacob Evans |
EIP-712 defines a standard for hashing and signing typed structured data, as opposed to raw bytestrings. It introduces the concept of a domain separator (chain ID, contract address, name, version, salt) and type hashing (hashStruct) so that signatures are bound to a specific application and chain. The resulting hash is formed as:
keccak256("\x19\x01" || domainSeparator || hashStruct(message))
EIP-712 is a prerequisite for ERC-7739 and is used internally by both OpenZeppelin’s EIP712 and Solady’s EIP712 base contracts.
ERC-1271 — Standard Signature Validation Method for Contracts
| Status | Final |
| Created | 2018-07-25 |
| Authors | Francisco Giordano, Matt Condon, Philippe Castonguay, Amir Bandeali, Jorge Izquierdo, Bertrand Masius |
ERC-1271 defines a standard way for smart contracts to validate signatures on their behalf. It introduces a single function:
function isValidSignature(bytes32 hash, bytes calldata signature) external view returns (bytes4 magicValue);
A contract MUST return 0x1626ba7e (the function selector) when the signature is valid. The standard enables protocols to accept signatures from both EOAs (via ecrecover) and smart contract wallets (via isValidSignature) through a unified verification path.
Implementation is context-dependent: contracts can use any signature scheme (ECDSA, multisig, BLS, etc.) and any validation logic (time-based, state-based, role-based). The function MUST NOT modify state (enforced by view/STATICCALL).
ERC-2098 — Compact Signature Representation
| Status | Final |
| Created | 2019-03-14 |
| Authors | Richard Moore, Nick Johnson |
ERC-2098 defines a 64-byte compact encoding for secp256k1 signatures by exploiting two properties: (1) yParity is always 0 or 1, and (2) the top bit of s is always 0 (due to canonical signature enforcement from EIP-2). The compact format stores yParity in the top bit of s:
Standard: [32-byte r][32-byte s][1-byte v] = 65 bytes
Compact: [32-byte r][1-bit yParity + 255-bit s] = 64 bytes
This saves 1 byte per signature on-chain and 32 bytes when word-aligned. On L2s where calldata cost dominates, the savings are significant. Solady’s SignatureCheckerLib natively auto-detects both 64-byte and 65-byte signatures. OpenZeppelin also supports EIP-2098 via its ECDSA library.
ERC-6492 — Signature Validation for Predeploy Contracts
| Status | Final |
| Created | 2023-02-10 |
| Authors | Ivo Georgiev, Agustin Aguilar |
| Requires | ERC-1271 |
ERC-6492 extends ERC-1271 to allow signature verification for smart accounts that have not yet been deployed (counterfactual contracts). This is critical for account abstraction, where wallet deployment is often deferred until the first transaction.
A counterfactual signature is wrapped with deployment data and a magic suffix 0x6492...6492 (32 bytes). The verifier detects this suffix, deploys the contract via the embedded factory calldata, then performs standard ERC-1271 verification. The magic bytes end with 0x92, which is not a valid v value for ecrecover, preventing collision with ECDSA signatures.
Solady implements ERC-6492 in SignatureCheckerLib.isValidERC6492SignatureNow. OpenZeppelin does not currently provide an ERC-6492 implementation.
ERC-7739 — Readable Typed Signatures for Smart Accounts
| Status | Draft |
| Created | 2024-05-28 |
| Authors | vectorized, Sihoon Lee, Francisco Giordano, Hadrien Croubois, Ernesto Garcia, et al. |
| Requires | ERC-191, EIP-712, ERC-1271, ERC-5267 |
ERC-7739 solves a signature replay vulnerability: when multiple smart accounts are owned by a single EOA, a signature valid for one account can be replayed against the others if the hash does not include the account address. Many popular applications (e.g. Permit2) produce hashes without the verifying contract’s address.
ERC-7739 defines a defensive rehashing scheme using nested EIP-712 typed data. The smart account wraps the original hash inside a TypedDataSign structure that includes the account’s own EIP-712 domain, binding the signature to a specific account and chain while preserving readability in wallet UIs.
The standard also defines a PersonalSign workflow for personal_sign messages and a detection mechanism: calling isValidSignature(0x7739...7739, "") returns 0x77390001 if the account supports ERC-7739.
Solady’s ERC1271 mixin implements ERC-7739 natively. OpenZeppelin provides draft-ERC7739Utils.
ERC-7913 — Signature Verifiers
| Status | Final |
| Created | 2025-03-21 |
| Authors | Hadrien Croubois, Ernesto Garcia, Francisco Giordano, Aryeh Greenberg |
ERC-7913 extends the concept of signer identity beyond Ethereum addresses to keys that have no on-chain address (P256, RSA, WebAuthn, ZK-based signers, etc.). It introduces a verifier interface:
interface IERC7913SignatureVerifier {
function verify(bytes calldata key, bytes32 hash, bytes calldata signature)
external view returns (bytes4);
}
A signer is represented as a bytes value: verifier || key. The first 20 bytes identify a deployed verifier contract; the remaining bytes contain the public key material. This avoids deploying individual ERC-1271 contracts for every key, as a single stateless verifier can handle all keys of a given type.
The standard is backward-compatible:
signer.length < 20: verification fails.signer.length == 20: falls back to ERC-1271/ECDSA (the signer is an address).signer.length > 20: callsIERC7913SignatureVerifier(verifier).verify(key, hash, signature).
OpenZeppelin provides the only full implementation among the three libraries compared here, including verifiers for P256, RSA, and WebAuthn, as well as single-signer and multi-signer (threshold, weighted) abstractions.
2. OpenZeppelin — SignatureChecker + Signers + Verifiers
OpenZeppelin provides the most comprehensive ERC-7913 implementation across three layers.
2.1 SignatureChecker Library (ERC-1271, ERC-7913)
The SignatureChecker library unifies ECDSA, ERC-1271, and ERC-7913 verification into a single entry point:
library SignatureChecker {
// ECDSA + ERC-1271 (address-based signer)
function isValidSignatureNow(address signer, bytes32 hash, bytes memory signature)
internal view returns (bool);
// ERC-7913 (bytes-based signer: verifier || key)
function isValidSignatureNow(bytes memory signer, bytes32 hash, bytes memory signature)
internal view returns (bool);
// Batch ERC-7913 verification with duplicate detection
function areValidSignaturesNow(bytes32 hash, bytes[] memory signers, bytes[] memory signatures)
internal view returns (bool);
}
The bytes-based overload implements the ERC-7913 routing logic:
function isValidSignatureNow(bytes memory signer, bytes32 hash, bytes memory signature)
internal view returns (bool)
{
if (signer.length < 20) {
return false;
} else if (signer.length == 20) {
// Fallback to address-based: ECDSA or ERC-1271
return isValidSignatureNow(address(bytes20(signer)), hash, signature);
} else {
// ERC-7913: call verifier contract
(bool success, bytes memory result) = address(bytes20(signer)).staticcall(
abi.encodeCall(IERC7913SignatureVerifier.verify, (signer.slice(20), hash, signature))
);
return (success && result.length >= 32 &&
abi.decode(result, (bytes32)) == bytes32(IERC7913SignatureVerifier.verify.selector));
}
}
The areValidSignaturesNow function verifies multiple signers with an optimized duplicate detection: if signers are sorted by keccak256(signer), the uniqueness check runs in O(n); otherwise it falls back to O(n^2).
2.2 AbstractSigner and Signer Implementations
OpenZeppelin defines an abstract base for signature validation:
abstract contract AbstractSigner {
function _rawSignatureValidation(bytes32 hash, bytes calldata signature)
internal view virtual returns (bool);
}
Concrete implementations include:
| Contract | Key type | Description |
|---|---|---|
SignerECDSA |
secp256k1 | Standard EOA signer; stores an address |
SignerERC7913 |
Any (via verifier) | Stores bytes signer (verifier+key) |
SignerP256 |
secp256r1 | P256 key verification |
SignerRSA |
RSA | RSA-PKCS#1 verification |
SignerWebAuthn |
WebAuthn | Passkey verification |
SignerEIP7702 |
Delegated EOA | EIP-7702 delegation |
MultiSignerERC7913 |
Multi-sig (threshold) | M-of-N ERC-7913 signers |
MultiSignerERC7913Weighted |
Weighted multi-sig | Weighted threshold voting |
2.3 MultiSignerERC7913 — Threshold Multi-Signature
MultiSignerERC7913 manages a set of authorized ERC-7913 signers with a configurable threshold:
abstract contract MultiSignerERC7913 is AbstractSigner {
EnumerableSet.BytesSet private _signers;
uint64 private _threshold;
function _rawSignatureValidation(bytes32 hash, bytes calldata signature)
internal view virtual override returns (bool)
{
(bytes[] memory signers, bytes[] memory signatures) = abi.decode(signature, (bytes[], bytes[]));
return _validateThreshold(signers) && _validateSignatures(hash, signers, signatures);
}
}
The weighted variant (MultiSignerERC7913Weighted) extends this with per-signer weights, enabling governance schemes where some signers carry more authority.
2.4 ERC-7913 Verifiers
OpenZeppelin provides three ready-to-deploy verifier contracts:
// P256 (secp256r1) — passkeys, secure enclaves
contract ERC7913P256Verifier is IERC7913SignatureVerifier {
function verify(bytes calldata key, bytes32 hash, bytes calldata signature)
public view virtual returns (bytes4)
{
if (key.length == 0x40 && signature.length >= 0x40) {
bytes32 qx = bytes32(key[0x00:0x20]);
bytes32 qy = bytes32(key[0x20:0x40]);
bytes32 r = bytes32(signature[0x00:0x20]);
bytes32 s = bytes32(signature[0x20:0x40]);
if (P256.verify(hash, r, s, qx, qy)) {
return IERC7913SignatureVerifier.verify.selector;
}
}
return 0xFFFFFFFF;
}
}
// ERC7913RSAVerifier: decodes (e, n) from key, calls RSA.pkcs1Sha256
// ERC7913WebAuthnVerifier: decodes WebAuthnAuth from signature, calls WebAuthn.verify
These are stateless contracts meant to be deployed once and referenced by all signers using that key type.
3. Solady — SignatureCheckerLib + ERC1271
3.1 SignatureCheckerLib (ERC-1271, EIP-2098, ERC-6492)
Solady provides gas-optimized signature checking with ECDSA and ERC-1271 support, entirely in assembly:
library SignatureCheckerLib {
// ECDSA + ERC-1271 verification
function isValidSignatureNow(address signer, bytes32 hash, bytes memory signature)
internal view returns (bool isValid);
// Calldata variant (saves gas on L2s)
function isValidSignatureNowCalldata(address signer, bytes32 hash, bytes calldata signature)
internal view returns (bool isValid);
// EIP-2098 compact: (r, vs)
function isValidSignatureNow(address signer, bytes32 hash, bytes32 r, bytes32 vs)
internal view returns (bool isValid);
// Decomposed: (v, r, s)
function isValidSignatureNow(address signer, bytes32 hash, uint8 v, bytes32 r, bytes32 s)
internal view returns (bool isValid);
// Pure ERC-1271 (no ECDSA fallback)
function isValidERC1271SignatureNow(address signer, bytes32 hash, bytes memory signature)
internal view returns (bool isValid);
// ERC-6492 support (counterfactual contracts)
function isValidERC6492SignatureNow(address signer, bytes32 hash, bytes memory signature)
internal returns (bool isValid);
}
Key characteristics:
- All assembly: Every function is written in Yul for minimal gas.
- EIP-2098 support: Accepts both 64-byte
(r, vs)and 65-byte(r, s, v)signatures natively. - ERC-6492 support: Can verify signatures from smart accounts that are not yet deployed (counterfactual).
- No ERC-7913 support: Solady does not implement the ERC-7913 standard.
- Zero-address guard: Returns
falseforaddress(0)signers.
3.2 ERC1271 Mixin
Solady’s ERC1271 is an abstract contract that smart accounts inherit. It implements the isValidSignature function with a nested EIP-712 approach and ERC-7739 detection:
abstract contract ERC1271 is EIP712 {
function isValidSignature(bytes32 hash, bytes calldata signature)
public view virtual returns (bytes4 result);
// Override to return the signer address
function _erc1271Signer() internal view virtual returns (address);
}
The validation pipeline tries three strategies in order:
- Safe caller check — If
msg.senderis a known safe caller (e.g.MulticallerWithSigner), validate directly. - Nested EIP-712 — Wrap the hash in a typed data structure to prevent cross-contract replay.
- RPC fallback — For
personal_signandeth_signTypedData_v4compatibility.
This design addresses the phishing risks described in ERC-7739.
3.3 Hashing Utilities
SignatureCheckerLib also includes toEthSignedMessageHash for EIP-191 message hashing, with an optimized assembly implementation supporting messages up to 999,999 bytes.
4. Solarity Solidity-lib — Custom Cryptography Primitives
Solidity-lib takes a fundamentally different approach. It does not implement ERC-1271 or ERC-7913. Instead, it provides low-level cryptographic verification libraries for curves beyond secp256k1:
| Library | Algorithm | Description |
|---|---|---|
ECDSA256 |
ECDSA on any 256-bit curve | Parameterized by EC256.Curve; works with P256, brainpool, etc. |
ECDSA384 |
ECDSA on 384-bit curves | secp384r1 and similar |
ECDSA512 |
ECDSA on 512-bit curves | secp521r1 and similar |
Schnorr256 |
Schnorr signatures | 256-bit curve Schnorr |
RSASSAPSS |
RSA-SSA-PSS | RSA signature verification with PSS padding |
ED256 |
EdDSA | 256-bit EdDSA (e.g. Ed25519) |
Example usage with ECDSA256:
library ECDSA256 {
function verify(
EC256.Curve memory ec, // Curve parameters (a, b, gx, gy, p, n, etc.)
bytes32 hashedMessage_,
bytes memory signature_, // bytes(r) + bytes(s)
bytes memory pubKey_ // bytes(x) + bytes(y)
) internal view returns (bool);
}
These libraries are designed for ZKP circuits, identity protocols, and bridge verifiers where specific curves are required. They operate at a lower level than ERC-1271/ERC-7913 and do not provide a unified isValidSignature interface.
5. Case Study: Coinbase Smart Wallet ERC-1271 (v1.1.0)
The Coinbase Smart Wallet is an ERC-4337-compatible smart account deployed in production. It implements its own ERC1271.sol, explicitly credited as “based on Solady’s”, but with significant architectural deviations. Comparing it to the library implementations reveals the trade-offs a production wallet makes.
5.1 Architecture Overview
The wallet is composed of three core contracts:
CoinbaseSmartWallet
├── ERC1271 (anti cross-account-replay)
├── MultiOwnable (index-based multi-owner)
├── IAccount (ERC-4337)
├── UUPSUpgradeable (Solady)
└── Receiver (Solady)
The ERC1271 contract provides the anti-replay wrapper. MultiOwnable manages owners. CoinbaseSmartWallet ties them together and implements _isValidSignature.
5.2 Anti-Replay Mechanism
Coinbase uses a simple, single-level EIP-712 wrapper with a custom type:
bytes32 private constant _MESSAGE_TYPEHASH = keccak256("CoinbaseSmartWalletMessage(bytes32 hash)");
function isValidSignature(bytes32 hash, bytes calldata signature) public view virtual returns (bytes4 result) {
if (_isValidSignature({hash: replaySafeHash(hash), signature: signature})) {
return 0x1626ba7e;
}
return 0xffffffff;
}
function replaySafeHash(bytes32 hash) public view virtual returns (bytes32) {
return keccak256(abi.encodePacked("\x19\x01", domainSeparator(), keccak256(abi.encode(_MESSAGE_TYPEHASH, hash))));
}
The domain separator includes address(this) and block.chainid, which prevents cross-account and cross-chain replay.
Compared to Solady’s ERC-7739 approach, Coinbase’s scheme is much simpler but has a key limitation: the original signed content becomes opaque. A wallet UI only sees CoinbaseSmartWalletMessage(bytes32 hash) – the actual data behind hash (e.g. a Permit2 approval, an NFT listing) is not visible to the user during signing.
Solady’s ERC-7739 TypedDataSign workflow preserves the original struct fields in the wallet UI by embedding them in a nested typed data structure, which gives users visibility into what they are actually signing.
| Aspect | Coinbase ERC1271 | Solady ERC1271 |
|---|---|---|
| Anti-replay strategy | Simple EIP-712 wrapper | ERC-7739 nested EIP-712 |
| Content readability in wallet UI | Opaque (bytes32 hash) |
Readable (original struct preserved) |
| Validation pipeline | Single path | 3-strategy (safe caller, nested EIP-712, RPC) |
| ERC-7739 support | No | Yes |
| ERC-6492 unwrapping | No (separate helper contract) | Yes (inline in _erc1271UnwrapSignature) |
| RPC off-chain fallback | No | Yes (tx.gasprice == 0 heuristic) |
| Code complexity | ~90 lines, pure Solidity | ~250 lines, heavy assembly |
5.3 Multi-Owner Signature Dispatch
Unlike Solady (single _erc1271Signer() returning one address) or OpenZeppelin’s AbstractSigner hierarchy, Coinbase uses an index-based multi-owner system via MultiOwnable:
struct SignatureWrapper {
uint256 ownerIndex; // Identifies which owner signed
bytes signatureData; // The actual signature
}
function _isValidSignature(bytes32 hash, bytes calldata signature) internal view virtual override returns (bool) {
SignatureWrapper memory sigWrapper = abi.decode(signature, (SignatureWrapper));
bytes memory ownerBytes = ownerAtIndex(sigWrapper.ownerIndex);
if (ownerBytes.length == 32) {
// Ethereum address owner: ECDSA or ERC-1271 via Solady's SignatureCheckerLib
address owner;
assembly ("memory-safe") { owner := mload(add(ownerBytes, 32)) }
return SignatureCheckerLib.isValidSignatureNow(owner, hash, sigWrapper.signatureData);
}
if (ownerBytes.length == 64) {
// P256 public key owner: WebAuthn verification
(uint256 x, uint256 y) = abi.decode(ownerBytes, (uint256, uint256));
WebAuthn.WebAuthnAuth memory auth = abi.decode(sigWrapper.signatureData, (WebAuthn.WebAuthnAuth));
return WebAuthn.verify({challenge: abi.encode(hash), requireUV: false, webAuthnAuth: auth, x: x, y: y});
}
revert InvalidOwnerBytesLength(ownerBytes);
}
The dispatch logic is:
- 32-byte owner: Treated as an Ethereum address. Verified with Solady’s
SignatureCheckerLib.isValidSignatureNow(ECDSA + ERC-1271). - 64-byte owner: Treated as a P256 public key (x, y). Verified with
WebAuthn.verifyfrom thewebauthn-sollibrary.
This is a two-key-type system, hardcoded in the contract. Adding a new key type (e.g. RSA) would require a contract upgrade.
5.4 Comparison with OpenZeppelin’s ERC-7913 Approach
OpenZeppelin’s MultiSignerERC7913 solves a similar problem (multi-owner with heterogeneous key types) but with a fundamentally different design:
| Aspect | Coinbase MultiOwnable |
OpenZeppelin MultiSignerERC7913 |
|---|---|---|
| Owner identifier | Index (uint256) |
bytes (verifier+key) |
| Key types | Hardcoded: address (32B) or P256 (64B) | Any via ERC-7913 verifiers |
| Adding new key types | Requires contract upgrade | Deploy a new verifier contract |
| Threshold | Not implemented (any single owner) | Configurable M-of-N threshold |
| Weighted voting | Not implemented | MultiSignerERC7913Weighted |
| Signature format | SignatureWrapper{ownerIndex, signatureData} |
abi.encode(signers[], signatures[]) |
| Duplicate detection | N/A (single signer per tx) | O(n) if sorted, O(n^2) otherwise |
5.5 Could Coinbase Replace Its ERC1271 with OpenZeppelin or Solady?
Replacing with Solady’s ERC1271 mixin:
Partially possible. Solady’s ERC1271 provides stronger anti-replay (ERC-7739) and richer features (ERC-6492 unwrapping, RPC fallback, safe caller bypass). However:
- Solady’s
_erc1271Signer()returns a singleaddress. Supporting multiple owners of different key types requires overriding_erc1271IsValidSignatureNowCalldatawith the same kind of dispatch logic Coinbase already has. - The
MultiOwnableindex-based ownership model has no equivalent in Solady – it would need to be kept. - The
SignatureWrapper{ownerIndex}encoding is specific to the Coinbase architecture and cannot be removed without breaking existing signed messages.
The practical migration would be: inherit Solady’s ERC1271 instead of the custom one, override _erc1271IsValidSignatureNowCalldata with the multi-owner dispatch, and gain ERC-7739 for free. The replaySafeHash function would no longer be needed as Solady handles anti-replay internally.
Replacing with OpenZeppelin’s approach:
This would require a deeper re-architecture:
- Replace
MultiOwnablewithMultiSignerERC7913(set-based instead of index-based). - Replace the hardcoded P256/WebAuthn dispatch with
ERC7913WebAuthnVerifier(deployed once, referenced by all P256 owners). - Use
AbstractSigner+SignerERC7913instead of the custom_isValidSignatureoverride. - Add
draft-ERC7739Utilsfor anti-replay.
The benefit would be extensibility: new key types (RSA, Ed25519, etc.) can be added by deploying verifier contracts without upgrading the wallet. The cost is a fundamentally different signature format (abi.encode(signers[], signatures[]) instead of SignatureWrapper{ownerIndex}), which is not backward-compatible.
Conclusion:
Coinbase’s ERC1271.sol is a deliberate simplification. It trades ERC-7739 readability and key-type extensibility for a smaller, more auditable codebase. The wallet already depends on Solady (SignatureCheckerLib, UUPSUpgradeable, Receiver), so the choice to not use Solady’s full ERC1271 mixin appears intentional – likely motivated by the desire for a transparent, easy-to-audit anti-replay mechanism. A migration to either Solady’s ERC1271 or OpenZeppelin’s MultiSignerERC7913 is technically feasible but would break backward compatibility with existing signatures.
6. Feature Comparison
| Feature | OpenZeppelin | Solady | Coinbase Smart Wallet | Solidity-lib |
|---|---|---|---|---|
| ECDSA (secp256k1) | ECDSA.recover |
assembly ecrecover |
Via Solady SignatureCheckerLib |
N/A (uses OZ) |
| ERC-1271 verification | isValidERC1271SignatureNow |
isValidERC1271SignatureNow |
N/A (is a wallet, not a verifier lib) | Not implemented |
| ERC-1271 implementation (for wallets) | via Account + AbstractSigner |
ERC1271 mixin |
Custom ERC1271 + MultiOwnable |
Not implemented |
| Anti-replay mechanism | draft-ERC7739Utils |
ERC-7739 built-in | Simple EIP-712 wrapper (opaque) | Not implemented |
| ERC-7913 verification | isValidSignatureNow(bytes, ...) |
Not implemented | Not implemented | Not implemented |
| ERC-7913 signer contracts | SignerERC7913, MultiSignerERC7913, Weighted |
Not implemented | Not implemented | Not implemented |
| ERC-7913 verifiers | P256, RSA, WebAuthn | Not implemented | Not implemented | Not implemented |
| Multi-owner support | MultiSignerERC7913 (set-based) |
Single signer | MultiOwnable (index-based) |
Not implemented |
| WebAuthn / P256 | Via ERC7913WebAuthnVerifier |
WebAuthn.sol |
Built-in via webauthn-sol |
Not implemented |
| EIP-2098 compact signatures | Supported | Native (64/65-byte auto-detect) | Via Solady | N/A |
| ERC-6492 (counterfactual) | Not implemented | isValidERC6492SignatureNow |
Helper contract (ERC1271InputGenerator) |
Not implemented |
| ERC-7739 (nested EIP-712) | draft-ERC7739Utils |
Built into ERC1271 |
Not implemented | Not implemented |
| Batch signature verification | areValidSignaturesNow |
Not implemented | Not implemented | Not implemented |
| Custom curves (P256, RSA, etc.) | Via verifiers (P256.sol, RSA.sol) |
P256.sol, WebAuthn.sol |
P256 only (via WebAuthn) | ECDSA256, ECDSA384, ECDSA512, Schnorr256, RSASSAPSS, ED256 |
| Assembly optimization | Partial (ERC-1271 call) | Full | Minimal (Solady deps only) | Pure Solidity |
7. Architecture Comparison
7.1 OpenZeppelin: Layered and Modular
SignatureChecker (library)
├── ECDSA.tryRecover (EOA)
├── IERC1271.isValidSignature (contract wallet)
└── IERC7913SignatureVerifier.verify (non-address key)
AbstractSigner (abstract)
├── SignerECDSA
├── SignerERC7913
├── SignerP256 / SignerRSA / SignerWebAuthn
├── MultiSignerERC7913
└── MultiSignerERC7913Weighted
Each layer is independent. SignatureChecker is a stateless library. Signer contracts are pluggable into Account. Verifiers are standalone deployments.
7.2 Solady: Flat and Gas-Optimized
SignatureCheckerLib (library)
├── ECDSA ecrecover (EOA, in assembly)
├── ERC-1271 staticcall (contract wallet, in assembly)
└── ERC-6492 wrapper (counterfactual, in assembly)
ERC1271 (abstract mixin)
├── Safe caller bypass
├── Nested EIP-712 (ERC-7739)
└── RPC fallback (personal_sign)
Everything is assembly. No ERC-7913 layer exists.
7.3 Solidity-lib: Cryptographic Primitives
ECDSA256 / ECDSA384 / ECDSA512 (libraries)
├── EC256.Curve parameterized verification
└── Shamir's trick for efficient multi-scalar multiplication
Schnorr256 / RSASSAPSS / ED256 (libraries)
└── Independent algorithm implementations
No signature routing or ERC-1271 integration. These are building blocks for custom verification logic.
8. Gas Considerations
OpenZeppelin SignatureChecker:
- ERC-1271 call uses assembly for the
staticcallto minimize overhead. - ERC-7913 routing adds one
staticcalland ABI decoding. areValidSignaturesNowuses O(n) duplicate detection when signers are sorted.
Solady SignatureCheckerLib:
- Full assembly minimizes gas for ECDSA + ERC-1271 paths.
- EIP-2098 compact signatures save 1 byte of calldata (significant on L2s).
calldatavariants avoid memory copies.- ERC-6492 adds a
callto a verifier contract (non-view).
Solidity-lib ECDSA256:
- Pure Solidity with
Math.invModPrimeand Shamir’s trick. - Heavier gas cost due to modular arithmetic on arbitrary curves.
- Designed for correctness on exotic curves, not gas minimization for secp256k1.
9. Security Considerations
Signature verification is a security-critical operation. Several cross-cutting concerns apply regardless of library choice:
- Anti-replay across accounts: When a single EOA key controls multiple smart contract wallets (common with ERC-7702), any signature valid for one account must not be replayable on another. Both Solady and OpenZeppelin address this with ERC-7739, which binds the signature to the account’s full EIP-712 domain and the calling application’s domain separator. Coinbase’s
CoinbaseSmartWalletMessagewrapper provides weaker protection — it binds to the account’s domain but does not preserve the application’s typed data structure. staticcallfor ERC-1271: Both OpenZeppelin and Solady usestaticcallwhen invokingisValidSignatureon external contracts, preventing state modifications during verification. This is essential to block reentrancy attacks that could exploit the verification flow.- Signature malleability: ERC-2098 compact signatures (Solady) normalize
svalues, mitigating ECDSA malleability. OpenZeppelin’sECDSA.tryRecoveralso rejects non-canonicalsvalues. When using Solidity-lib’s generic curve libraries, developers must ensure their curve parameters and verification logic handle malleability explicitly. - ERC-7913 verifier trust: OpenZeppelin’s ERC-7913 pattern routes verification to the contract at
address(bytes20(signer)). A compromised or malicious verifier contract can accept any signature. The verifier address is encoded in the signer bytes, so signer registration must be carefully controlled in multi-signature schemes. - Off-chain vs on-chain verification: Solady’s RPC-mode fallback (
tx.gasprice == 0heuristic) allows signature validation duringeth_calleven when nested EIP-712 wrapping is not used. This heuristic is not fully reliable and should not be the sole basis of a security-critical decision. See the full analysis for details.
10. When to Use Each
Choose OpenZeppelin when:
- You need ERC-7913 support (non-address keys: P256 passkeys, RSA, WebAuthn).
- You are building a multi-signature wallet with threshold or weighted governance.
- You need ready-to-deploy verifier contracts for standard key types.
- Maximum standards compliance and auditability matter.
Choose Solady when:
- Gas cost is the primary concern (L2 calldata savings, high-volume verification).
- You need ERC-6492 counterfactual signature verification.
- You need ERC-7739 nested EIP-712 for phishing protection in smart wallets.
- ERC-1271 + ECDSA coverage is sufficient (no ERC-7913 requirement).
Choose Solidity-lib when:
- You need signature verification on non-standard curves (secp384r1, secp521r1, brainpool).
- You are building ZKP circuits, identity protocols, or Bitcoin/cross-chain bridges.
- You need Schnorr, EdDSA, or RSA-PSS verification at the Solidity level.
- You will build your own routing logic on top of the primitives.
11. Conclusion
OpenZeppelin is the only library of the four that implements the full ERC-1271 + ERC-7913 stack, including signer management, multi-signature with thresholds and weights, and pre-built verifiers for P256, RSA, and WebAuthn. Solady focuses on gas-optimized ERC-1271 verification with unique features like ERC-6492 and ERC-7739, but does not support ERC-7913. The Coinbase Smart Wallet demonstrates what a production ERC-1271 implementation looks like: a deliberate simplification over Solady’s full ERC-7739 approach, trading content readability for auditability, while reusing Solady’s SignatureCheckerLib for the actual cryptographic verification. Solidity-lib takes a different path entirely, providing low-level cryptographic primitives for exotic curves rather than standard-based signature routing. The choice depends on whether you need ERC-7913 multi-key support (OpenZeppelin), gas-minimal ERC-1271 verification (Solady), a production-tested multi-owner wallet pattern (Coinbase), or custom-curve cryptography (Solidity-lib).

@startmindmap
* Signature Verification
** OpenZeppelin
*** SignatureChecker library
**** ECDSA + ERC-1271 + ERC-7913
**** Batch verification
*** AbstractSigner hierarchy
**** SignerECDSA
**** SignerERC7913
**** MultiSignerERC7913
**** MultiSignerERC7913Weighted
*** Verifier contracts
**** ERC7913P256Verifier
**** ERC7913RSAVerifier
**** ERC7913WebAuthnVerifier
** Solady
*** SignatureCheckerLib (assembly)
**** ECDSA + ERC-1271
**** EIP-2098 compact signatures
**** ERC-6492 counterfactual
*** ERC1271 mixin
**** ERC-7739 nested EIP-712
**** Safe caller bypass
*** No ERC-7913 support
** Coinbase Smart Wallet
*** Custom ERC1271 (based on Solady)
**** Simple EIP-712 anti-replay
**** No ERC-7739
*** MultiOwnable (index-based)
**** Address owners (ECDSA)
**** P256 owners (WebAuthn)
*** Uses Solady internally
**** SignatureCheckerLib
**** UUPSUpgradeable
** Solarity solidity-lib
*** Custom curve libraries
**** ECDSA256 / ECDSA384 / ECDSA512
**** Schnorr256
**** RSASSAPSS / ED256
*** No ERC-1271 / ERC-7913
*** Building blocks for custom logic
@endmindmap
References
Standards
- ERC-191: Signed Data Standard
- EIP-712: Typed Structured Data Hashing and Signing
- ERC-1271: Standard Signature Validation Method for Contracts
- ERC-2098: Compact Signature Representation
- ERC-6492: Signature Validation for Predeploy Contracts
- ERC-7739: Readable Typed Signatures for Smart Accounts
- ERC-7913: Signature Verifiers