I am wondering what the correct way is to define the TYPEHASH for a nested struct data structure for the EIP-712. I am trying to do this, as I want to retrieve the signer of a request struct using ECDSA and the EIP-712 standard for hashing structs.
This is the contract:
import "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
contract SignatureChecker is EIP712 {
using ECDSA for bytes32;
struct Fee {
address recipient;
uint256 value;
}
struct Request {
address to;
address from;
Fee[] fees;
}
bytes32 public TYPEHASH = keccak256("Request(address to,address from, Fee[] fees)");
constructor() EIP712("SignatureChecker", "1") {}
function verify(
Request calldata request,
bytes calldata signature,
address supposedSigner
) external view returns (bool) {
return recoverAddress(request, signature) == supposedSigner;
}
function recoverAddress(
Request calldata request,
bytes calldata signature
) public view returns (address) {
return _hashTypedDataV4(keccak256(encodeRequest(request))).recover(signature);
}
function encodeRequest(Request calldata request) public view returns (bytes memory) {
return abi.encode(TYPEHASH, request.to, request.from, request.fees);
}
}
I just want to make sure that I am encoding the request correctly in the encodeRequest function. Unfortunately I could not find anything on how to create a typehash of a nested struct. Is the way I am creating the typehash correct?
When I tried out the verify function without the fees property and the different TYPEHASH without the fee, it worked completely fine. However when I try to retrieve the address of a signature of the request struct with the fees array, it returns a wrong address.
I have also seen an example where someone tried to do this:
bytes32 public constant TYPEHASH = keccak256("Request(address to,address from, Fee[] fees)Fee(address recipient, uint256 value)");
Unfortunately it also produces a wrong address.
After doing quite a lot of research (including reading the entire EIP-712), I could craft a solution, which works:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
import "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
contract SignatureChecker is EIP712 {
using ECDSA for bytes32;
struct Fee {
address recipient;
uint256 value;
}
struct Request {
address to;
address from;
Fee[] fees;
}
bytes32 public constant FEE_TYPEHASH = keccak256("Fee(address recipient,uint256 value)");
bytes32 public constant REQUEST_TYPEHASH =
keccak256(
"Request(address to,address from,Fee[] fees)Fee(address recipient,uint256 value)"
);
constructor() EIP712("SignatureChecker", "1") {}
function verify(
Request calldata request,
bytes calldata signature,
address signer
) external view returns (bool) {
return recoverAddressOfRequest(request, signature) == signer;
}
function recoverAddressOfRequest(
Request calldata request,
bytes calldata signature
) public view returns (address) {
return _hashTypedDataV4(keccak256(encodeRequest(request))).recover(signature);
}
function recoverAddressOfFee(
Fee calldata fee,
bytes calldata signature
) public view returns (address) {
return _hashTypedDataV4(keccak256(encodeFee(fee))).recover(signature);
}
function encodeFee(Fee calldata fee) public pure returns (bytes memory) {
return abi.encode(FEE_TYPEHASH, fee.recipient, fee.value);
}
function encodeRequest(Request calldata request) public pure returns (bytes memory) {
bytes32[] memory encodedFees = new bytes32[](request.fees.length);
for (uint256 i = 0; i < request.fees.length; i++) {
encodedFees[i] = keccak256(encodeFee(request.fees[i]));
}
return
abi.encode(
REQUEST_TYPEHASH,
request.to,
request.from,
keccak256(abi.encodePacked(encodedFees))
);
}
}
The main problem was, that in order for this to work, you have to encode the every Fee element inside of the Request struct individually, and hash the resulting array to append it to the encoded request.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With