490+ Tools Comprehensive Tools for Webmasters, Developers & Site Optimization

Ethereum Signature Verifier

Verify and analyze Ethereum digital signatures

The exact message that was signed
130 hexadecimal characters with 0x prefix (65 bytes)
The Ethereum address that supposedly signed the message

About Ethereum Signatures

Digital signatures are cryptographic proofs that verify a message was signed by the owner of a specific private key, without revealing the key itself. In Ethereum and Web3, signatures enable secure authentication and authorization.

How Ethereum Signatures Work

Ethereum uses the Elliptic Curve Digital Signature Algorithm (ECDSA) with the secp256k1 curve. The signing process:

  1. Message Hashing: Message is hashed using keccak256
  2. Prefix Addition: EIP-191 prefix prevents replay attacks
  3. Signing: Private key signs the hash
  4. Components: Produces r, s, v values

EIP-191 Message Prefix

To prevent signed messages from being valid transactions, Ethereum prepends a prefix:

"\x19Ethereum Signed Message:\n" + message.length + message

This ensures signatures created for off-chain purposes can't be replayed as transactions.

Signature Components

ECDSA Signature Parts
  • r (32 bytes): X-coordinate of a random point on the curve
  • s (32 bytes): Signature proof value
  • v (1 byte): Recovery identifier (27 or 28, sometimes 0 or 1)

Total: 65 bytes (130 hex characters)

Common Use Cases

1. Wallet Authentication

Sign-in with Ethereum (SIWE) allows users to authenticate with their wallet:

// Frontend (ethers.js)
const signer = provider.getSigner();
const message = "Sign in to MyApp";
const signature = await signer.signMessage(message);

// Backend verifies signature
const address = ethers.utils.verifyMessage(message, signature);

2. Gasless Transactions

Users sign transaction intent off-chain, relayer submits on-chain:

  • User signs permit/approval message
  • Relayer submits transaction with signature
  • Contract verifies signature and executes
  • User pays no gas

3. Message Verification

Prove ownership of address without transaction:

  • Service provides challenge message
  • User signs with wallet
  • Service verifies signature
  • Access granted if valid

4. NFT/Token Permits

EIP-2612 allows gasless approvals using signatures:

// User signs permit message
const signature = await token.signPermit(
    owner, spender, value, deadline
);

// Contract verifies and sets allowance
token.permit(owner, spender, value, deadline, v, r, s);

Signature Standards

EIP-191: Signed Data Standard

Defines format for signed data with version byte:

  • 0x00: Data with intended validator
  • 0x01: Structured data (EIP-712)
  • 0x45: Personal message (most common)

EIP-712: Typed Structured Data

Allows signing complex structured data with type information:

const domain = {
    name: 'MyDApp',
    version: '1',
    chainId: 1,
    verifyingContract: '0x...'
};

const types = {
    Transfer: [
        { name: 'to', type: 'address' },
        { name: 'amount', type: 'uint256' }
    ]
};

const value = {
    to: '0x...',
    amount: 1000000
};

const signature = await signer._signTypedData(domain, types, value);

Verification in Solidity

Using ECDSA Library

import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";

function verify(
    string memory message,
    bytes memory signature,
    address expectedSigner
) public pure returns (bool) {
    bytes32 messageHash = keccak256(abi.encodePacked(message));
    bytes32 ethSignedHash = ECDSA.toEthSignedMessageHash(messageHash);
    address recoveredSigner = ECDSA.recover(ethSignedHash, signature);
    return recoveredSigner == expectedSigner;
}

Manual Recovery

function recoverSigner(
    bytes32 messageHash,
    bytes memory signature
) public pure returns (address) {
    require(signature.length == 65, "Invalid signature length");

    bytes32 r;
    bytes32 s;
    uint8 v;

    assembly {
        r := mload(add(signature, 32))
        s := mload(add(signature, 64))
        v := byte(0, mload(add(signature, 96)))
    }

    return ecrecover(messageHash, v, r, s);
}

Security Considerations

Replay Protection

  • Include nonce or timestamp in message
  • Specify chain ID for multi-chain apps
  • Add expiration time to prevent stale signatures
  • Track used signatures on-chain

Signature Malleability

The 's' value can be flipped to create valid alternative signatures. Always validate:

require(uint256(s) <= 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0, "Invalid s value");

Zero Address Check

Failed signature recovery returns address(0). Always check:

address signer = ECDSA.recover(hash, signature);
require(signer != address(0), "Invalid signature");

Best Practices

  • Always use EIP-191 or EIP-712 for message signing
  • Include domain separation in structured data
  • Validate all signature components
  • Implement replay protection
  • Add expiration times to signatures
  • Use established libraries (OpenZeppelin, ethers.js)
  • Never sign arbitrary data from untrusted sources
Signature Format
  • Total: 65 bytes (130 hex)
  • r: 32 bytes (64 hex)
  • s: 32 bytes (64 hex)
  • v: 1 byte (2 hex)
  • Prefix: 0x
  • Example: 0x1234...5678
Common Issues
  • Wrong message format
  • Missing EIP-191 prefix
  • Incorrect v value (27/28 vs 0/1)
  • Signature from wrong network
  • Using transaction signature for message
  • Not checking zero address