DEV Community

Cover image for πŸ” Understanding Ethereum ECDSA, EIP-712 and Its Role in Permit Functionality 🧾
Truong Phung
Truong Phung

Posted on

πŸ” Understanding Ethereum ECDSA, EIP-712 and Its Role in Permit Functionality 🧾

1. ECDSA

In Ethereum and Solidity, digital signatures are generated using the Elliptic Curve Digital Signature Algorithm (ECDSA). This algorithm is used to verify the authenticity of a message signed by a private key. The signature is typically broken into three components: v, r, and s. Let's explore these components in more detail:

1. ECDSA Signature Basics

When someone signs a message using their private key (off-chain), they produce a signature consisting of two main values, r and s, plus a recovery identifier v. This signature proves that the message was signed by the owner of a particular private key without revealing the private key itself.

  • r and s: These are the two components of the actual signature, representing points on the elliptic curve.
    • r: A random integer generated during the signing process, part of the elliptic curve calculations.
    • s: The second half of the signature, derived from the private key and the message being signed.
  • v: The recovery identifier, which is used to recover the signer's public key from the signature.

2. Understanding v, r, and s

  • r (32 bytes):
    • A point on the elliptic curve generated during the signature creation process.
    • It is deterministic but varies with each signature, even if the same message is signed multiple times.
  • s (32 bytes): The second part of the signature, calculated using the private key, the message hash, and r. There are two possible values for s (high and low). By convention, the "low" value is used to prevent signature malleability attacks (i.e., changing the signature without changing the underlying message).
  • v (1 byte):
    • The recovery identifier, also known as the "recovery id". It is a single byte that can be either 27 or 28 on Ethereum.
    • v helps the ecrecover function (used to recover the public key from the signature) determine which of two possible public keys corresponds to the signature. Without v, there would be ambiguity about which public key was used to sign the message.

3. How the Signature is Created

Here’s how the signing process typically works off-chain:

  1. Hash the Message: The message is hashed using the Keccak-256 hashing algorithm. This produces a unique hash of the message that is used for signing.

    messageHash = keccak256(abi.encodePacked(...));
    
  2. Sign the Hash: The private key signs the hash of the message, producing the signature. The signing process yields three values: r, s, and v.

    • The elliptic curve algorithm computes the points r and s based on the private key and the message hash.
    • v is derived from the signing process and tells us which public key can be used to verify the signature.
  3. Provide the Signature: The resulting signature, composed of r, s, and v, is then sent alongside the message. The contract will later use these values to verify that the signature is valid and corresponds to the correct address (public key).

4. How the Signature is Verified On-Chain

