如何在 Solidity 中使用嵌套结构为 EIP712 类型数据签名定义 TYPEHASH?

eth*_*123 3 ethereum solidity

我想知道为EIP-712 的嵌套结构数据结构定义 TYPEHASH 的正确方法是什么。我正在尝试这样做,因为我想使用 ECDSA 和哈希结构的 EIP-712 标准检索请求结构的签名者。

这是合同:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

我只是想确保在encodeRequest 函数中正确编码请求。不幸的是,我找不到有关如何创建嵌套结构的 typehash 的任何内容。我创建 typehash 的方式正确吗?

当我尝试不带费用属性的验证功能和不带费用的不同 TYPEHASH 时,它工作得很好。但是,当我尝试使用费用数组检索请求结构签名的地址时,它返回错误的地址。

我还看到一个例子,有人试图这样做:

bytes32 public constant TYPEHASH = keccak256("Request(address to,address from, Fee[] fees)Fee(address recipient, uint256 value)");

不幸的是,它也会产生错误的地址。

eth*_*123 5

经过大量研究(包括阅读整个 EIP-712)后,我可以制定一个有效的解决方案:

// 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))
            );
    }
}
Run Code Online (Sandbox Code Playgroud)

主要问题是,为了使其工作,您必须单独编码 Request 结构中的每个 Fee 元素,并对结果数组进行哈希处理以将其附加到编码的请求中。