Why does ecrecover call in my Solidity code fail to resolve the signer?
ecrecover()
is a very useful Solidity function that allows the smart contract to validate that incoming data is properly signed by an expected party. The signing must done according to the technique described here: https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_sign. The resulting signature can then be successfully validated by Solidity withecrecover()
.
A sample Solidity code to do the validation looks like the following:
function verify(bytes32 documentHash, uint8[] memory sigV, bytes32[] memory sigR, bytes32[] memory sigS) public {
bytes memory prefix = "\x19Ethereum Signed Message:\n32";
bytes32 prefixedProof = keccak256(abi.encodePacked(prefix, documentHash));
address recovered = ecrecover(prefixedProof, sigV[0], sigR[0], sigS[0]);
emit LogSignature(documentHash, prefixedProof, recovered);
}
Therecovered
value should be the account address of the Ethereum signer. However sometimes this fails and the value is returned as "0x0000000000000000000000000000000000000000".
The most likely cause of the failure to recover the signer is due to the difference in how different Ethereum implementations returns thev
portion of the signature. For a simple explanation of ther
,s
andv
portions of an Ethereum signature, visit this Solidity documentation page. Different Ethereum implementations returns differentv
values from theeth_sign
call. This can catch developers by surprise and can result in the error above.
Basically Solidity expects thev
value to be either27
or28
, as explained here. Other values are also possible when signing transactions using EIP 155 schema. But currently theeth_sign
implementations do not support EIP 155 signing.
Given the above, the client program that calls the Solidity function must supply av
value of either27
or28
. However, when calling the target Ethereum node to sign, differentv
values may be returned.
testrpc
and Ganache, both popular with development environments, return00
or01
for the V value portion of the signature. So27
must be added in order to calculate the correct value.
Geth/Quorum, on the other hand, both return the V value according to the ecrecover technique, with 27 already added.
Protocol | eth.sign() supported? |
To calculate V value from returned signature |
---|---|---|
Geth | Yes | web3.utils.toDecimal("0x" + sig.slice(130, 132)) |
Quorum | Yes | web3.utils.toDecimal("0x" + sig.slice(130, 132)) |
Besu | No | |
testrpc | Yes | web3.utils.toDecimal("0x" + sig.slice(130, 132)) + 27 |
Ganache | Yes | web3.utils.toDecimal("0x" + sig.slice(130, 132)) + 27 |
A client program must compensate for this difference in order to get the properv
value. Here's a sample truffle project that demonstrates the difference and what client programs must to do always pass in the properv
value.