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
ands
: 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, andr
. There are two possible values fors
(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
or28
on Ethereum. v
helps theecrecover
function (used to recover the public key from the signature) determine which of two possible public keys corresponds to the signature. Withoutv
, there would be ambiguity about which public key was used to sign the message.
- The recovery identifier, also known as the "recovery id". It is a single byte that can be either
3. How the Signature is Created
Here’s how the signing process typically works off-chain:
- 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(...));
- Sign the Hash: The private key signs the hash of the message, producing the signature. The signing process yields three values:
r
,s
, andv
.- The elliptic curve algorithm computes the points
r
ands
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.
- The elliptic curve algorithm computes the points
- Provide the Signature: The resulting signature, composed of
r
,s
, andv
, 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);
digest
: The hash of the message, which was signed off-chain.v
: The recovery identifier (helps in resolving which key to use).r
ands
: The signature values generated during the signing process. Theecrecover
function returns the address corresponding to the public key of the signer. The contract compares this address with the expectedowner
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
, thevalue
,nonce
, anddeadline
. - This produces the
v
,r
, ands
values. - The spender or a third party submits the signed permit (with the
v
,r
, ands
values) to the blockchain, where it is validated usingecrecover
.
The contract then:
- Hashes the permit message using the same parameters.
- Uses
ecrecover
to recover the address of the signer from the signature components (v
,r
,s
). - 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);
ecrecover(digest, v, r, s)
uses the digest (hashed message) and thev
,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
ands
: 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) ) );
}
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:
- User Signs a Permit: Off-chain, the user signs a message containing details like the spender address, amount, nonce, and deadline.
- Submit the Permit On-Chain:
The spender submits the signed message to the contract via the
permit
function. - Verify the Signature:
The smart contract uses EIP-712 hashing and signature recovery (via
ecrecover
) to ensure that:
- The signature is valid.
- It was signed by the token owner.
- The
nonce
is unique, and thedeadline
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);
}
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.