Search code examples
pythonethereumsolidityweb3py

unable to verify a signed message in solidity


I am using this tutorial : https://web3py.readthedocs.io/en/stable/web3.eth.account.html#sign-a-message

##  in web3py

sig = Web3.soliditySha3( [uint256, address], [tokens, contractaddress] ) ## used below also
output: HexBytes('0x3efb3cf4e41109f6f1f998401d02dbe894719a8806f45e79a5fab7d4799f00bb')
from eth_account.messages import encode_defunct
msg = sig.hex()
message = encode_defunct(text=msg)
signed_message = w3.eth.account.sign_message(message, private_key=private_key)
signed_message

SignedMessage(messageHash=HexBytes('0x4c0c7077f770069785167e8b7451d63fad1e858ef5251b239eb0781c314000d2'), r=5665764915496639843348851536709769469640799172147461427941649091688603148258, s=48121517563314450138554207713326165180739298862566159641495137088718857193470, v=27, signature=HexBytes('0x0c86b594baa5bb06a0f4054ffdf3896377cfb757d42dcaeacf0241d96a4d5fe26a63d0514338ec600c89ee808dc088e7b3aadc55b9f5d86685d3ff212e2e47fe1b'))

When I pass these params such as signed_message.messageHash and signed_message.signature to the recoversigner(mentioned below) function in the smart contract. It gives the correct address. But when I am passing arguments tokens, contractaddress (used above) to the solidity keccak256(abi.encodePacked(token,address(this))) the message that is genarated is different from the signed_message.messageHash and in turn I get a different address as output from ecrecover.

I checked what is the output of keccak256(abi.encodePacked(..)). I found It is same as sig value

What I have done:

  1. Make an ERC20 token.

  2. setup the channel

My goal:

  1. sign messages (with web3py)

  2. validate the signed message with solidity.

Like this:

user will pass some arguments : like contract address and number of tokens along with the signed message. and I will validate it with

    function splitSignature(bytes memory sig)
        pure
        internal
        returns (uint8 v, bytes32 r, bytes32 s)
    {
        require(sig.length == 65 , "invalid length");

        assembly {
            r := mload(add(sig, 32))
            s := mload(add(sig, 64))
            v := byte(0, mload(add(sig, 96)))
        }
        if (v < 27) {
            v += 27;
        }

        require(v == 27 || v == 28 , "value of v ");
        return (v, r, s);
    }

    function recoverSigner(bytes32 message, bytes memory sig)
        internal 
        pure
        returns (address)
    {
        (uint8 v, bytes32 r, bytes32 s) = splitSignature(sig);
        return ecrecover(message, v, r, s);
    }

bytes32 message = prefixed(keccak256(abi.encodePacked(token,address(this))));

This is not working properly. I am not getting the correct address as output.

Please help..


Solution

  • I'm not entirely clear on what you're trying to do, but an important clarification to make here is that Web3's eth.sign (and its underlying JSON-RPC call eth_sign) do not sign a simple hash. It adds a prefix string, with a length embedded.

    See the eth_sign docs:

    The sign method calculates an Ethereum specific signature with: sign(keccak256("\x19Ethereum Signed Message:\n" + len(message) + message))).

    By adding the prefix to the passed message, your client makes sure that you aren't using eth_sign to sign a transaction (presumably, by accident, or due to attack).

    Calling recoverHash(sig, ...) is sort of nonsensical. Although the variable is called "sig", it is just a hash of some data -- it hasn't been signed by anything.


    If you are designing a new message signing scheme, I highly recommend using some kind of standard. If you want the simplest possible option, you could use EIP-191's "Version 0" message.

    For that, you can use eth-account's encode_intended_validator(), which would look something like:

    from eth_account.messages import encode_intended_validator
    message = encode_intended_validator(YOUR_CONTRACT_ADDR, sig)
    signed_message = w3.eth.account.sign_message(message, private_key=private_key)