In Solidity, the function used to verify an ECDSA signature is ecrecover. This function takes four parameters: digest, v, r, and s. It recovers the public key of the signer (which is derived from the signer's Ethereum address) and checks whether it matches the expected signer.

Here’s how this works:

address recoveredAddress = ecrecover(digest, v, r, s);
Enter fullscreen mode Exit fullscreen mode
  • digest: The hash of the message, which was signed off-chain.
  • v: The recovery identifier (helps in resolving which key to use).
  • r and s: The signature values generated during the signing process.
    The ecrecover function returns the address corresponding to the public key of the signer. The contract compares this address with the expected owner address:

    require(recoveredAddress != address(0) && recoveredAddress == owner, 'INVALID_SIGNATURE');
    

If the recovered address matches the owner's address and is not the zero address, the signature is valid.

5. Use Case in the permit Function

In the context of the permit function (ERC-2612):

  • The user (owner) signs a permit off-chain, which contains the information about the spender, the value, nonce, and deadline.
  • This produces the v, r, and s values.
  • The spender or a third party submits the signed permit (with the v, r, and s values) to the blockchain, where it is validated using ecrecover.

The contract then:

  1. Hashes the permit message using the same parameters.
  2. Uses ecrecover to recover the address of the signer from the signature components (v, r, s).
  3. Ensures that the recovered address matches the owner's address to confirm the validity of the signature.

6. Signature Verification Example in Practice

Here’s a simplified example of how the signing and recovery process works:

1. Off-Chain Signature Creation (using a wallet):

The wallet signs the hashed message (permit details) with the private key. This generates r, s, and v.

2. On-Chain Signature Verification:
bytes32 digest = keccak256(
    abi.encodePacked(
        '\x19\x01',              // EIP-712 prefix
        DOMAIN_SEPARATOR,        // Contract domain separator
        keccak256(abi.encode(    // Hash of the permit data
            PERMIT_TYPEHASH, 
            owner, 
            spender, 
            value, 
            nonces[owner]++, 
            deadline
        ))
    )
);
address recoveredAddress = ecrecover(digest, v, r, s);
Enter fullscreen mode Exit fullscreen mode
  • ecrecover(digest, v, r, s) uses the digest (hashed message) and the v, r, s signature components to recover the signer’s address.
  • If the recovered address matches the expected owner, the signature is valid.

Summary of v, r, s in ECDSA

  • r and s: The signature components representing points on the elliptic curve. These are used to verify that the message was signed by the private key corresponding to the signer's address.
  • v: The recovery id, which helps resolve which of the two possible public keys (from elliptic curve math) was used to sign the message.

The combination of these values ensures secure, verifiable signatures in Ethereum smart contracts, allowing off-chain signature generation and on-chain verification.

2. Understanding EIP-712 and Its Role in Permit Functionality

When working with Ethereum and Solidity, secure off-chain data signing is a critical requirement for many applications, especially those involving token approvals and meta-transactions. EIP-712, a standard for "Typed Structured Data Hashing and Signing," plays a pivotal role in enabling these functionalities seamlessly and securely.

What is EIP-712?

EIP-712 standardizes how structured data is encoded, hashed, and signed, ensuring compatibility between off-chain systems and Ethereum smart contracts. It provides:

  • Human-Readable Messages: Makes it easier for users to verify the content of the data being signed.
  • Security: Includes domain-specific details like chain ID and contract address to prevent signature reuse (replay attacks) across different domains or contracts.

EIP-712Domain in Solidity

The EIP712Domain is a structured hash that uniquely identifies a contract and its context. It includes fields such as:

  • name: The name of the contract (e.g., "MyToken").
  • version: The version of the contract (e.g., "1").
  • chainId: The blockchain ID, ensuring signatures are valid only on the intended chain.
  • verifyingContract: The contract address.

The DOMAIN_SEPARATOR is the hash of this domain and is a key element used in the final signature verification process.
Example of constructing the DOMAIN_SEPARATOR in Solidity:

bytes32 public DOMAIN_SEPARATOR;

constructor() {
    DOMAIN_SEPARATOR = keccak256(
        abi.encode(
            keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
            keccak256(bytes("MyToken")),
            keccak256(bytes("1")),
            block.chainid,
            address(this)
        )
    );
}
Enter fullscreen mode Exit fullscreen mode

Permit: Gasless Approvals with ERC-2612

The permit function, introduced in ERC-2612, leverages EIP-712 to allow users to approve token allowances using off-chain signatures instead of on-chain transactions. This eliminates the need for an approve transaction, saving gas and streamlining user interactions.

How Permit Works:

  1. User Signs a Permit: Off-chain, the user signs a message containing details like the spender address, amount, nonce, and deadline.
  2. Submit the Permit On-Chain: The spender submits the signed message to the contract via the permit function.
  3. Verify the Signature: The smart contract uses EIP-712 hashing and signature recovery (via ecrecover) to ensure that:
  4. The signature is valid.
  5. It was signed by the token owner.
  6. The nonce is unique, and the deadline has not expired.

Example of the permit function in Solidity:

function permit(
    address owner,
    address spender,
    uint256 value,
    uint256 deadline,
    uint8 v,
    bytes32 r,
    bytes32 s
) external {
    require(deadline >= block.timestamp, "Permit: expired");
    bytes32 structHash = keccak256(
        abi.encode(
            keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"),
            owner,
            spender,
            value,
            nonces[owner]++,
            deadline
        )
    );
    bytes32 digest = keccak256(
        abi.encodePacked("\x19\x01", DOMAIN_SEPARATOR, structHash)
    );
    address signer = ecrecover(digest, v, r, s);
    require(signer == owner, "Permit: invalid signature");
    _approve(owner, spender, value);
}
Enter fullscreen mode Exit fullscreen mode

Why Use EIP-712 and Permit?

  • User Convenience: Users no longer need to perform multiple transactions (e.g., approve and transfer), reducing friction.
  • Gas Savings: Permit eliminates the need for an approve transaction, significantly saving gas fees.
  • Security: With EIP-712, signatures are tied to the contract and chain, preventing replay attacks.

EIP-712 and Permit are foundational technologies for building user-friendly and efficient decentralized applications, especially in the context of DeFi and tokenized ecosystems.

If you found this helpful, let me know by leaving a πŸ‘ or a comment!, or if you think this post could help someone, feel free to share it! Thank you very much! πŸ˜ƒ

Top comments (2)

Collapse
 
stevendev0822 profile image
Steven

Thanks for sharing.
I guess its worth it

Collapse
 
truongpx396 profile image
Truong Phung

thank you Steven for your feedback πŸ˜ƒ