什么是签名重放攻击?同一份钱包签名,为什么可能被再次使用
重放攻击不需要伪造签名,而是重复或跨环境使用真实授权。本文讲清 Nonce、期限、chainId、验证合约与 EIP-712 的保护边界。
钱包弹出一份签名,你确认后没有立即转账,于是以为它只是一张“登录证明”。但对某些合约来说,签名本身就是一份可执行授权。如果验证逻辑没有限制使用次数、合约、网络或有效期,同一份有效签名可能被再次提交,这就是签名重放风险。
重放攻击不需要伪造私钥。攻击者利用的恰恰是真签名,只是把它放到签名者没有预期的次数、时间或环境里重新使用。
验证出“确实由这个地址签署”只是第一步;合约还必须确认这份签名用于哪里、何时有效,以及是否已经被使用。
为什么签名可以脱离原来的页面
链下签名通常由消息生成摘要,再由私钥签名。任何拿到签名和原始消息的人,都可能把它提交给能够验证这套格式的合约。若合约只恢复签名者地址,却没有记录使用状态或绑定环境,就无法区分第一次授权和重复提交。

一个简化例子是“凭签名领取 100 枚代币”。如果消息只写接收地址和数量,合约每次都验证签名正确,却不消耗 nonce 或标记订单已领取,同一份签名就可能反复领取。
重放可能发生在哪些边界
| 重放范围 | 缺失的限制 | 可能后果 |
|---|---|---|
| 同一合约重复使用 | Nonce 或已使用标记 | 重复领取、转账或执行指令 |
| 另一个合约使用 | verifyingContract | 相同格式授权在仿制合约生效 |
| 另一条链使用 | chainId | 同一签名跨网络再次提交 |
| 很久以后使用 | deadline 或有效区间 | 过期意图仍可被执行 |
| 另一种业务使用 | 类型或用途标识 | 登录签名被解释为资产授权 |
Nonce 如何让签名“一次性”
Nonce 可以理解为签名者或订单的序号。消息把 nonce 一起签进去,合约验证成功后立即将它标记为已使用或递增。再次提交同一签名时,nonce 已不符合当前状态,因此交易失败。
Nonce 设计不只有一种:账户顺序 nonce 简单,但并发签名可能互相影响;随机或位图 nonce 允许不同授权独立使用,却需要防止重复和正确管理存储。无论采用哪种方式,都要在执行敏感动作前消耗 nonce,并考虑交易回滚、取消和批量操作。
EIP-712 的域分隔解决什么

EIP-712 为结构化数据签名定义了域分隔机制。常见域字段包括应用名称、版本、chainId 和 verifyingContract。这样,即使两个应用使用相同的 Transfer 数据结构,摘要也会因域不同而不同。
但 EIP-712 标准本身明确说明,它不自动提供重放保护。域可以限制签名环境,协议仍需在消息或状态里设计 nonce、期限、订单 ID 或使用标记。只看到钱包展示“结构化签名”,不能推断它一定不可重放。
为什么 chainId 和合约地址都重要
只绑定 chainId,签名仍可能被同一网络上的另一份兼容合约接受;只绑定合约地址,在某些跨链部署或分叉环境中又可能出现相同地址与逻辑。将 chainId 与 verifyingContract 纳入域,有助于把签名限定到特定网络和验证合约。OpenZeppelin 的 EIP712 实现会根据当前 chainId 构建域分隔,以降低链分叉后的重放风险。
代理升级还要注意域版本和业务语义。如果合约地址不变、逻辑却改变,旧签名是否继续有效必须有明确设计。需要时更新 version、使旧 nonce 失效,或提供签名撤销机制。
期限不是一次性保护
deadline 只能限制“最晚何时提交”,在期限内仍可能被重复使用。Nonce 解决次数,期限解决时间,域分隔解决环境,类型和参数解决用途,它们承担不同职责,不能互相替代。
| 保护字段 | 主要回答 |
|---|---|
| nonce / orderId | 这份授权是否已经执行? |
| deadline | 现在是否仍在有效期? |
| chainId | 是否在预期网络? |
| verifyingContract | 是否由预期合约验证? |
| typeHash 与参数 | 执行的是否是用户看到的动作? |
开发者核查清单
- 使用成熟、经过审查的 EIP-712 与签名恢复库,不自行拼接含糊字节串。
- 消息中包含明确动作、全部关键参数、nonce、期限和必要的接收方。
- 域绑定 chainId、verifyingContract、名称与版本,并考虑代理升级。
- 在外部调用或资产转移前消耗 nonce,遵循安全的状态更新顺序。
- 检查 ECDSA 签名可塑性、零地址恢复、ERC-1271 合约签名者等边界。
- 测试同签名重复提交、跨合约、跨链、过期、取消、并发和回滚场景。
普通用户签名前能做什么
核对正在连接的域名、网络、验证合约、资产、数量、接收方、授权对象、nonce 和期限。遇到钱包只显示十六进制哈希、无限期限、陌生合约或与当前操作不相符的类型时,应停止并确认。不要把签名截图、原始 JSON 和签名值随意发给他人,因为某些授权可由任何提交者代为上链。
取消网页连接不一定撤销已经签出的授权。是否能取消取决于协议是否提供 nonce 递增、订单取消或链上撤销机制。若怀疑签了恶意内容,应查看相关合约说明与链上状态,并根据风险处理代币授权和资产。
常见误区
- “没付 Gas 就没风险”:签名可由第三方代为提交,签名者不一定是上链者。
- “EIP-712 就一定安全”:它提高可读性和域分隔能力,但不会自动加入 nonce。
- “有 deadline 就不能重复”:有效期内仍可能多次提交。
- “签名只在当前网页有效”:真正的边界由消息和验证合约决定,不由浏览器标签页决定。
最后总结
- 重放攻击利用真实签名的重复或跨环境使用,并非伪造签名。
- Nonce、期限、chainId、验证合约和用途类型要共同限定授权。
- EIP-712 提供结构化签名和域分隔,但协议必须自行实现重放保护。
- 用户应把签名当作潜在授权,读清对象、参数、期限和合约。
风险提示:钱包签名可能代表登录、授权、挂单或资产操作,不同协议的撤销方式也不同。签名前请独立核验页面、网络、合约与消息内容。本文仅作技术科普,不构成任何投资建议。
本文为链上指南原创内容。技术依据参考 EIP-712 官方规范与OpenZeppelin EIP712 文档。