You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Solidity中如何为带嵌套结构体的EIP712类型数据定义TYPEHASH?

EIP-712嵌套结构体与动态数组的TYPEHASH定义及签名验证修正

问题根源

你当前的实现未遵循EIP-712对嵌套结构体和动态数组的哈希规则,这是签名恢复地址错误的核心原因:

  • 嵌套结构体需要单独定义TYPEHASH,且编码时需传入该结构体实例的哈希值,而非直接传入结构体
  • 动态数组不能直接编码,必须先对数组内每个元素的哈希值做拼接后再整体哈希

正确实现步骤

1. 定义所有结构体的独立TYPEHASH

每个结构体(包括嵌套的Fee)都需要单独定义TYPEHASH,字符串格式必须严格匹配结构体字段顺序和类型:

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

2. 处理动态数组的哈希计算

对于Fee[]这类动态数组,需遍历每个元素计算哈希,再将所有哈希值拼接后做整体哈希:

function hashFees(Fee[] calldata fees) public pure returns (bytes32) {
    bytes32[] memory feeHashes = new bytes32[](fees.length);
    for (uint256 i = 0; i < fees.length; i++) {
        feeHashes[i] = keccak256(abi.encode(FEE_TYPEHASH, fees[i].recipient, fees[i].value));
    }
    return keccak256(abi.encodePacked(feeHashes));
}

3. 修正上层结构体的编码逻辑

encodeRequest函数需要传入嵌套结构体的哈希值、动态数组的哈希值,而非直接传入结构体或数组实例:

function encodeRequest(Request calldata request) public pure returns (bytes memory) {
    return abi.encode(REQUEST_TYPEHASH, request.to, request.from, hashFees(request.fees));
}

完整修正后的合约代码

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)");

    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 pure returns (bytes memory) {
        return abi.encode(REQUEST_TYPEHASH, request.to, request.from, hashFees(request.fees));
    }

    function hashFees(Fee[] calldata fees) public pure returns (bytes32) {
        bytes32[] memory feeHashes = new bytes32[](fees.length);
        for (uint256 i = 0; i < fees.length; i++) {
            feeHashes[i] = keccak256(abi.encode(FEE_TYPEHASH, fees[i].recipient, fees[i].value));
        }
        return keccak256(abi.encodePacked(feeHashes));
    }
}

关键规则说明

  • 嵌套结构体:每个嵌套结构体必须单独定义TYPEHASH,编码时先计算该结构体实例的哈希(keccak256(abi.encode(结构体TYPEHASH, 字段值...))),再将哈希值传入上层结构体编码
  • 动态数组:先对数组内每个元素计算哈希(遵循结构体哈希规则),将所有哈希值组成数组后,执行keccak256(abi.encodePacked(哈希数组))得到最终数组哈希值,再传入上层编码
  • TYPEHASH格式:字符串必须严格遵循结构体名(类型1 字段名1,类型2 字段名2,...)格式,字段顺序、类型必须与合约定义完全一致,不能有多余空格

内容的提问来源于stack exchange,提问作者ethadvocate123

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.05 22:45:27