can someone help me to find out what is wrong with my solidity code? (I'm trying to implement usage of the signatures and EIP712)

(ECDSA and _hashTypedDataV4 are from OpenZeppelin: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/release-v4.7/contracts/utils/cryptography/ECDSA.sol and https://github.com/OpenZeppelin/openzeppelin-contracts/blob/release-v4.7/contracts/utils/cryptography/draft-EIP712.sol)

string public constant AUTHORIZER_SIGNATURE = "Authorizer(string websiteDomain,uint256 currentBlock,bytes32 uniqueToken)";
bytes32 private constant _AUTHORIZER_SIGNATURE_TYPEHASH = keccak256(abi.encodePacked(keccak256(bytes(AUTHORIZER_SIGNATURE))));

function _getDigest(string memory websiteDomain, uint256 signCurrentBlock, bytes32 uniqueToken) private view returns (bytes32) {
    return _hashTypedDataV4(
        _getStructHashFromPayloadMsg(websiteDomain, signCurrentBlock, uniqueToken)

function _getStructHashFromPayloadMsg(string memory websiteDomain, uint256 signCurrentBlock, bytes32 uniqueToken) private pure returns (bytes32) {
    return keccak256(
            _AUTHORIZER_SIGNATURE_TYPEHASH, // "message(string websiteDomain,uint256 currentBlock,bytes32 uniqueToken)"

function DEBUG_recover(string memory websiteDomain, uint256 signCurrentBlock, bytes32 uniqueToken, bytes memory signature) external view returns (address) {
    return ECDSA.recover(_getDigest(websiteDomain, signCurrentBlock, uniqueToken), signature);

On the node side, I write something like:

    method: 'eth_signTypedData_v4',
    params: ["0xb2AF24e5249479C3160b10b15eDc1192dc1171C8",{domain:{chainId:11155111,name:"my-domain",verifyingContract:"my-deployed-contract",version:"1"},message:{websiteDomain:"a-domain",currentBlock:1,uniqueToken:"0x31323334353637383930313233343536373839"},types:{EIP712Domain:[{name:"name",type:"string"},{name:"version",type:"string"},{name:"chainId",type:"uint256"},{name:"verifyingContract",type:"address"}],Authorizer:[{name:"websiteDomain",type:"string"},{name:"currentBlock",type:"uint256"},{name:"uniqueToken",type:"bytes32"}]},primaryType:"Authorizer"}];,
    from: '0xb2AF24e5249479C3160b10b15eDc1192dc1171C8',
  (err, res) => {
    if (err) {
    } else {

await instance.DEBUG_recover('a-domain', 1, '0x31323334353637383930313233343536373839', '0x6b10a4e4010eec912205e604dafcff30a4204dd50f158ffd24d3f95a5b85dd2a2db3af256aac3a04a06c4e45eb606bfc3f3b7a4e0be24b796794a675f75c11411b');

So basically, I expect DEBUG_recover to return me: 0xb2AF24e5249479C3160b10b15eDc1192dc1171C8 (a testnet wallet address), but I receive another address instead...

(first I was using Ganache, but it seems to mess with the chainId... So I try with Sepolia instead, but it doesn't work either...)

Could this be related to my attribute "uniqueToken" being of type bytes32? (I got it's "0x31323334353637383930313233343536373839" value with a command like web3.utils.asciiToHex("<another-value>")

2 Answers 2


Ok, so actually the problem was that I used an incorrect signature first, then when I try with string and bytes instead of bytes32 for the uniqueToken, I got another problem due to the dynamic types I guess...

I solved by problems just using correctly a bytes32 (correctly generated), so no more than 1 dynamic types value and it's now working as I expected.

  • When you call th_signTypedData_v4 params should be a string. You are passing objects but you should stringify them with JSON.stringify

  • this is the openzeppelin ECDSA.recover function:

    function recover(
          bytes32 hash,
          uint8 v,
          bytes32 r,
          bytes32 s
      ) internal pure returns (address) {
          (address recovered, RecoverError error) = tryRecover(hash, v, r, s);
          return recovered;

it expects 4 args. But you are passing

return ECDSA.recover(_getDigest(websiteDomain, signCurrentBlock, uniqueToken), signature);

_getDigest is returning keccask256 hash which is bytes32 and you are passing signature. So I think you are passing wrong arguments to recover function.

  • On metamask docs, it seems that I should use a JSON.stringify on the second element of the params array, but I'm using the truffle web3 object and it doesn't seems to handle the stringified param: If I do: params[1] = JSON.stringify(params[1]); And try to get a signature with th_signTypedData_v4, I got the following error: "ProviderError: Cannot read properties of undefined (reading 'EIP712Domain')" (also, if I remove an attribute like "primaryType", the error ask for it, so I guess this version prefer to get an object...) Commented Oct 31, 2022 at 22:43
  • For the ECDSA.recover, there is also another signature (on the 4.7 release at least): function recover(bytes32 hash, bytes memory signature) that I try to use, following the comment at this line: github.com/OpenZeppelin/openzeppelin-contracts/blob/… Commented Oct 31, 2022 at 22:43
  • (also I tried to use both metamask and truffle console with the same private key, and I obtain the same signature with both. Metamask need a JSON.stringify to work, truffle need object to work, but same result...) So the problem is either the solidity contract or the payload I try to sign... I also tried replacing the bytes32 type with string, and using different alternative in solidity (abi.encode / abi.encodePacked / bytes.concat / typecasting...), none of my results is the correct address. Commented Nov 1, 2022 at 12:03

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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