In blockchain and Web3 technologies, secure communication and identity verification are made possible by digital signatures. On Ethereum, one of the core tools for verifying these signatures is the built-in Solidity function ecrecover.
In this post, we’ll explore what ecrecover is, why we need the v, r, and s parameters, what they represent, and how digital signature verification works. We'll also walk through practical examples of both signing and verification.
Ethereum uses the ECDSA (Elliptic Curve Digital Signature Algorithm) over the secp256k1 curve to ensure the security of messages and transactions. Here's how it works:
A user signs the hash of a message with their private key.
The result is a digital signature composed of three parts:
r: A 32-byte value generated during the signing process.
s: Another 32-byte value.
v: A 1-byte recovery identifier used to determine the correct public key from the signature. In Ethereum, this is typically 27 or 28.
Together, these three values prove that the message was signed by someone in possession of the corresponding private key.
In Solidity, the ecrecover function is used as follows:
address signer = ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s);
hash: The keccak256 hash of the signed message.
v, r, and s: The three components of the digital signature.
This function returns the Ethereum address that created the signature, allowing you to verify the authenticity of the signer directly onchain.
r: First 32-byte value output from the ECDSA signing process.
s: Second 32-byte value of the signature.
v: Recovery parameter, helps choose the correct public key. Usually 27 or 28 in Ethereum.
The v value is necessary because the same r and s can produce two possible public keys. v tells us which one is valid.
import { ethers } from "ethers";
async function signMessage() {
const wallet = ethers.Wallet.createRandom();
const message = "Hello Ethereum!";
const signature = await wallet.signMessage(message);
const sig = ethers.Signature.from(signature);
console.log("Address:", wallet.address);
console.log("Message:", message);
console.log("v:", sig.v);
console.log("r:", sig.r);
console.log("s:", sig.s);
}
signMessage();
This script creates a random wallet, signs a message, and logs the v, r, and s values to the console.
pragma solidity ^0.8.0;
contract VerifySignature {
function verify(
bytes32 messageHash,
uint8 v,
bytes32 r,
bytes32 s,
address expectedSigner
) public pure returns (bool) {
return ecrecover(messageHash, v, r, s) == expectedSigner;
}
}
This contract takes the messageHash and signature components, recovers the signer’s address, and checks if it matches the expected address.

