ERC-1271:查询合约账户的签名策略
EOA 可以用私钥生成 ECDSA 签名。合约账户的地址由部署规则产生,其签名有效性由代码决定:例如核对多签阈值、会话密钥或已批准的摘要。ERC-1271 为调用方提供统一的查询接口。规范
EIP-7702 委托账户仍有私钥,同时可以执行委托代码。这与传统合约账户不同;是否走 ECDSA 或 ERC-1271,应看应用约定与所用验签库的具体版本,不能简单等同为“有代码就没有私钥”。
1. 接口与返回值
interface IERC1271 {
function isValidSignature(bytes32 hash, bytes calldata signature)
external view returns (bytes4);
}
成功必须返回 0x1626ba7e,即该接口的函数选择器。签名字节可以采用钱包自定义格式,不必是 65 字节 ECDSA 签名。调用方不能自行解包后假设只有一个签名者。
低级调用的 success == true 只说明调用未回滚。空 fallback 也可能正常返回;必须继续检查返回数据和魔数。Solidity 对空数据进行普通 ABI 返回值解码会失败,不会产生“内存未清零导致的随机 bool”。
在本例固定的 OpenZeppelin 5.2.0 中,库要求成功返回至少 32 字节 ABI 编码数据,并核对魔数。接口返回的是 bytes4,但正常 ABI 返回值占一个 32 字节槽。源码
2. 当前有效不代表以后有效
钱包可能修改所有者、阈值、模块或摘要批准状态。同一组 (signer, hash, signature) 在两个区块上可以得到不同结果。
链下撮合时的成功验证适合做预检查,实际结算仍应在执行时验证。不要把昨天验证成功的结果永久缓存为今天的授权。业务协议自己的 nonce、取消状态和有效期也必须独立检查。
3. 静态调用的能力边界
验签不得修改状态。现代 EVM 调用方通常用 STATICCALL 执行该查询,其静态约束也传递到子调用。钱包不能在这个调用树内写存储、发日志或通过非零 value 调用转移 ETH。
这不等于完全隔离外部调用:钱包仍可读取其他合约、递归调用 view 接口,并观察调用方的中间状态。若报价或权限依赖未完成的状态更新,仍需分析只读重入及状态一致性。
钱包验签还会消耗 Gas,也可能回滚。批处理应定义单项失败的处理方式和总体预算。ERC-1271 不规定统一 Gas 上限;硬编码较小上限可能排除合法的复杂多签钱包,不能无条件作为兼容性建议。
4. 与业务授权一起使用
本博客的 EIP-712 示例 使用 SignatureChecker.isValidSignatureNow。固定版本 5.2.0 在 code.length == 0 时尝试 ECDSA,否则调用 ERC-1271。这是库的分派规则,不是 EOA/合约账户的永久分类法。
委托后的 EOA 若按这个库接入,需要委托代码实现约定的 ERC-1271 策略。支持未部署的反事实钱包等情形,还需要额外协议,不能从 ERC-1271 接口本身推导。
签名摘要必须绑定真实业务参数。只对调用者提供的 hash 返回 true,随后却按另一组 recipient、amount 执行转账,仍然会形成授权漏洞。
5. 可运行的边界测试
仓库 examples/ethereum-signatures 使用一个仅供测试的 PolicyWallet:批准特定摘要时返回魔数,撤销后返回失败值。它不是可部署的钱包方案。
npm ci --ignore-scripts
forge test --match-test 'testContractWallet|testEmptyFallback' -vv
测试确认:合约钱包可完成签名转账;策略改变后原签名失效;空 fallback 的成功返回不能冒充有效签名。测试用钱包的配置入口故意简化,真实钱包必须具备配置权限控制。