I am trying to verify a signature in solidity, which I generated in my truffle test. I followed this tutorial for implementing the smart contract, but made small adjustions:
library Lib {
/**
Signature verification functions
*/
function getMessageHash(address _to) public pure returns (bytes32) {
return keccak256(abi.encodePacked(_to));
}
function getEthSignedMessageHash(bytes32 _messageHash) public pure returns (bytes32) {
/*
Signature is produced by signing a keccak256 hash with the following format:
"\x19Ethereum Signed Message\n" + len(msg) + msg
*/
return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", _messageHash));
}
function recoverSigner(bytes32 _ethSignedMessageHash, bytes memory _signature)
public pure returns (address)
{
(bytes32 r, bytes32 s, uint8 v) = splitSignature(_signature);
return ecrecover(_ethSignedMessageHash, v, r, s);
}
function splitSignature(bytes memory sig) public pure returns (bytes32 r, bytes32 s, uint8 v) {
require(sig.length == 65, "invalid signature length");
assembly {
/*
First 32 bytes stores the length of the signature
add(sig, 32) = pointer of sig + 32
effectively, skips first 32 bytes of signature
mload(p) loads next 32 bytes starting at the memory address p into memory
*/
// first 32 bytes, after the length prefix
r := mload(add(sig, 32))
// second 32 bytes
s := mload(add(sig, 64))
// final byte (first byte of the next 32 bytes)
v := byte(0, mload(add(sig, 96)))
}
// implicitly return (r, s, v)
}
function verify(address _signer, address _to, bytes memory signature) public pure returns (bool) {
bytes32 messageHash = getMessageHash(_to);
bytes32 ethSignedMessageHash = getEthSignedMessageHash(messageHash);
return recoverSigner(ethSignedMessageHash, signature) == _signer;
}
}
contract MyContract{
address public myaddr;
...
function submitSignature(bytes memory signature) signerCheck public {
if(Lib.verify(myaddr, address(this), signature)){ // returns false
mysignature = signature;
...
}else{
revert("Signature does not match sender.");
}
}
}
In my truffle test I'm creating the signature as follows:
var hash = MyContract.web3.extend.utils
.soliditySha3({v: mycontract.address, t: 'address' });
var prefixedHash = MyContract.web3.extend.utils.soliditySha3("\x19Ethereum Signed Message:\n32", hash);
var signature = await MyContract.web3.eth.sign(prefixedHash, accounts[1], (error, result) => {
if (error) {
console.log(error)
}
});
var tx = await mycontract.submitSignature(signature, { from: accounts[1] });
The verify
function returns false. I am not entirely sure how solidity hashes addresses and whether web3.eth.sign
prefixes the signature with \x19Ethereum Signed Message:\n
or not. I tried different variations, nothing worked.
Update:
web3.eth.sign
already prepends \x19Ethereum Signed Message:\n
, so the correct signature would be
var hash = MyContract.web3.extend.utils
.soliditySha3({v: mycontract.address, t: 'address' });
var signature = await MyContract.web3.eth.sign(prefixedHash, accounts[1]);});
signature = signature.substr(0, 130) + (signature.substr(130) == "00" ? "1b" : "1c");
Best Answer
Have a look at this: Sign message with web3 and verify with openzeppelin-solidity ECDSA.sol
Notice this little switcheroo:
Hope it helps.