2

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(
        abi.encode(
            _AUTHORIZER_SIGNATURE_TYPEHASH, // "message(string websiteDomain,uint256 currentBlock,bytes32 uniqueToken)"
            keccak256(bytes(websiteDomain)),
            signCurrentBlock,
            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:

web3.currentProvider.send(
  {
    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) {
      console.error(err);
    } else {
      console.log(res);
    }
  }
);

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

3

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.

1
  • 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);
          _throwError(error);
          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.

3
  • 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

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