If I have a cryptographic signature from an Ethereum address key pair, how can I verify that signature?
1 Answer
Solidity and Serpent have ecrecover
for this purpose.
ecrecover(bytes32 data, uint8 v, bytes32 r, bytes32 s) returns (address)
The function arguments are:
data
is what was signed. Since it is 32 bytes, that usually means that the initial data is hashed first to 32 bytes, before it was signed.
v
, r
, s
is the signature. (v
is the recovery id: a 1 byte value specifying the sign and finiteness of the curve point; this
value is in the range of [27, 30], however the Ethereum protocol declares the upper two possibilities, representing infinite values, invalid)
Important note with the examples below, sha3 is Keccak-256.
Here is a snippet in Solidity:
contract Auth {
function verify(address p, bytes32 hash, uint8 v, bytes32 r, bytes32 s) constant returns(bool) {
// Note: this only verifies that signer is correct.
// You'll also need to verify that the hash of the data
// is also correct.
return ecrecover(hash, v, r, s) == p;
}
}
Here is an example in Serpent:
def test_ecrecover(h, v, r, s):
return(ecrecover(h, v, r, s))
The corresponding test code in Python (requires bitcoin
and ethereum
packages):
import bitcoin as b
from ethereum import tester, utils
class TestECRecover(object):
CONTRACT = """
def test_ecrecover(h, v, r, s):
return(ecrecover(h, v, r, s))
"""
def setup_class(cls):
cls.s = tester.state()
cls.c = cls.s.abi_contract(cls.CONTRACT)
cls.snapshot = cls.s.snapshot()
def setup_method(self, method):
self.s.revert(self.snapshot)
def test_ecrecover(self):
priv = b.sha256('some big long brainwallet password')
pub = b.privtopub(priv)
msghash = b.sha256('the quick brown fox jumps over the lazy dog')
V, R, S = b.ecdsa_raw_sign(msghash, priv)
assert b.ecdsa_raw_verify(msghash, (V, R, S), pub)
addr = utils.sha3(b.encode_pubkey(pub, 'bin')[1:])[12:]
assert utils.privtoaddr(priv) == addr
result = self.c.test_ecrecover(utils.big_endian_to_int(msghash.decode('hex')), V, R, S)
assert result == utils.big_endian_to_int(addr)
Under the hood, ecrecover
uses the ECDSARECOVER
precompiled contract located at address 1.
Note: Geth and web3.eth.sign will add a prefix to the data
message before signing.
The sign method calculates an Ethereum specific signature with: sign(keccak256("\x19Ethereum Signed Message:\n" + len(message) + message))).
By adding a prefix to the message makes the calculated signature recognisable as an Ethereum specific signature. This prevents misuse where a malicious DApp can sign arbitrary data (e.g. transaction) and use the signature to impersonate the victim.
For this case, the second argument to verify()
must be keccak256("\x19Ethereum Signed Message:\n", len(message), message)
instead of keccak256(message)
.
-
While already a very thorough answer, can you add what ecrecover returns in case of a good or invalid signature?– J-BCommented Jan 28, 2016 at 3:32
-
1As I understand it, a good signature will return the address which did the signing. A bad signature will return a different address. There's no way to tell if a signature is valid or not without also knowing the address which was supposed to do the signing.– ryepdxCommented Mar 20, 2016 at 22:17
-
1From the solidity docs on ecrecover: "recover the address associated with the public key from elliptic curve signature or return zero on error"– ZMittonCommented Feb 21, 2017 at 22:00
-
Thank you to the anonymous editor who added the important code comment: "You'll also need to verify that the hash of the data is also correct"– eth ♦Commented Aug 10, 2017 at 6:45
-
If the signature is wrong, you still get a valid address from this. So do you just have to make the signer include the address somewhere in the original message to check for correctness?– sudoCommented Jun 3, 2018 at 4:14