Solidity 中 ECDSA 签名验证的原理是什么?如何实现?
ECDSA 签名验证就是用私钥签名、用公钥验证,链下签名链上验证。Solidity 用 ecrecover(hash, v, r, s) 从签名恢复出签名者地址,再对比是否为预期地址。标准流程:bytes32 hash = keccak256(abi.encodePacked(...)) → 用 EIP-712 结构化哈希 → 链下签名得 (r, s, v) → 链上 ecrecover 恢复地址。OpenZeppelin 的 ECDSA 库封装了边界检查和 malleability 防护。
追问
EIP-712 为什么比普通 keccak256 签名好?
普通签名 keccak256(abi.encodePacked(...)) 用户看到的是一串十六进制,无法判断签了什么。EIP-712 定义了结构化的类型化数据,钱包(MetaMask)会显示人类可读的内容("你正在授权转移 100 USDC"),防止钓鱼签名。
签名重放攻击怎么防?
在签名数据中包含 nonce(递增计数器)和 address(this)(合约地址)。部署新合约后旧签名失效(地址变了),同一合约内每个 nonce 只能用一次。缺少 nonce 攻击者可以重复提交同一签名。
ecrecover 返回 address(0) 意味着什么?
签名无效时 ecrecover 不 revert,而是返回零地址。所以必须检查 recovered != address(0),否则攻击者构造一个恢复为零地址的签名就能绕过验证。OpenZeppelin 的 ECDSA.recover 已经内置了这个检查。
签名延展性(Malleability)是什么?
ECDSA 签名 (r, s) 中,s 可以替换为 n - s(n 是椭圆曲线阶数)得到另一个合法签名,签名不同但恢复的地址一样。EIP-2 要求 s 在曲线阶数的上半部分,ECDSA.toEthSignedMessageHash 和 OpenZeppelin 库都做了这个约束。
多签钱包如何用 ECDSA 实现?
链下收集足够多签名,链上逐一 ecrecover 验证,检查恢复出的地址都在授权列表中且不重复。Gnosis Safe 就是这个模式——不需要链上存储 nonce 状态,gas 更省,但需要链下协调签名顺序。