solidity – How to Construct Hash from Address and Uint256 for Ecrecover and Signature Verification

ecrecoverkeccaksignaturesolidityweb3js

I am creating a signature with web3.js form a hashed message that contains two numbers and an address:

const tokenIds = [1,2,3];
const state = 1;
const messageHash = web3.utils.keccak256(tokenIds.length + state + web3.utils.keccak256(fromAddress));
const sign = await web3.eth.accounts.sign(messageHash, signer.privateKey);

await contract.myFn(
  tokenIds,
  sign.v,
  sign.r,
  sign.s,
  {
    from: fromAddress,
  }
);

In my smart contract I then try to reconstruct the hash for message and verify the signature as follow:

function myFn(uint[] calldata tokenIds, uint8 v, bytes32 r, bytes32 s) external {

  require(
    _signerAddress == ecrecover(
      keccak256(abi.encodePacked(
        "\x19Ethereum Signed Message:\n32",


        // messageHash:
        // When I concat those two strings with the address
        // this hash is invalid (not the same as the on from the client). 

        keccak256(abi.encodePacked(
          // uint256 amount
          Strings.toString(tokenIds.length),
          // uint256 state
          Strings.toString(state),
          // address msg.sender is fromAddress
          keccak256(abi.encodePacked(msg.sender))
        ))

      ))
      , v, r, s
    ),
    'mint not allowed'
  );
}

Basically it seems that the string concatenation when recreating messageHash on hash doesn't match. Maybe there is a reliable way to convert the msg.sender (fromAddress) to string?

Best Answer

I figured it out thanks to this brilliant article.

The secret is to use web3.utils.soliditySha3 which will abi encode and hash like Solidity would!

const message = web3.utils.soliditySha3(
  tokenIds.length,
  state,
  fromAddress
);

and in the contract simply do:

keccak256(abi.encodePacked(
  tokenIds.length,
  state,
  msg.sender
))
Related Topic