EIP-712签名如何核对域分离? 图 1
EIP-712签名如何核对域分离? · 图 1

EIP-712的价值不是把签名“变安全”,而是把一团十六进制哈希变成有类型、有字段的授权消息。真正需要核对的对象是钱包收到的完整typed data:签名域属于谁、消息授权了什么、是否只能用一次。只看弹窗标题或站点图标,仍可能签下有效的恶意授权。

先看一份签名由哪两部分组成

EIP-712数据包含types、primaryType、domain和message。types定义每种结构及字段顺序,primaryType指出本次消息的顶层类型,message提供实际值。domain不是普通说明文字,而是签名哈希的一部分,常见字段如下:

domain字段它限制的范围缺失时要追问
name / version应用与协议版本升级前后的消息会不会混用
chainId目标链同一签名能否搬到另一条链
verifyingContract验证合约另一份合约能否接受相同消息
salt额外命名空间应用是否自定义了隔离规则

domain可以只声明部分字段,所以“钱包展示了EIP-712”不等于以上项目全部存在。核验时应以实际JSON为准。

摘要是怎样算出来的

每个结构先按类型定义计算typeHash,再把字段按规范编码得到hashStruct。最终待签摘要是固定前缀、domainSeparator和顶层消息hashStruct的组合哈希。字段顺序、嵌套类型和数组内容都会影响结果;前端展示若丢掉一个字段,签名本身却仍会覆盖它。

这也是为什么不能把普通abi.encodePacked随意替换进EIP-712流程。结构化编码要求可确定、无歧义地表示类型和值,钱包与合约必须对同一类型图得出同一摘要。

domain separator不负责“一次性”

EIP-712规范明确提供域分离,但没有自动提供nonce和过期时间。应用若希望一份授权只用一次,需要把nonce写进message,并在合约执行后标记或递增;希望授权到期,则要把deadline纳入消息并由验证逻辑检查。仅有chainId和verifyingContract,只能缩小可重放的范围,不能阻止同一合约再次使用签名。

签名前可以按四步检查:先确认当前域名与钱包连接来源;再展开domain核对chainId和合约地址;然后逐项读message中的接收方、额度、资产、nonce与期限;最后判断拒绝签名是否会造成真实损失。恶意页面常用“登录”“领取”遮盖无限额度或第三方spender。

合约端还要验证什么

OpenZeppelin EIP712实现通常先用_hashTypedDataV4(structHash)把消息结构哈希与当前domain separator组合成最终摘要,再用ECDSA.recover恢复EOA签名者,或用SignatureChecker兼容EOA与ERC-1271合约账户。_EIP712Name()_EIP712Version()等getter只用于读取域信息,不负责恢复签名者。即使验证结果正确,也只是第一关。执行函数仍要验证签名者角色、nonce未使用、期限未过、参数处于业务范围,并先消费nonce再进行可能触发回调的外部操作。代理升级还应确认版本变化是否需要改变domain,避免旧签名在新逻辑中获得意外含义。

合约钱包不能只走ecrecover;Safe等账户可能需要ERC-1271合约签名验证。检查授权页面的风险边界可结合智能合约连接检查,交易提交后再用交易回执字段确认真正执行了哪一次调用。