同一份签名在另一条链上被再次出示:跨链重放与链标识的围栏 图 1
同一份签名在另一条链上被再次出示:跨链重放与链标识的围栏 · 图 1

一条链上已经花掉的一笔授权,到了另一条链上忽然还能再花一次——这不是幻觉,而是签名机制的一个先天属性造成的风险。数字签名证明的是谁同意了这组数据,它本身并不关心这组数据在哪个世界被执行。只要两条链上的合约结构足够相似、nonce 序列又恰好对得上,同一份签名就可能像没花过一样再次通过校验。这类风险的正规名字叫重放,跨链时代把它从理论题变成了工程题。

先讲防御成型前的问题长什么样。以太坊主网早期,交易签名里不含链的身份信息,著名的分叉事件让同一份交易历史同时活在两条链上,人们第一次直观看到:在一条链上把币转给某个地址,另一条链上同样的签名同样合法,币就在那个世界也转了一次。交易层的问题后来被一个简洁的补丁解决——EIP-155 把链标识直接编进签名的哈希输入里,签名从此与特定链标识绑定,换链即失效。这个字段就是你在钱包设置里见过的数字链号,同一个主网在配置文件里也只是一个编号。

链内交易被 155 号补丁管住了,签名的另一大用途却留在围栏外:离线签名。授权额度、限价订单、 Permit 签名这些操作不在链上直接执行,而是签好之后交给第三方择机提交,签名的哈希输入由签名者所在合约自行拼装。如果合约只签金额和地址、不写自己是谁、在哪条链,那这份签名同样是一条链内通用、跨部署通用的通行证。EIP-712 为此提供了标准化容器:哈希输入里除了数据本身,还要拼进应用名称、版本、验证合约地址和链标识四个字段,这四件套合称域分隔符,任何一项不同,签名就是另一份数据。

把镜头拉到跨链场景,重放防线还有第三层:消息去重。跨链桥与消息协议传递的不是签名原件,而是对源链事实的断言——某高度某交易里发生了某事件。接收端的合约会维护一张已消费清单,每条消息带上来源链标识、来源合约、事件序号这三样身份,见过即拒收,重复的断言第二次提交时直接失败。治理动作的跨链执行也依赖同一套账:主网投票通过后发往各链的执行指令,各自绑定目标链标识与提案编号,防止同一决议在一条链上被恶意二次触发。桥被攻破的多个历史案例里,校验链条上最薄弱的一环往往就是身份字段的绑定被简化。

普通用户在这道题里的角色不是写协议,而是会看签名弹窗。一份规范的 EIP-712 签名请求会写明应用名称与验证合约,部分钱包还会显示所属网络;如果弹窗只是一串十六进制乱码、应用名显示为未知合约,你无法确认这份签名的域是否绑定了链标识——它理论上就可能在你没打算操作的链或合约上生效。应对纪律很简单:不签通用授权给来源不明的合约;对高频操作优先用带域分隔的标准签名类型;发现签名里出现陌生网络号或陌生合约名,立即中止并排查前端是否被劫持。

还有一类容易被忽略的重放来自重同步而非攻击:同一事件被索引服务或中继重复投递,若接收合约的去重清单因为升级或状态迁移丢失,正常消息会被当成新消息二次执行。这类事故的责任全在协议方,但用户的受益面是对账能力——大额跨链或申领操作后,用区块浏览器核对目标链上实际执行的次数与事件编号,确认链上世界只执行了一次你以为只执行一次的指令。对账发现双份执行时,先冻结相关授权再联系协议方,顺序不能反。

风险提示放在最后:链号在配置文件里的数字变化不改变资产归属,任何以网络迁移为名要求你到陌生站点重新签名或输入助记词的说法都是钓鱼话术。重放是协议层的工程问题,不需要用户做任何资产动作来解决。本文仅为机制科普,不构成投资建议,也不构成任何收益承诺,具体协议的防重放实现以其合约代码与官方文档为准。

同一份签名在另一条链上被再次出示:跨链重放与链标识的围栏 图 2
同一份签名在另一条链上被再次出示:跨链重放与链标识的围栏 · 图 2