# 如何针对嵌套结构体进行EIP712签名? **Published by:** [MHXW](https://paragraph.com/@mhxw/) **Published on:** 2023-11-29 **URL:** https://paragraph.com/@mhxw/eip712 ## Content 本篇文章会给大家介绍如何对嵌套结构体进行签名(EIP712 TypedData Encoding With Nested Array of Structs)。本文案例是结构体中嵌套其他结构体数组,与结构体中嵌套其他结构体处理方式一样。 如果你对EIP712相关概念和知识了解不多,可以先阅读下面这篇文章。 https://mirror.xyz/xyyme.eth/cJX3zqiiUg2dxB1nmbXbDcQ1DSdajHP5iNgBc6wEZz4 在某个项目中,需要将下面代码块中的StrategyRequest 结构体进行EIP712签名,不过在测试中,发现和使用ethers v6 生成的structHash 不一致,最后是如何排查解决的呢? enum OptionType { LONG_CALL, LONG_PUT, SHORT_CALL, SHORT_PUT } struct Option { uint256 positionId; // underlying asset address address underlying; // option strike price (with 18 decimals) uint256 strikePrice; int256 premium; // option expiry timestamp uint256 expiryTime; // order size uint256 size; // option type OptionType optionType; bool isActive; } struct Future { uint256 positionId; // underlying asset address address underlying; // (with 18 decimals) uint256 entryPrice; // future expiry timestamp uint256 expiryTime; // order size uint256 size; bool isLong; bool isActive; } struct StrategyRequest { address admin; uint256 timestamp; uint256 mergeId; CollateralInfo[] collaterals; Option[] option; Future[] future; } 一、首先查看结构体typehash是否正确。由于该结构体是多层嵌套不同结构体,按照EIP712标准,当struct 里面还有 struct 的情况,内部struct 的签名对象类型哈希中的结构体对象名称需要按照字母从a到z拼接;因此StrategyRequest结构体中的内部结构体排序之后为:1:CollateralInfo,2:Future,3:Option;代码如下://keccak256("StrategyRequest(address admin,uint256 timestamp,uint256 mergeId,CollateralInfo[] collaterals,Option[] option,Future[] future)CollateralInfo(address collateralToken,uint256 collateralAmount)Future(uint256 positionId,address underlying,uint256 entryPrice,uint256 expiryTime,uint256 size,bool isLong,bool isActive)Option(uint256 positionId,address underlying,uint256 strikePrice,int256 premium,uint256 expiryTime,uint256 size,uint256 optionType,bool isActive)"); bytes32 public constant STRATEGY_REQUEST_TYPE_HASH = 0xc9571297085db7b4ed482bfb5dc48ceb0aa85911a4dd114186601baab392814c; 二、生成其他结构体的typehash //keccak256("CollateralInfo(address collateralToken,uint256 collateralAmount)"); bytes32 public constant COLLATERAL_INFO_TYPE_HASH = 0x9ad6f6e9b22cfe4c61d51d75600afaaae0fe0f7dbc24f035d03f68b7f87920fa; //keccak256("Future(uint256 positionId,address underlying,uint256 entryPrice,uint256 expiryTime,uint256 size,bool isLong,bool isActive)"); bytes32 public constant FUTURE_TYPE_HASH = 0xcdff66689589cd15845093f3be135b778815fc2b8dfa35ff5112e645191afe86; //keccak256("Option(uint256 positionId,address underlying,uint256 strikePrice,int256 premium,uint256 expiryTime,uint256 size,uint256 optionType,bool isActive)"); bytes32 public constant OPTION_TYPE_HASH = 0xbc63504838568be333400315a4bfe079d052fe27fe59b4bdac11192ccbca3e47; 三、结构体hash 在一个结构体中里面没有其他结构体情况下,structHash生成采用以下方式: struct StrategyRequest { address admin; uint256 timestamp; uint256 mergeId; } function _structHash(StrategyRequest memory strategy) internal pure returns (bytes32) { bytes32 structHash = keccak256( abi.encode( Constants.STRATEGY_REQUEST_TYPE_HASH, strategy.admin, strategy.timestamp, strategy.mergeId ) ); return structHash; } 在一个结构体中里面有其他结构体情况下,structHash生成采用以下方式: 1、首先把其他结构体的每个字段进行整体abi.encode之后进行keccak256 2、如果涉及内部字段是数组结构体参数,需要先实现第1步,然后将所有的keccak256 hash 进行abi.encode 之后再进行keccak256 3、将keccak256后的变量作为参数放置到structHash 中的abi.encode中 4、最后再进行keccak256之后即可生成structHash function _structHash(StrategyTypes.StrategyRequest memory strategy) internal pure returns (bytes32) { bytes32 structHash = keccak256( abi.encode( Constants.STRATEGY_REQUEST_TYPE_HASH, strategy.admin, strategy.timestamp, strategy.mergeId, _hashCollateral(strategy.collaterals), _hashOption(strategy.option), _hashFuture(strategy.future) ) ); return structHash; } function _hashStrategy(StrategyTypes.StrategyRequest memory strategy) internal view returns (bytes32) { bytes32 structHash = _structHash(strategy); return _hashTypedDataV4(structHash); } function _hashCollateral(StrategyTypes.CollateralInfo[] memory collaterals) internal pure returns (bytes32) { uint256 len = collaterals.length; bytes32[] memory collateralsHashes = new bytes32Unsupported embed; for (uint256 i; i < len; ) { collateralsHashes[i] = keccak256(abi.encode(Constants.COLLATERAL_INFO_TYPE_HASH, collaterals[i])); unchecked { ++i; } } bytes32 collateralsHash = keccak256(abi.encodePacked(collateralsHashes)); return collateralsHash; } function _hashOption(StrategyTypes.Option[] memory options) internal pure returns (bytes32) { uint256 len = options.length; bytes32[] memory optionsHashes = new bytes32Unsupported embed; for (uint256 i; i < len; ) { optionsHashes[i] = keccak256( abi.encode( Constants.OPTION_TYPE_HASH, options[i].positionId, options[i].underlying, options[i].strikePrice, options[i].premium, options[i].expiryTime, options[i].size, options[i].optionType, options[i].isActive ) ); unchecked { ++i; } } bytes32 optionsHash = keccak256(abi.encodePacked(optionsHashes)); return optionsHash; } function _hashFuture(StrategyTypes.Future[] memory futures) internal pure returns (bytes32) { uint256 len = futures.length; bytes32[] memory futuresHashes = new bytes32Unsupported embed; for (uint256 i; i < len; ) { futuresHashes[i] = keccak256( abi.encode( Constants.FUTURE_TYPE_HASH, futures[i].positionId, futures[i].underlying, futures[i].entryPrice, futures[i].expiryTime, futures[i].size, futures[i].isLong, futures[i].isActive ) ); unchecked { ++i; } } // 这里使用abi.encodePacked和 abi.encode都可以,因为里面参数为bytes32数组,打包之后效果一样 bytes32 futuresHash = keccak256(abi.encodePacked(futuresHashes)); return futuresHash; } 四,使用ethers输出日志和链上结果比较const domain = { name: "XXX Protocol", version: "1", chainId: 10, verifyingContract: "0x1f9090aae28b8a3dceadf281b0f12828e676c326", }; const types = { StrategyRequest: [ { name: "admin", type: "address" }, { name: "timestamp", type: "uint256" }, { name: "mergeId", type: "uint256" }, { name: "collaterals", type: "CollateralInfo[]" }, { name: "option", type: "Option[]" }, { name: "future", type: "Future[]" }, ], CollateralInfo: [ { name: "collateralToken", type: "address" }, { name: "collateralAmount", type: "uint256" }, ], Future: [ { name: "positionId", type: "uint256" }, { name: "underlying", type: "address" }, { name: "entryPrice", type: "uint256" }, { name: "expiryTime", type: "uint256" }, { name: "size", type: "uint256" }, { name: "isLong", type: "bool" }, { name: "isActive", type: "bool" }, ], Option: [ { name: "positionId", type: "uint256" }, { name: "underlying", type: "address" }, { name: "strikePrice", type: "uint256" }, { name: "premium", type: "int256" }, { name: "expiryTime", type: "uint256" }, { name: "size", type: "uint256" }, { name: "optionType", type: "uint256" }, { name: "isActive", type: "bool" }, ], }; const data = { admin: "0x45ee97cD5f227EdC94376A71a094F1D05ad0d151", timestamp: 1701092243, mergeId: 6, collaterals: [ { collateralToken: "0x6495ce3e8808d7f4edf7e6e61e97099473b7a962", collateralAmount: "8", }, { collateralToken: "0x6495ce3e8808d7f4edf7e6e61e97099473b7a962", collateralAmount: "8", } ], future: [ { positionId: 0, underlying: "0x650c92218306ef82c9ebdb5297ff2288f05c24a8", entryPrice: "19200000000000000000000", expiryTime: "1703836800", size: "2000000000000000000", isLong: true, isActive: true, }, ], option: [ { positionId: 0, underlying: "0x650c92218306ef82c9ebdb5297ff2288f05c24a8", strikePrice: "19200000000000000000000", premium: "44000000000000000000", expiryTime: "1703836800", size: "2000000000000000000", optionType: 0, isActive: true, }, ], }; let strategyManagerAddr = config[network.name].strategyManager; const eip712FacetInterface = await ethers.getContractAt("EIP712Facet", strategyManagerAddr); console.log("hashDomain:\t", ethers.TypedDataEncoder.hashDomain(domain)); // let structHash = await eip712FacetInterface.structHash(data); // console.log("structHash1:\t", structHash.toString()); const hashStruct = ethers.TypedDataEncoder.hashStruct("StrategyRequest", types, data); console.log("structHash2:\t", hashStruct); // let result = await eip712FacetInterface.hashStrategy(data); // console.log("typeDataHash1:\t", result.toString()); const typeDataHash = ethers.TypedDataEncoder.hash(domain, types, data); console.log("typeDataHash2:\t", typeDataHash); 参考链接 https://eips.ethereum.org/EIPS/eip-712 ## Publication Information - [MHXW](https://paragraph.com/@mhxw/): Publication homepage - [All Posts](https://paragraph.com/@mhxw/): More posts from this publication - [RSS Feed](https://api.paragraph.com/blogs/rss/@mhxw): Subscribe to updates