1

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?

0

The message you send to the contract is not the same you are encoding in javascript orderTotal + tokenAddr is not the same as abi.encodePacked(orderTotal, tokenContract).

Either use the same function you use in the contract.

function getMessageHash(uint256 orderTotal, address tokenContract) public pure returns (bytes32) {
    bytes32 messageHash = keccak256(abi.encodePacked(orderTotal, tokenContract));
    returnmessageHash;
}

And call that from javascript verifyTest.getMessageHash(orderTotal, tokenAddr).

Another option is to call soliditysha3 from web3 v1.0 that will properly format the data.

  • Thanks very much for your help! I have managed to get good results from your first suggestion. Your answer is almost there—I had to prepend the Ethereum Signed Message prefix before returning the hash, but that signature results in a good address recovery. This method appears quick on a local testnet, but would I be correct in thinking there would be more latency when dealing with main/rinkeby/kovan nets, when computing the hash? – Craig Sep 10 '18 at 19:59
  • Making a call to your contract adds latency. How long the delay will be depends on the configuration of your node, if you use infura+vm provider it will be minimum (once the contract bytecode is cached the execution will be locally), but it will still be higher than calling soliditySha3(). – Ismael Sep 10 '18 at 21:59
  • Thanks for the clarification. I will study the web3 v1.0 API and look to get a local data concat working. I will update my question to show the solutions you have guided me to. Thanks again for your help. – Craig Sep 10 '18 at 22:26

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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