I am busy trying to get my head around recovery of an address using ecrecover(). The I'm having trouble recovering the correct address when providing signed data to the contract.
The contract that I'm testing is shown below:
pragma solidity ^0.4.24;
contract VerifyTest {
// https://ethereum.stackexchange.com/a/15911
function verifyMessage(bytes32 messageHash, uint8 v, bytes32 r, bytes32 s) private view returns (bool) {
bytes memory hashPrefix = "\x19Ethereum Signed Message:\n32";
bytes32 prefixedHash = keccak256(abi.encodePacked(hashPrefix, messageHash));
return ecrecover(prefixedHash, v, r, s) == msg.sender;
}
function testBuyOrder(uint256 orderTotal, address tokenContract, uint8 v, bytes32 r, bytes32 s) public view returns (bool) {
bytes32 messageHash = keccak256(abi.encodePacked(orderTotal, tokenContract));
return verifyMessage(messageHash, v, r, s);
}
}
When compiled, this generates the following ABI object:
[
{
"constant": true,
"inputs": [
{
"name": "orderTotal",
"type": "uint256"
},
{
"name": "tokenContract",
"type": "address"
},
{
"name": "v",
"type": "uint8"
},
{
"name": "r",
"type": "bytes32"
},
{
"name": "s",
"type": "bytes32"
}
],
"name": "testBuyOrder",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
}
]
I can successfully call the testBuyOrder()
function using the small snippet of Javascript below:
// Trying to keep it concise; assume we have a functioning web3 instance...
const signTestAddr = "[ADDRESS_OF_DEPLOYED_CONTRACT]";
const signTestABI = [...]; // ABI quoted above...
testSignMessage = () => {
console.log(web3.version.api);
let tokenAddr = "0x128Df2a07Dc41E034bD9a3CEaddDc0341250a6C8";
let verifyTest = web3.eth.contract(signTestABI).at(signTestAddr);
let orderTotal = web3.fromDecimal(100000000);
console.log(orderTotal);
let testHash = web3.sha3(orderTotal + tokenAddr);
signHashedMessage(testHash, (error, signature) => {
if(error === null) {
signature = signature.substring(2);
let r = '0x' + signature.substring(0, 64);
let s = '0x' + signature.substring(64, 128);
let v = '0x' + signature.slice(128, 130);
let vDec = parseInt(v, 16);
// Call the function on our contract here...
verifyTest.testBuyOrder(100000000, tokenAddr, vDec, r, s, (testErr, result) => {
if(testErr === null) {
console.log("Success: " + result);
} else {
console.log("Error: " + testErr);
}
});
} else {
console.log("error: " + error);
}
})
};
The output from the javascript above is:
0.20.3
0x5f5e100
Success: false
The above code successfully calls the contract, but I can't see why the call to testBuyOrder()
always returns false.
Edit0: In addition, I apologise for the seemingly duplicated question, but I have attempted to apply the wisdom of others' answers to no avail.
Edit1:
Ismael's first suggestion was to include hashing functions in the contract itself which would ensure that the data is being correctly concatenated and formatted before being hashed. Ismael's function had to be slightly modified in order to get the correct hash to be signed.
The working solution to that function is below:
function getMessageHash(uint256 orderTotal, address tokenContract) public pure returns (bytes32) {
bytes memory hashPrefix = "\x19Ethereum Signed Message:\n32";
bytes32 messageHash = keccak256(abi.encodePacked(orderTotal, tokenContract));
return keccak256(abi.encodePacked(hashPrefix, messageHash));
}
Edit2:
Having explored the use of Web3 v1.0, I am still struggling to recover the sender address in the contract.
My updated client code looks as follows:
signHashedMessage = (messageHash, callback) => {
web3.eth.getCoinbase().then((coinbase) => {
web3.eth.sign(prefixHashedData(messageHash), coinbase, callback);
});
};
prefixHashedData = (messageHash) => {
let msgPrefix = "\x19Ethereum Signed Message:\n32";
return web3.utils.soliditySha3(msgPrefix, messageHash);
};
testSignMessage = () => {
let verifyTest = new web3.eth.Contract(signTestABI, signTestAddr);
let tokenAddr = "0x128Df2a07Dc41E034bD9a3CEaddDc0341250a6C8";
let orderTotal = 100000000;
let testHash = web3.utils.soliditySha3(orderTotal, tokenAddr);
signHashedMessage(testHash, (error, signature) => {
if(error === null) {
signature = signature.substring(2);
let r = '0x' + signature.substring(0, 64);
let s = '0x' + signature.substring(64, 128);
let v = '0x' + signature.slice(128, 130);
let vDec = parseInt(v, 16);
// Call the function on our contract here...
let testBuyOrderRes = verifyTest.methods.testBuyOrder(orderTotal, tokenAddr, vDec, r, s);
testBuyOrderRes.call((callError, callResult) => {
if (callError === null) {
console.log("Verified: " + callResult);
} else {
console.log("Error: " + callError);
}
});
} else {
console.log("error: " + error);
}
});
};
I have added signHashedMessage()
and prefixHashedMessage()
for completeness to show my working.
To verify prefixHashedData()
, I added getMessageHash()
, described above, to my contract. Calling getMessageHash()
returned the same hash as was returned in using web.utils.soliditySha3()
as Ismael described it would.
The new problem that I am having with the Web3 v1.0 code above; when verifying the signature on the contract, by calling testBuyOrder()
, the function is returning false
.
Can anyone see the mistake I am making?