0

I use these contract functions to verify signed message:

function verify(
        address to,
        uint256 amount,
        uint256 nonce,
        bytes memory signature
    ) public view returns (bool) {
        address from = owner();

        console.log("from contract");
        bytes32 messageHash = getMessageHash(from, to, amount, nonce);
        console.logBytes32(messageHash);
        bytes32 ethSignedMessageHash = getEthSignedMessageHash(messageHash);
        console.logBytes32(ethSignedMessageHash);
        address addr = recoverSigner(ethSignedMessageHash, signature);
        console.log(addr);

        return recoverSigner(ethSignedMessageHash, signature) == owner();
    }

    function getMessageHash(
        address from,
        address to,
        uint256 amount,
        uint256 nonce
    ) public pure returns (bytes32) {
        return keccak256(abi.encode(from, to, amount, nonce));
    }

    function getEthSignedMessageHash(bytes32 _messageHash)
        public
        pure
        returns (bytes32)
    {
        return
            keccak256(
                abi.encodePacked(
                    "\x19Ethereum Signed Message:\n32",
                    _messageHash
                )
            );
    }

    function splitSignature(bytes memory sig)
        public
        view
        returns (
            uint8,
            bytes32,
            bytes32
        )
    {
        require(sig.length == 65, "Invalid signature length");

        bytes32 r;
        bytes32 s;
        uint8 v;

        assembly {
            r := mload(add(sig, 32))
            s := mload(add(sig, 64))
            v := byte(0, mload(add(sig, 96)))
        }
        console.logUint(v);
        console.logBytes32(r);
        console.logBytes32(s);
        return (v, r, s);
    }

    function recoverSigner(
        bytes32 _ethSignedMessageHash,
        bytes memory _signature
    ) public view returns (address) {
        (uint8 v, bytes32 r, bytes32 s) = splitSignature(_signature);

        return ecrecover(_ethSignedMessageHash, v, r, s);
    }

But when I call it from js, it always gives me "Wrong signature" error, which is require() that checks verify() is equals to true.

To check contract functions I created js script, that mimic contract functionality with ethers.js:

async function info(signer, from, to, amount, nonce) {
        const payload = ethers.utils.defaultAbiCoder.encode(
            ["address", "address", "uint256", "uint256"],
            [from, to, amount, nonce]
        )

        const array = ethers.utils.arrayify(payload)
        const hashed_payload = ethers.utils.keccak256(array) // message hash in contract
        console.log("Message Hash: ", hashed_payload)
        const types = ethers.utils.solidityKeccak256(
            ["string", "bytes32"],
            ["\x19Ethereum Signed Message:\n32", hashed_payload]

        )
        console.log("Eth signed message: ", types) // eth signed message 

        const signature = await signer.signMessage(array);
        console.log("Signature: ", signature)
        const sig = ethers.utils.splitSignature(signature);
        console.log("Signature:", sig);
        console.log("Recovered:", ethers.utils.verifyMessage(ethers.utils.arrayify(payload), sig));
        console.log("++++")

The problem is that information from hardhat console and from js script is identical. Here it is:

// from js:
Message Hash:  0xe0ad70fdc7e7a60160b483c98583ee4a9ed173709bab8e802628279ce64749c8
Eth signed message:  0x030deb535a9f28fcf9d3cc003d7e1889e793f1e53c9abde29b8a6c3a8e04b065
Signature:  0xd7e114deb528f8ca0d0926865febaaf9cf10583f694a9b92aac4abe33a92a7334b441c3c9936767f58d537cc6ab46c6cd5bc92c244afc5522385d3b8f22bc0ee1c
Signature: {
  r: '0xd7e114deb528f8ca0d0926865febaaf9cf10583f694a9b92aac4abe33a92a733',
  s: '0x4b441c3c9936767f58d537cc6ab46c6cd5bc92c244afc5522385d3b8f22bc0ee',
  _vs: '0xcb441c3c9936767f58d537cc6ab46c6cd5bc92c244afc5522385d3b8f22bc0ee',
  recoveryParam: 1,
  v: 28,
  yParityAndS: '0xcb441c3c9936767f58d537cc6ab46c6cd5bc92c244afc5522385d3b8f22bc0ee',
  compact: '0xd7e114deb528f8ca0d0926865febaaf9cf10583f694a9b92aac4abe33a92a733cb441c3c9936767f58d537cc6ab46c6cd5bc92c244afc5522385d3b8f22bc0ee'
}
Recovered: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
++++

from contract
0xe0ad70fdc7e7a60160b483c98583ee4a9ed173709bab8e802628279ce64749c8 // message hash
0x030deb535a9f28fcf9d3cc003d7e1889e793f1e53c9abde29b8a6c3a8e04b065 // eth signed message
28 // v
0xd7e114deb528f8ca0d0926865febaaf9cf10583f694a9b92aac4abe33a92a733 // r
0x4b441c3c9936767f58d537cc6ab46c6cd5bc92c244afc5522385d3b8f22bc0ee // s
0x63087790b2ffbb189b8e36508ed97f1749500432 // recovered address
0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 // address correctly recovered in js, so u can see, they are not equal

With this outputs and code it seems like recoverSigner() with ecrecover() and ethers.utils.verifyMessage() works differently, but this is unlikely. I'm almost sure, there's mistake I just can't see.

Please, help.

1 Answer 1

0

I solved this by changing signature generation on my frontend with ethers.utils.solidityKeccak256() method (twice for functions in this example, if check needed). It seems like ethers.utils.defaultAbiCoder() works differently.

So, the right combination was solidityKeccak256 signature generation on frontend + using abi.encodePacked() in contract functions instead of abi.encode()

Refer to this question to see another example.

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.