Verifying TypedData signature in Solidity using _hashTypedDataV4

I have a method that receives an uint256[ ] parameter. I am able to sign the message using etherjs and and create a signature. But inside my Contract when I try to verify the Signature I get an incorrect recovered signer address.
I tried the above with just a uint256 (not array) and the Verification worked. When I try it with an array it does not work.
I would like to know how to encode the method parameters correctly. (if it is an uint256 array)

The message property of my ERC712 is like this:

message: {
      tokenIds: [1,2,3],
      uri: "abcd.com"
    },

Inside my SmartContract, I am verifying the data like this:

function pauseTokens(uint256[] memory tokenIds, string memory uri, bytes signature) public onlyRole(ABC) {

 bytes32 digest = _hashTypedDataV4(
      keccak256(
        abi.encode(
          keccak256("MyType(uint256[] tokenIds, string uri"),
          tokenIds,                  // IS THIS the CORRECT way to encode uint256[ ] ?
          keccak256(bytes(uri)),
        ),
     )
 );
 
address recoveredSigner = ECDSA.recover(digest, signature);   // Incorrect signer :-( 
}

No.

If I remember correctly you should be probably be doing that:

 bytes32 digest = _hashTypedDataV4(
      keccak256(
        abi.encode(
          keccak256("MyType(uint256[] tokenIds, string uri"),
          keccak256(tokenIds),
          keccak256(bytes(uri)),
        ),
     )
 );

Please check to confirm. For the record, the de-factor reference for encoding is possibly this js library.

Hi Amxx. Thank for you reply.
Unfortunately your solution doesnt work.

However, I was able to get the code to work with some Trial and Error.
The correct way to encode any array is:
keccak256(abi.encodePacked(tokenIds))

So my updated code is as follows:

function pauseTokens(uint256[] memory tokenIds, string memory uri, bytes signature) public onlyRole(ABC) {

 bytes32 digest = _hashTypedDataV4(
      keccak256(
        abi.encode(
          keccak256("MyType(uint256[] tokenIds, string uri"),
          keccak256(abi.encodePacked(tokenIds)),                  // THIS is the CORRECT way
          keccak256(bytes(uri)),
        ),
     )
 );
 
address recoveredSigner = ECDSA.recover(digest, signature);   // Correct signer :-) 
}

Hi @mario.christopher, which environment you are using? I tried on remix(hardhat) still i got wrong address.

Hi h_k,
My envt is:

  • VS Code
  • Truffle
  • Solc 0.8.14
  • Rinkeby
1 Like

@mario.christopher could you share your validation method on solidity. Here is mine

    function _validate(LibOrder.Order memory order, bytes memory signature) external view returns (address) {
        bytes32 hash = LibOrder.hash(order);
        return _hashTypedDataV4(hash).recover(signature);
    }

I'm using these libraries

import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import "@openzeppelin/contracts/utils/cryptography/draft-EIP712.sol";

Hi @h_k
Please see my validation and recovery code in my resolved/corrected "Solution" posted in this same thread above - marked as the Solution.
function pauseTokens( ...

I'm using this library:

import "@openzeppelin/contracts/utils/cryptography/draft-EIP712.sol";

(the above library already imports)

import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";

Hi, @mario.christopher
How can you get signature from frontnend?
did you use _signTypedData of ethers?

Yes, here's the simplified code that I used:

export async function signTypedeData(payload: any, privateKey: string, typeName: string) {
  const wallet = new ethers.Wallet(privateKey);
  const signature = await wallet._signTypedData( ... );
  return signature;
}