Using ecrecover directly inside zero-knowledge proof (ZKP) circuits is computationally expensive. Instead, most ZK circuits accept the signer’s public key (split into x and y coordinates) as an input.
Here’s an example in Noir:
fn main(
pub_key_x: [u8; 32],
pub_key_y: [u8; 32],
signature: [u8; 64],
hashed_message: pub [u8; 32]
) -> pub Field {
let address = ecrecover::ecrecover(pub_key_x, pub_key_y, signature, hashed_message);
address
}
This approach avoids the cost of recovering the public key from the signature and makes the circuit more efficient.
EIP-155 introduced a modification to the v parameter to include the chainId as a protection against replay attacks.
The formula is:
v = CHAIN_ID * 2 + 35 or CHAIN_ID * 2 + 36
Examples:
On Ethereum Mainnet (chainId = 1), possible v values are 37 and 38.
On Polygon (chainId = 137), possible values are 309 and 310.
This ensures that the same signature cannot be reused across multiple chains.
While v is typically 27 or 28, EIP-155 allows it to take other values.
Always validate the v value correctly during signature verification.
Always check that the address returned by ecrecover matches the expected signer.
To prevent replay attacks, include a nonce or chainId in the message being signed.
Ethereum’s ecrecover function is a foundational tool for verifying digital signatures on-chain. Understanding how it works including the role of v, r, and s is critical for writing secure smart contracts.
As zero-knowledge technologies evolve, verifying signatures efficiently off-chain and inside ZK circuits becomes more practical but at the core, it all starts with understanding how signature recovery works.
In blockchain and Web3 technologies, secure communication and identity verification are made possible by digital signatures. On Ethereum, one of the core tools for verifying these signatures is the built-in Solidity function ecrecover.
In this post, we’ll explore what ecrecover is, why we need the v, r, and s parameters, what they represent, and how digital signature verification works. We'll also walk through practical examples of both signing and verification.
Ethereum uses the ECDSA (Elliptic Curve Digital Signature Algorithm) over the secp256k1 curve to ensure the security of messages and transactions. Here's how it works:
A user signs the hash of a message with their private key.
The result is a digital signature composed of three parts:
r: A 32-byte value generated during the signing process.
s: Another 32-byte value.
v: A 1-byte recovery identifier used to determine the correct public key from the signature. In Ethereum, this is typically 27 or 28.
Together, these three values prove that the message was signed by someone in possession of the corresponding private key.
In Solidity, the ecrecover function is used as follows:
address signer = ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s);
hash: The keccak256 hash of the signed message.
v, r, and s: The three components of the digital signature.
This function returns the Ethereum address that created the signature, allowing you to verify the authenticity of the signer directly onchain.
r: First 32-byte value output from the ECDSA signing process.
s: Second 32-byte value of the signature.
v: Recovery parameter, helps choose the correct public key. Usually 27 or 28 in Ethereum.
The v value is necessary because the same r and s can produce two possible public keys. v tells us which one is valid.
import { ethers } from "ethers";
async function signMessage() {
const wallet = ethers.Wallet.createRandom();
const message = "Hello Ethereum!";
const signature = await wallet.signMessage(message);
const sig = ethers.Signature.from(signature);
console.log("Address:", wallet.address);
console.log("Message:", message);
console.log("v:", sig.v);
console.log("r:", sig.r);
console.log("s:", sig.s);
}
signMessage();
This script creates a random wallet, signs a message, and logs the v, r, and s values to the console.
pragma solidity ^0.8.0;
contract VerifySignature {
function verify(
bytes32 messageHash,
uint8 v,
bytes32 r,
bytes32 s,
address expectedSigner
) public pure returns (bool) {
return ecrecover(messageHash, v, r, s) == expectedSigner;
}
}
This contract takes the messageHash and signature components, recovers the signer’s address, and checks if it matches the expected address.

Using ecrecover directly inside zero-knowledge proof (ZKP) circuits is computationally expensive. Instead, most ZK circuits accept the signer’s public key (split into x and y coordinates) as an input.
Here’s an example in Noir:
fn main(
pub_key_x: [u8; 32],
pub_key_y: [u8; 32],
signature: [u8; 64],
hashed_message: pub [u8; 32]
) -> pub Field {
let address = ecrecover::ecrecover(pub_key_x, pub_key_y, signature, hashed_message);
address
}
This approach avoids the cost of recovering the public key from the signature and makes the circuit more efficient.
EIP-155 introduced a modification to the v parameter to include the chainId as a protection against replay attacks.
The formula is:
v = CHAIN_ID * 2 + 35 or CHAIN_ID * 2 + 36
Examples:
On Ethereum Mainnet (chainId = 1), possible v values are 37 and 38.
On Polygon (chainId = 137), possible values are 309 and 310.
This ensures that the same signature cannot be reused across multiple chains.
While v is typically 27 or 28, EIP-155 allows it to take other values.
Always validate the v value correctly during signature verification.
Always check that the address returned by ecrecover matches the expected signer.
To prevent replay attacks, include a nonce or chainId in the message being signed.
Ethereum’s ecrecover function is a foundational tool for verifying digital signatures on-chain. Understanding how it works including the role of v, r, and s is critical for writing secure smart contracts.
As zero-knowledge technologies evolve, verifying signatures efficiently off-chain and inside ZK circuits becomes more practical but at the core, it all starts with understanding how signature recovery works.
Share Dialog
Share Dialog

Subscribe to Azriel

Subscribe to Azriel
<100 subscribers
<100 subscribers
No activity yet