T26 · Cross-chain
资产跨链以后,还是同一个资产吗?
- 练习的能力
- BuilderProtocol Literacy
- 动手
- 跟踪一次跨链转账的两端交易,写清楚中间的信任假设由谁承担。
- AI Lab
- 让 AI 对比三种跨链架构的信任模型,再用一次真实桥攻击事件检验它的分类。
一个现实问题
一个用户在 A 链上有 1000 USDC。他想去 B 链上的一个协议做点事,于是用一座桥把钱转过去。
两分钟后,B 链上的钱包显示:USDC 1000。图标是对的,名字是对的,小数位是对的。他把它存进一个借贷协议做抵押,借出一笔钱去做别的事。
三周后的某个凌晨,A 链上那座桥的锁仓合约被搬空了。
B 链上没有任何东西发生变化。合约地址没变,他的余额还是 1000,代币名字还是 USDC。唯一变化的是:这 1000 个代币背后,现在什么都没有了。
几分钟内它的价格掉到几分钱。借贷协议里他那笔抵押瞬间资不抵债,但清算人也不愿意接——接过来只会拿到一堆废纸。坏账留在了协议里,由所有存款人分摊。
回头看,这个用户从头到尾没做错任何事:他用的是主流的桥,看到的是熟悉的代币符号,钱包和行情软件都显示这是 USDC。
所以问题很尖锐:他手里那 1000 个东西,到底是什么?
思想实验
把两条链想成两个国家,它们之间没有直达的通信线路。
你在 A 国的仓库存了一箱货,仓库给你一张提货单。你带着这张单子到了 B 国,想在这边把它卖掉。
B 国的买家凭什么相信这张单子? 他们看不到 A 国的仓库,也没办法自己去核实。
历史上人类解决这个问题只有三种办法。
第一种:请一个 B 国也认的公证人。 他在 A 国看过仓库,他说单子是真的,B 国的人就认。简单、快、便宜。风险也一目了然:公证人说了算。 他被收买、被胁迫、或者干脆自己印一张假单子,B 国没有任何办法发现。
第二种:B 国自己派人常驻 A 国仓库门口。 每一次入库出库都亲眼记下来,自己维护一本 A 国的流水账。这样 B 国不需要相信任何人,只需要相信自己派出去的那套记账规则。
代价是贵。这个人要一直在那里,要抄下 A 国仓库的全部变动,哪怕 99% 的变动和 B 国无关。而且如果 A 国的记账规则很复杂,这个人可能根本抄不动。
第三种:根本不传单子。 B 国有个人手里本来就有一箱同样的货。你把 A 国的单子给他,他把 B 国的货给你。他自己去承担「A 国那箱货到底在不在」的风险,因为他判断自己扛得住,并且收你一笔费用。
现在是关键的一层。
上面三种办法争论的从来不是「货怎么过去」——货一步都没有动过。三种办法争论的只有一件事:「A 国仓库里有一箱货归你」这条消息,B 国凭什么信。
所以,那张提货单从来不是货。它是一条消息。B 国那些人愿意用真金白银换它,是因为他们选择把这条消息解释成一件资产。
消息可以是假的。消息一旦是假的,被解释出来的那件资产,就从来没有存在过。
开头那个用户手里的 1000 USDC,就是一条消息的解释结果。锁仓合约被搬空的那一刻,消息的依据没了,解释也就没了。
你来决定
你要为自己的应用选一座桥,或者干脆自己设计一座。先选验证方式。
观察结果
四种做法摆在一起:
| 你在信任谁 | 典型失败模式 | 延迟 | 成本 | 接新链的难度 | |
|---|---|---|---|---|---|
| 外部验证者 | N 个人里凑不出 M 个坏人 | 私钥被盗、验证逻辑有 bug | 低 | 低 | 低 |
| 轻客户端 | 源链的共识本身 | 实现 bug、共识升级打断 | 中 | 高 | 高 |
| 挑战期 | 至少一个诚实的挑战者,且他发得出交易 | 挑战被审查或抢跑 | 高 | 中 | 中 |
| 流动性网络 | 做市商与池子深度 | 池子被抽干、对手方违约 | 低 | 中 | 中 |
第二列是全表唯一重要的一列。它让第一条结论变得很直白:
不存在「无需信任」的跨链,只存在「信任谁」的不同选择。
任何一份宣传材料说自己 trustless,你要做的只有一件事:把那句话翻译成「如果谁作恶,我的钱会没」。 翻译不出来,说明你还没读懂它。
第二条结论解释了为什么桥是这个行业出事最多的地方之一。
桥的锁仓量是会增长的——它本质上是一个资金池,用的人越多越深。但它的安全预算不会跟着涨。一个有 N 个验证者的桥,锁仓从一千万涨到十亿,验证者还是那 N 个人,攻破它需要的成本几乎没变。
于是每座桥都在朝着同一个状态漂移:
攻破它的成本,迟早会低于攻破它的收益。
这句话本身就是下一章「经济攻击」的核心公式,只不过在桥这里它表现得格外赤裸——因为桥的资金是集中的。一个借贷协议被攻破,损失通常是某个池子;一座桥被攻破,损失是它锁的全部。
第三条结论是这一章的标题:
跨链传的是消息。资产只是消息的一种解释方式。
理解这句话,你就能回答开头那个问题了:那 1000 个代币,是 B 链上一份合约根据一条来自 A 链的消息铸出来的凭证。它的价值完全等于「那条消息为真」这件事的可信度,一分不多,一分不少。
建立模型
三层:传输、验证、解释
任何一座桥都能拆成三层,而且只有中间那层决定信任假设。
- 源链发生一个事件
- 传输层:谁把它搬过去
- 验证层:谁证明它是真的
- 解释层:目标链怎么处理它
| 层 | 做什么 | 换掉它会影响什么 |
|---|---|---|
| 传输 | 把消息从 A 搬到 B。中继、预言机网络、甚至用户自己贴一份证据 | 只影响活性:没人搬,消息就卡住,但伪造不了 |
| 验证 | 判断这条消息是不是真的来自源链 | 全部安全性都在这里 |
| 解释 | 目标链上的合约拿到消息之后做什么:铸币、解锁、调一个函数 | 影响攻击的后果有多大 |
分清这三层能挡掉一大半营销话术。「我们有 20 个独立中继」听起来很安全,但中继属于传输层——中继再多,也只是保证消息送得到,不保证消息是真的。要问的永远是:验证层是谁。
资产的三种表示方式
同样是「把钱弄过去」,链上的实现有三种,用户看到的东西完全不同。
| 方式 | 源链发生什么 | 目标链发生什么 | 用户拿到的是 |
|---|---|---|---|
| 锁定铸造 | 资产锁进合约 | 铸出一份包装资产 | 凭证,价值依赖锁仓安全 |
| 销毁铸造 | 资产被销毁 | 由同一个发行方铸出原生资产 | 原生资产,信任发行方 |
| 流动性交换 | 资产进池子 | 从另一边的池子里取出 | 原生资产,信任流动性 |
第一种是包装资产的来源,也是开头那个场景的来源。它有一个非常容易被忽略的后果:
同一种资产在目标链上会有多个互不相同的版本。 五座桥搬过去的 USDC 是五个不同的合约地址,符号可能都叫 USDC,但它们彼此不可互换:A 桥铸的币回不到 B 桥的锁仓里去。
于是流动性被切成了碎片,而且用户几乎没有办法分辨自己手里是哪一个。这是集成方必须自己处理的一件事:
你的合约白名单里写的是地址,不是符号。 任何按符号匹配资产的代码,迟早会接收到一个不该接收的东西。
第二种是发行方自己下场做跨链。信任假设从「桥」收窄到「发行方」,通常是包装资产的一个明显改进——前提是你本来就已经信任这个发行方了。
消息层:幂等与重放,又见 outbox
跨链消息必须满足两条性质,而它们和 T17 的 outbox 是同一套东西。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
contract MessageReceiver {
// 已执行的消息,防重放。键是消息的全局唯一坐标
mapping(bytes32 => bool) public executed;
event Executed(bytes32 indexed id);
function execute(
uint256 srcChainId,
uint256 nonce,
address sender,
bytes calldata payload,
bytes calldata proof
) external {
// 1. 唯一 ID 必须包含目标链,否则同一条消息能在多条链上各执行一次
bytes32 id = keccak256(
abi.encode(srcChainId, block.chainid, nonce, sender, payload)
);
// 2. 幂等:执行过就直接拒绝
require(!executed[id], "already executed");
// 3. 验证层:这一行决定了整座桥的安全性
require(verify(id, proof), "invalid proof");
// 4. 先记账,再执行。顺序反了就是重入,原因见 T28
executed[id] = true;
_apply(sender, payload);
emit Executed(id);
}
function verify(bytes32, bytes calldata) internal view virtual returns (bool) {
return false; // 由具体的验证层实现
}
function _apply(address, bytes calldata) internal virtual {}
}四个注释各对应一类真实事故。
唯一 ID 里必须包含目标链。 漏掉它,同一份证明可以在每条部署了这份合约的链上各执行一次。一次跨链转账变成 N 次铸币。
幂等必须在链上、用持久状态实现。 中继重发、用户手动重试、区块重组,都会让同一条消息到达不止一次。这和 T16 的主键幂等是同一个道理:去重的依据必须是消息自身的坐标,不能是「我记得发过了」。
验证那一行是整座桥的全部安全性。 后面的真实案例会展示,有多少事故就出在这一个函数里。
先记账、再执行。 反过来就是重入,下一章会把这条规则的代价演示得非常清楚。
最终性:跨链最容易被忽略的那条线
这是一个直接从 T17 继承过来的问题,而且后果严重得多。
源链 :用户锁仓 → 交易进块 → ...若干个确认... → 足够深
目标链 : ↑
在哪一个时刻铸币?如果目标链在源链刚出块时就铸币,而源链随后发生重组、那笔锁仓交易不再存在——目标链上的币已经铸出来了,源链上的抵押从来没有过。
这是凭空增发,而且没有任何办法收回。
T17 的 outbox 讲过同一件事:不可回滚的动作,必须等到足够深之后才做。 跨链是这条规则最昂贵的应用场景,因为「已经发出去的通知」还能道歉,「已经铸出去的币」只能认。
所以每一座桥都有一个等待深度。而这里有两个现实的坑:
第一,等待深度是按链配置的,不是全局常量。 不同链的最终性模型差别极大,有的有明确的确定性最终性,有的只有概率性保证。把一条链的参数抄到另一条链上,是一个会被利用的错误。
第二,等待深度和用户体验直接冲突。 「三秒到账」是市场上最有效的宣传语之一,而它的实现方式往往是:把等待深度调小,或者干脆让垫资方承担这段风险。 后者是诚实的做法,前者是把风险偷偷转嫁给全体用户。
攻击面清单
桥出事多,不是因为写桥的人水平差,而是因为它同时具备三个性质:资金集中、验证逻辑复杂、跨越两套安全模型。
把攻击面列全,按历史上真实出现的频率排序:
| 攻击面 | 具体形态 |
|---|---|
| 验证逻辑 bug | 没检查签名者是否在验证者集合里;没检查证明对应的是哪条链;把一个空证明判成有效 |
| 私钥被盗 | 验证者的机器、云账号、CI 被入侵;门限签名的分片被集中保管 |
| 铸币权限 | 目标链上的铸币函数没有正确限定调用者;升级之后权限漏了 |
| 升级权限 | 合约可升级,且升级权限在一个普通账户手里,没有时间锁 |
| 消息重放 | 唯一 ID 缺少目标链或 nonce;执行前没有记账 |
| 最终性不足 | 等待深度太浅,源链重组之后目标链已铸币 |
| 解释层过宽 | 消息不只能转账,还能调任意函数,于是一条伪造消息等于任意代码执行 |
最后一行值得单独说。很多桥的底层是通用消息通道——它能传的不只是「给某人铸多少币」,而是「在目标链上调用这个地址的这个函数」。
这让它非常强大,也让一次成功的伪造从「多铸一些币」升级成「以桥合约的身份做任何事」。集成这类通道时要加一句自己的约束:只接受来自特定源地址、特定格式的消息,不要接受任意调用。
算一次账
下一章会把这件事做成方法论,这里先做一次最简单的:
攻破成本 = 拿到 M 把私钥的成本 + Gas + 时间
攻破收益 = 桥上锁着的全部资产
安全的条件:攻破成本 > 攻破收益第二行通常是公开可查的——锁仓量就写在链上,任何人都能读。第一行则很难估,但你至少能问出几个有答案的问题:验证者有几个、分别是谁、是不是同一个团队运营、密钥怎么保管、有没有时间锁。
当锁仓量增长了两个数量级,而第一行的答案一个字都没变时,这座桥已经进入了一个只是还没轮到它的状态。
它叫什么
让一条链上的状态变化能够在另一条链上产生效果的系统。
它的本质不是「搬运资产」——资产一步都没动过。它是把一条消息送过去,并让目标链相信这条消息。
跨链的底层能力:在 B 链上执行一段由 A 链上的事件触发的逻辑。
资产跨链只是它的一个应用。通道越通用,一次伪造的后果越大,因为伪造出来的不再是一笔转账,而是一次任意调用。
源链把资产锁进合约,目标链铸出等量的包装资产;反向操作时销毁包装资产、解锁原资产。
它产生的包装资产是凭证而不是资产本身,价值完全依赖锁仓端的安全。同一种资产经过不同的桥会产生多个互不兼容的版本,这是流动性碎片化的主要来源。
在目标链上运行一份源链的共识验证逻辑,自己验证区块头和 Merkle 证明。
它的信任假设最小——只信任源链的共识。代价是实现难、Gas 贵、且源链共识升级会打断它。用零知识证明压缩验证成本,是这条路目前主要的工程方向。
一句可以写成「如果谁作恶或谁失效,我的钱会没」的话。
每一座桥都有这句话,区别只在它是否被写出来。 评估任何跨链方案,第一件事就是把它翻译成这句话;翻译不出来,说明你还没读懂它的验证层。
目标链上代表另一条链上资产的代币。
三条实用规则:按地址而不是按符号识别它;知道它的赎回路径是什么;知道它的锁仓端出事时你会拿到什么。三个问题答不上来,就不要把它接进任何会产生债务的协议。
保证同一条跨链消息只被执行一次的机制:全局唯一 ID 加上目标链上的已执行记录。
唯一 ID 必须同时包含源链、目标链和 nonce。少一个维度,就多一条被重复执行的路径。
动手
全程测试网。 这个 Lab 的目的是读懂两端的交易结构,不是搬钱。任何在主网上「试一下」的冲动,请等到你能完整写出这一章要求的那句话之后。
产出是一份一页纸的报告,最后一行必须是那句话:「如果 ____ 作恶或失效,我的钱会没。」
选两条测试网和一座支持它们的桥。 从水龙头领一点测试币,发起一次小额跨链。
记下发起的时间。从这一刻开始计时,后面要用。
读源链那笔交易。
在区块浏览器上打开它,回答四个问题:
- 钱去了哪个地址?那是一个锁仓合约,还是一个 EOA?
- 它发出了什么事件?事件里有没有目标链 ID、接收方、nonce?
- 这个合约锁了多少钱?(读它的余额,不是读官网的数字)
- 这个合约是代理吗?谁能升级它?有没有时间锁?第三、四个问题最重要,也是最多人跳过的。读余额这件事你在 T5 已经会做了,判断代理在 T8 也讲过。 把它们用在这里。
记下到账时间,算出实际等待深度。
用到账时间减去发起时间,除以源链的出块时间,得到一个大致的等待区块数。
把这个数字和源链的最终性特征对照一下。 如果它明显偏小,你已经找到了这座桥最主要的风险敞口之一。
读目标链那笔交易。
- 是谁发起的这笔交易?(中继、验证者、还是你自己?)
- 它调用了哪个函数?参数里带的「证明」长什么样?
- 铸币的权限归谁?去读那个代币合约的 mint 相关权限
- 你收到的代币,合约地址是什么?它和这条链上「官方」的同名代币是同一个地址吗?最后一个问题会让很多人第一次意识到包装资产的存在。
把验证层找出来。
沿着目标链那笔交易的调用链往里走,找到判断「这条消息是不是真的」的那一步。它通常是一次签名验证、一次 Merkle 证明校验,或者一次对某个注册表的查询。
然后回答:它在验证什么?验证通过意味着谁做了背书?
找不到这一步,就去读桥的文档和合约源码。找不到验证层,等于你不知道自己在信任谁——这本身就是一个结论。
写那句话。 一句话,主语必须具体。
不合格:「如果桥被攻击,我的钱会没。」——这是废话
合格: 「如果这 8 个验证者里有 5 个的私钥同时泄露,
我在目标链上的包装资产会变成没有抵押的空头凭证。」写完之后追加两问:这 8 个验证者分别是谁?是不是同一个团队? 答案经常令人意外。
再做一次反向跨链,观察差异。
反向路径的验证方式、等待时间、手续费常常和正向完全不同。很多桥的两个方向是两套机制,一个方向安全不代表另一个方向也安全。
做完之后把两个方向的那句话并排写下来,这份对照就是这个 Lab 的成果。
AI Lab
分三步,第三步才是真正的考题。
第一步:
把跨链桥分成三类:外部验证者、轻客户端、挑战期。
对每一类,用「如果 ___ 作恶或失效,用户的钱会没」这个句式写出信任假设,
主语必须具体到角色和数量,不要写「桥」「系统」这种词。
再补充这三类各自的延迟、成本和接新链的难度。
第二步:
把桥拆成传输层、验证层、解释层。
说明每一层出问题分别会导致什么后果,
并指出哪一层的问题会导致资金损失,哪一层只会导致消息卡住。
第三步:
找一次真实发生过的跨链桥资金损失事件。
按你上面的分层框架,说明:
1. 攻击者具体做了什么?发了什么交易?
2. 失效的是哪一层?
3. 如果这座桥用的是另外两种架构之一,这次攻击还成立吗?
4. 这次事件发生前,有没有办法从链上公开数据提前看出风险?第一步模型通常答得像教科书,因为这是一道被写过很多遍的题。要盯的是主语:一旦出现「如果桥被攻破」这种没有主语的句子,说明它在复述而不是在分析。
第二步是这个 Lab 的骨架。模型很容易把中继数量、节点分布这类传输层的指标当成安全性论据——这正是营销材料最常用的手法,而它在这个框架下一眼就能被识破。
第三步是真正的考题,而且它同时在考模型和考你。桥的事故报告公开且详细,模型有很多素材,但它经常把「私钥被盗」和「验证逻辑有 bug」混为一谈——这两者对应的防御手段完全不同:前者要改密钥管理,后者要改代码。归错类,后面所有结论都会跟着错。
第 4 小问最有价值:事前能不能从链上看出来。 大多数情况答案是「能看出一部分」——锁仓量、验证者数量、升级权限有没有时间锁,全都是公开的。把这几项列成一张表,就是你评估任何一座桥的起点。
最后必须你自己做:补一条它没提到的攻击面。 上面那张攻击面清单里有七行,模型很少能一次说全,漏掉的那几条通常是重放保护和最终性——而它们恰恰是你自己写跨链集成时最可能踩的两条。
AI 说完之后,你必须自己验证
- 它有没有把「中继数量多」当成安全性论据:中继属于传输层,再多也不影响伪造
- 每一种架构它给出的信任假设,能不能被改写成「如果谁作恶,钱会没」这种主语具体的句子
- 它有没有提到最终性与等待深度:漏掉这一条的对比是不完整的
- 它引用的攻击事件,你自己去查过交易记录或事后报告,时间、金额、手法都对得上
- 它把那次事故归到哪一层:是验证逻辑、私钥管理、铸币权限还是升级权限,归错了说明它的分类框架没落到实处
- 它有没有声称某种方案「完全无需信任」:这个说法在任何架构上都不成立
- 最后自己补一条它没提到的攻击面,并说明哪种架构对它免疫
真实案例
桥的合约会校验一组签名,但校验逻辑里漏了最关键的一步:它确认了签名在密码学上有效,却没有确认签名者属于验证者集合。
于是攻击者用自己随便生成的几把私钥签了一份伪造的存款证明,提交上去,验证通过,铸出了巨额资产。
这个漏洞的性质值得琢磨:代码没有语法错误,编译通过,测试也通过——因为正常路径上的签名本来就来自真验证者。只有构造一份异常输入才会暴露它。
这是下一章那句话的最好注脚:多数漏洞出在权限边界,而不是语法。
验证者数量不多,阈值也不高。攻击者通过入侵运营方的基础设施,拿到了超过阈值数量的密钥,然后用完全合法的流程签出了一笔提款。
链上看不到任何异常:签名有效、流程正确、合约行为完全符合设计。
它说明了两件事。第一,桥的安全上限是它的密钥管理水平,代码写得再好也补不上这一层。第二,链上监控在这类攻击面前几乎无效——因为没有任何一步是「不正常」的。
一个借贷协议接受某座桥的包装资产作为抵押品,并且风险参数和原生资产设得差不多。
桥出事之后,这份包装资产失去支撑、价格崩塌。清算人不愿意接,因为接过来只是一堆没人要的凭证。坏账留在协议里,由存款人分摊。
教训是给集成方的:包装资产的风险等于它自身的风险加上桥的风险,两者相加。 给它的抵押率、给它的喂价来源、给它的规模上限,都应该体现这一点。T21 讲过预言机是协议最常见的单点,这里补上另一个:跨链资产的赎回路径也是一个单点。
不少桥的核心合约是可升级的,升级权限握在一个普通外部账户或者一个低阈值多签手里,没有时间锁。
这意味着:即使代码完美无缺、验证者全部诚实,一把私钥仍然可以在一笔交易内改掉整座桥的逻辑。
这一条不需要任何攻击技巧就能检查,而且检查结果完全公开。它应该出现在你评估任何桥的第一页。 第 24 章 说过同一句话:没有时间锁的升级权限,等于把资金交给了那把钥匙。
改一个变量
攻破收益涨了 100 倍,攻破成本几乎没变。这座桥从「攻击不划算」滑到了「攻击很划算」,而且这个过程中没有任何一行代码发生变化,也不会触发任何告警。
这是桥这个品类最根本的结构性问题:安全预算不随资金规模增长。
可行的缓解手段都是在人为制造成本或上限:提高阈值与验证者数量、给提款加速率限制、给单笔加上限、给大额加时间锁。它们都会牺牲体验,而这正是问题所在——市场奖励的是快,不是慢。
等待深度从「猜一个经验值」变成「有明确依据」,重组导致凭空增发这一整类风险基本消失。
但验证层的问题一个都没解决:伪造证明、私钥被盗、铸币权限失控,和源链的最终性毫无关系。
这个区分很重要,因为它经常被混淆:最终性解决的是「这件事到底有没有发生」,验证层解决的是「你凭什么相信它发生了」。 两个不同的问题。
包装资产消失了,用户在每条链上拿到的都是原生资产,流动性碎片化问题也一起消失。
信任假设从「一座桥的验证者」收窄到「这个发行方」。如果你本来就持有它发行的资产,这不是新增信任,只是把已有的信任复用了一次——这是一次实打实的改进。
代价是它只适用于有中心化发行方的资产,而且把发行方变成了更彻底的单点:它能冻结、能拒绝、能停止服务。改进是真的,只是把风险换了一个形状。
能力大了一个量级:跨链治理、跨链清算、一条链上的应用直接调用另一条链上的合约。
风险也跟着升级:一次成功的伪造不再是「多铸一些币」,而是「以桥合约的身份做任何事」。 而桥合约在很多协议里是被授权的地址。
集成这类通道时必须自己加一层解释层的约束:只接受来自特定源链、特定源地址的消息,只允许一组固定的函数,参数范围也要校验。这套做法和 T32 的 Validator 与 Policy 是同一个思路——上游不可信时,防线画在自己这一侧。
带走的问题
它解决什么问题?跨链解决的是「A 链上的状态变化如何在 B 链上产生效果」。注意它不解决「把资产搬过去」——资产从来没有移动过。想清楚这一点,你就不会再问「我的币在跨链的路上吗」这种问题:它一直在源链的锁仓合约里,或者已经被销毁了。
谁提供流动性?对流动性网络型的桥,这个问题的答案直接决定了你能不能过去、滑点多大。对锁定铸造型的桥,问题要换个问法:目标链上那份包装资产的市场,深度是谁提供的? 桥出事时能不能跑掉,取决于这个答案,而不是取决于桥本身。
谁承担风险?这是这一章唯一真正重要的问题。答案永远不是「桥」,而是一组具体的人或机制:N 个验证者、一个发行方、一群做市商、或者源链的共识本身。能把主语说具体,你就完成了这一章。
本章自测
取决于用的是哪种方式。
锁定铸造:不是。你拿到的是一份凭证,它的价值等于「源链那份锁仓还在」这件事的可信度。锁仓被搬空,凭证归零,而它的合约地址、符号、余额都不会有任何变化。
销毁铸造和流动性交换:是。你拿到的是目标链上的原生资产,风险分别转移到了发行方和做市商身上。
判断方法很朴素:问一句「我拿到的这个东西,怎么换回原来的东西」。 如果答案是「走回那座桥」,那它就是凭证。
因为中继属于传输层,它只负责把消息搬过去。传输层出问题的后果是消息卡住,不是消息变假。
伪造消息的能力全部在验证层。一座桥有一百个中继但只有三个验证者,它的安全性就是那三个验证者。
评估任何跨链方案,先做一次分层:传输、验证、解释。然后只盯中间那一层。
因为同一条消息会以各种理由到达不止一次:中继重发、用户重试、源链重组之后重新广播。没有重放保护,一次转账会被执行多次。
唯一 ID 至少要包含源链 ID、目标链 ID、nonce、发送方和 payload。
目标链 ID 是最容易漏、后果最严重的一个:漏掉它,同一份证明可以在每一条部署了这份合约的链上各执行一次。
执行记录必须写在链上的持久状态里,而且必须在执行之前写——顺序反了就是重入。
必须等到源链达到足够的最终性。等得不够,源链一次重组就会让目标链的铸币失去抵押,这是凭空增发,无法回收。
具体深度按链配置,不能全局统一:不同链的最终性模型差别极大,把一条链的参数抄到另一条链上是会被利用的错误。
这和 T17 的 outbox 是同一条规则:不可回滚的动作必须等到足够深之后才做。 跨链是这条规则代价最高的应用场景。
市场上「几秒到账」的体验,要么来自垫资方承担这段风险(诚实),要么来自把等待深度调小(把风险转嫁给了所有人)。
没有标准答案,检查这几件事:
- 那句话写出来了吗:「如果 ____ 作恶或失效,我的钱会没」,主语是否具体到角色和数量。
- 验证层是什么?你自己在链上找到那一步了吗,还是只看了文档?
- 锁仓端现在锁着多少钱?这个数字和验证者数量的比例,过去半年变化了多少?
- 升级权限在谁手里?有没有时间锁?
- 你的合约是按地址还是按符号识别这个资产的?
- 它出事时,你的协议会产生多少坏账?这个数字算过吗?
- 有没有为它单独设规模上限,还是和原生资产共用参数?
第 3 条和第 7 条是最能体现你真的理解了这一章的两条。第 3 条衡量风险在怎样漂移,第 7 条决定它出事时你会亏多少。
一句话带走
跨链传的是消息,资产只是消息的一种解释方式。