Solidity Signature Verification – Web3 Generated Signatures

signaturesoliditytesttruffle

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:

signature = signature.substr(0, 130) + (signature.substr(130) == "00" ? "1b" : "1c");

Hope it helps.

Related Topic