34

I have tried to use ecrecover() to verify the signature of a message.

I have looked at lots of references here and elsewhere, like:

and others.

But I still cannot get ecrecover() to return the signing address. So I am hoping that someone can point out some stupid mistake I am making.

Here is my code:

pragma solidity ^0.4.0;

contract test {

  function test() {
  }

  function verify(bytes32 _message, uint8 _v, bytes32 _r, bytes32 _s) constant returns (address) {
   address signer = ecrecover(_message, _v, _r, _s);
   return signer;
  }
}

Then in geth, I do:

> var msg = web3.sha3("hello")
"0x1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8"

> eth.accounts[0] -->
"0x7156526fbd7a3c72969b54f64e42c10fbb768c8a"

> var sig = eth.sign(eth.accounts[0], msg)
"0x9242685bf161793cc25603c231bc2f568eb630ea16aa137d2664ac80388256084f8ae3bd7535248d0bd448298cc2e2071e56992d0774dc340c368ae950852ada1c"

> var r = sig.substr(0,66)
"0x9242685bf161793cc25603c231bc2f568eb630ea16aa137d2664ac8038825608"

> var s = "0x" + sig.substr(66,64)
"0x4f8ae3bd7535248d0bd448298cc2e2071e56992d0774dc340c368ae950852ada"

> var v = 28
28

> test.verify(msg,v,r,s)
"0x33692ee5cbf7ecdb8ca43ec9e815c47f3db8cd11"

...which of course, is NOT eth.accounts[0]

I am totally stumped. Is there anyone who can see what I am doing wrong?

4

6 Answers 6

56

I was stuck on this issue as well for a very long time.

So the solution is: Add this prefix string to your Solidity smart contract.

function verify(bytes32 hash, uint8 v, bytes32 r, bytes32 s) constant returns(bool) {

    bytes memory prefix = "\x19Ethereum Signed Message:\n32";
    bytes32 prefixedHash = keccak256(prefix, hash);
    return ecrecover(prefixedHash, v, r, s) == (Your Address);
}
4
15

According to issue #3731:

Geth prepends the string \x19Ethereum Signed Message:\n<length of message> to all data before signing it (https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_sign). If you want to verify such a signature from Solidity, you'll have to prepend the same string in solidity before doing the ecrecovery.

Here's a working example I tested out using truffle:

Example.sol

pragma solidity ^0.4.0;

contract Example {
    function testRecovery(bytes32 h, uint8 v, bytes32 r, bytes32 s) returns (address) {
        bytes memory prefix = "\x19Ethereum Signed Message:\n32";
        bytes32 prefixedHash = sha3(prefix, h);
        address addr = ecrecover(prefixedHash, v, r, s);

        return addr;
    }
}

example.js (test)

var Example = artifacts.require('./Example.sol')

var Web3 = require('web3')
var web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:8545'))

contract('Example', (accounts) => {
  var address = accounts[0]

  it('ecrecover result matches address', async function() {
    var instance = await Example.deployed()
    var msg = '0x8CbaC5e4d803bE2A3A5cd3DbE7174504c6DD0c1C'

    var h = web3.sha3(msg)
    var sig = web3.eth.sign(address, h).slice(2)
    var r = `0x${sig.slice(0, 64)}`
    var s = `0x${sig.slice(64, 128)}`
    var v = web3.toDecimal(sig.slice(128, 130)) + 27

    var result = await instance.testRecovery.call(h, v, r, s)
    assert.equal(result, address)
  })
})

Running test:

$ truffle test

Using network 'development'.

Compiling ./contracts/Example.sol...


  Contract: Example
    ✓ ecrecover result matches address (132ms)


  1 passing (147ms)

It's probably better to do the prefixing at the application level instead of in solidity contract since it'll be cheaper.

Related

2
  • Does the value '0x8CbaC5e4d803bE2A3A5cd3DbE7174504c6DD0c1C' that you assigned to the msg variable have any significance? Or is it just dummy data for the call? In other words, is it just something for the sha3() function to hash? Also, why do you need to use the slice() function on the hash with a 2 byte offset? Is it to get ride of the format identifier in the hash? Commented Nov 13, 2018 at 17:51
  • 1
    @RobertOschler great questions. 1. It's best practice to hash the message data to sign because it's always the same size. The msg value in the example is an example of a hash, eg sha3("some data to sign"). 2. Yes, it's to remove the 0x prefix which aren't important bytes that are required for the calculation
    – Miguel
    Commented Nov 13, 2018 at 21:35
7

So, with thanks to Adil (see above), here is the finished code that I used that definitely works with the process as outlined by me above:

pragma solidity ^0.4.0;

contract test {

  function test() {
  }

  function verify(bytes32 _message, uint8 _v, bytes32 _r, bytes32 _s) constant returns (address) {
    bytes memory prefix = "\x19Ethereum Signed Message:\n32";
    bytes32 prefixedHash = sha3(prefix, _message);
    address signer = ecrecover(prefixedHash, _v, _r, _s);
    return signer;
  }
}
4
  • I am totally confused by this. In your example contract the _message is the hash you really sign on, right? I mean the _message is the message your sign on and generate the _v, _r and _s. Then in the contract you hash it the second time with the prefix and throw it into the ecrecover function, right? Why we need to do like this and it seems to me doesn't work.(I generate the _r, _s, _v in Truffle and test the contract in Remix) Can you tell me how to solve it?
    – Wang
    Commented Jul 19, 2017 at 6:49
  • yes, that is exactly how it works.. you hash a message and pass it into the contract. Then you hash it again with the prefix. Then it works. But I don't know why it works. I cannot help you further than that, except to say that if you follow they above steps you should get the right answer. If anyone could explain why this works, or why you even need that random prefix, that would be great! Commented Jul 20, 2017 at 10:38
  • with go-ethereum you'll need v+27 instead. github.com/ethereum/go-ethereum/issues/2053 Commented Dec 18, 2020 at 11:40
  • v+27 fails where i've seen , but somebody use it for whatever reason Commented Dec 20, 2020 at 17:25
0

Are you sure you're using the right data types? I've created a simple validation app using ecrecover a few weeks ago.

You can watch the code here: https://github.com/Shultzi/validator/blob/master/client/main.js

As you can see, I passed the h, r and s not as buffers but a hex. Also, make sure that in your smart contract the v value is uint8

2
  • BTW, I just checked your arguments in my code and I also received the address "0x3369..." I'm certain that it has something to do with the account you're signing. Which client you're using for signing?
    – shultz
    Commented May 6, 2017 at 18:41
  • hi thanks for your reply... I am signing via the geth console. Using version 1.6.1-stable-021c3c28 of geth and doing web3.eth.sign() . You think there is a problem there? Commented May 10, 2017 at 17:06
0

The following article may explain the "\x19EthereumSignedMessage" part

https://blog.ricmoo.com/verifying-messages-in-solidity-50a94f82b2ca

1
  • 1
    The way OpenZeppelin's ECDSA does it is to require that you hash your message into a bytes32, rather than using evm assembly to generate a base10 string representing the arbitrary length of the message string.
    – Yaoshiang
    Commented May 16, 2019 at 1:36
0

OpenZeppelin's ECDSA has a tool to generate these wrapped messages as well as root out additional corner cases in ECDSA.

I wrote an article describing what I think is the right way to use web3's hashMessage() and sign(), Solidity's ecrecover(), and Open Zeppelin's ECDSA lib.

https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/cryptography/ECDSA.sol

https://medium.com/@yaoshiang/ethereums-ecrecover-openzeppelin-s-ecdsa-and-web3-s-sign-8ff8d16595e1

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.