本文介绍了使用 Wake 框架测试 EIP-712 类型字符串的技巧,通过 encode_eip712_type 和 encode_eip712_data 方法,开发者可以精确地检查类型字符串和预签名数据,确保与 Solidity 合约中的哈希计算方式一致,从而避免因类型定义错误导致的签名验证问题,并提供了一些预防签名错误的技巧。
类型哈希中的单个错别字是一个中等严重程度的错误,但是手动验证该哈希既繁琐又容易出错。EIP-712 调试中最耗时的部分是弄清楚实际进入摘要的内容。Wake 的 Struct 基类公开了 encode_eip712_type 和 encode_eip712_data,因此你可以准确地看到正在哈希的内容,将其与 Solidity 直接比较,并在部署之前确认签名是否匹配。如果你需要复习原始签名与结构化签名,请从在 Wake 中签名数据:原始、结构化和哈希流开始。
Struct、Account 和链 fixtures 可用。Struct 的子类的 dataclasses。字段顺序和字段名称必须与链上验证的 Solidity 结构镜像。Eip712Domain。name、version、chainId 和 verifyingContract 值必须与 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 端匹配,你就可以安全地对其进行哈希处理。
验证了类型字符串后,完全按照合约的方式构建类型哈希和数据字节。下面的代码段镜像了 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)
要点:
name、version、chainId 和 verifyingContract 必须与合约的 EIP712Domain 完全匹配。metadata={"original_name": ...} 以防止 Python 关键字以静默方式更改 Solidity 字段名称。encode_eip712_type() 以快速找到排序或大小写问题。sign_structured。keccak256(abi.encodePacked(typeHash, data)),并在验证签名之前断言它等于 Wake 的输出。Wake 的 Struct.encode_eip712_type 和 encode_eip712_data 消除了类型化数据测试中的猜测。通过公开类型字符串和 preimage 字节,它们使你可以与 Solidity 精确对齐,验证合约域,并证明签名匹配而无需反复试验。保持域一致,记录类型字符串,并在双方断言摘要以发布可靠的 permit 和 meta-transaction 流。
- 原文链接: ackee.xyz/blog/eip-712-e...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!