在 Wake 调试 EIP-712 类型字符串和哈希

  • Ackee
  • 发布于 3天前
  • 阅读 35

本文介绍了使用 Wake 框架测试 EIP-712 类型字符串的技巧,通过 encode_eip712_typeencode_eip712_data 方法,开发者可以精确地检查类型字符串和预签名数据,确保与 Solidity 合约中的哈希计算方式一致,从而避免因类型定义错误导致的签名验证问题,并提供了一些预防签名错误的技巧。

简介:测试类型字符串的技巧

类型哈希中的单个错别字是一个中等严重程度的错误,但是手动验证该哈希既繁琐又容易出错。EIP-712 调试中最耗时的部分是弄清楚实际进入摘要的内容。Wake 的 Struct 基类公开了 encode_eip712_typeencode_eip712_data,因此你可以准确地看到正在哈希的内容,将其与 Solidity 直接比较,并在部署之前确认签名是否匹配。如果你需要复习原始签名与结构化签名,请从在 Wake 中签名数据:原始、结构化和哈希流开始。

测试上下文和前提条件

  • 在 Wake 测试框架内运行测试,以便 StructAccount 和链 fixtures 可用。
  • 使用 Struct 的子类的 dataclasses。字段顺序和字段名称必须与链上验证的 Solidity 结构镜像。
  • 构建一个与合约完全匹配的 Eip712DomainnameversionchainIdverifyingContract 值必须与 Solidity 端相同。
  • 调试失败时,在测试中打印类型字符串和调用跟踪,以快速发现模式或域不匹配。

技术背景:辅助函数的作用

encode_eip712_type 构建规范的类型字符串,包括引用的结构,而不是返回哈希。这种可见性使你可以在调试时检查排序和字段名称。encode_eip712_data 返回馈入最终摘要的打包字节。这些辅助函数一起镜像了 hashTypedData 的 Solidity 端,并使其可以直接断言测试和合约使用相同的 preimage,即使使用嵌套结构或基于代理的验证合约也是如此。

构建你可以检查的类型化结构

基本 Struct 类位于 wake.testing 中。使用 dataclasses 对其进行子类化,并完全镜像 Solidity 名称。当 Python 关键字与 Solidity 不同时,请使用 field(metadata={"original_name": ...}) 来保留链上模式。

from dataclasses import dataclass, field
from wake.testing import *

@dataclass
class Person(Struct):
    name: str
    wallet: Address

@dataclass
class Mail(Struct):
    from_: Person = field(metadata={"original_name": "from"})
    to: Person
    contents: str

mail = Mail(
    from_=Person("Alice", Address(1)),
    to=Person("Bob", Address(2)),
    contents="Hello",
)

print(mail.encode_eip712_type())
## Mail(Person from,Person to,string contents)Person(string name,address wallet)

保持类型字符串对人类可读有助于你及早发现不匹配,例如不正确的数据类型、多余的空格、缺少嵌套结构或重命名的变量。一旦字符串与 Solidity 端匹配,你就可以安全地对其进行哈希处理。

以与 Solidity 相同的方式散列类型化数据

验证了类型字符串后,完全按照合约的方式构建类型哈希和数据字节。下面的代码段镜像了 hashTypedData 中使用的 EIP-712 管道。

type_hash = keccak256(mail.encode_eip712_type().encode())
data = mail.encode_eip712_data()

typed_data_hash = keccak256(abi.encode_packed(type_hash, data))
domain = Eip712Domain(
    name="Mail",
    version="1",
    chainId=chain.chain_id,
    verifyingContract=Address(0x1234),
)

signature = Account.new().sign_structured(mail, domain)

由于 encode_eip712_data 返回馈入摘要的确切字节,因此你可以记录并将其与 Solidity 辅助函数或实时合约调用进行比较,而无需猜测。

针对合约进行交叉检查

端到端断言提供最强的证明。下面的模糊测试以两种方式对相同的结构进行签名,使用手动哈希和高级 sign_structured 辅助函数,并验证两者是否与基本合约上合约的 hashTypedData 实现匹配。

from wake.testing import *
from wake.testing.fuzzing import *
from dataclasses import dataclass, field

from pytypes.src.utils.ERC1967Factory import ERC1967Factory
from pytypes.ext.wake_tests.helpers.EIP712Mock import EIP712Mock

@dataclass
class Person(Struct):
    name: str
    wallet: Address

@dataclass
class Mail(Struct):
    from_: Person = field(metadata={"original_name": "from"})
    to: Person
    contents: str

class Eip712FuzzTest(FuzzTest):
    def __init__(self):
        self._factory = ERC1967Factory.deploy()

    def pre_sequence(self) -> None:
        self._impl = EIP712Mock.deploy()
        self._signer = Account.new()
        self._proxy = EIP712Mock(self._factory.deploy_(self._impl, self._signer).return_value)

    @flow()
    def sign_flow(self, mail: Mail) -> None:
        type_hash = keccak256(mail.encode_eip712_type().encode())
        mail_hash = keccak256(abi.encode_packed(type_hash, mail.encode_eip712_data()))

        for target in (self._impl, self._proxy):
            manual = self._signer.sign_hash(target.hashTypedData(mail_hash))
            structured = self._signer.sign_structured(
                mail,
                Eip712Domain(
                    name=target.NAME(),
                    version=target.VERSION(),
                    chainId=chain.chain_id,
                    verifyingContract=target.address,
                ),
            )
            assert manual == structured

@chain.connect()
def test_eip712_fuzz():
    Eip712FuzzTest().run(100, 100000)

要点:

  • 类型字符串是可见的,因此你可以确认包含嵌套结构。
  • 数据字节与 Solidity 的 preimage 完全匹配,而不是来自不透明的辅助函数。

稳定签名的预防技术

  • 对齐域:nameversionchainIdverifyingContract 必须与合约的 EIP712Domain 完全匹配。
  • 保持名称规范:使用 metadata={"original_name": ...} 以防止 Python 关键字以静默方式更改 Solidity 字段名称。
  • 记录类型字符串:在失败的测试中打印 encode_eip712_type() 以快速找到排序或大小写问题。
  • 避免盲哈希:除非外部 API 需要预先计算的摘要,否则首选 sign_structured
  • 断言 preimages:在 Solidity 中重新计算 keccak256(abi.encodePacked(typeHash, data)),并在验证签名之前断言它等于 Wake 的输出。
  • 模糊嵌套结构:生成可选或嵌套字段的组合,以捕获类型字符串中缺少的结构定义。

结论

Wake 的 Struct.encode_eip712_typeencode_eip712_data 消除了类型化数据测试中的猜测。通过公开类型字符串和 preimage 字节,它们使你可以与 Solidity 精确对齐,验证合约域,并证明签名匹配而无需反复试验。保持域一致,记录类型字符串,并在双方断言摘要以发布可靠的 permit 和 meta-transaction 流。

附加资源

  • 原文链接: ackee.xyz/blog/eip-712-e...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
Ackee
Ackee
Cybersecurity experts | We audit Ethereum and Solana | Creators of @WakeFramework , Solidity (Wake) & @TridentSolana | Educational partner of Solana Foundation