原文发表于 2026 年 9 月 11 日 16:00(UTC+8),作者为 Johan、Lisa,编辑为 77。以下事件经过、漏洞分析与资金状态均依据该文,时间截至原文发稿,不代表此后的进展。
据原文披露,2026 年 9 月 6 日,Liquid Network 出现一次利用 rangeproof(范围证明)验证缓存的共识层攻击。攻击者通过构造缓存键相同、实际验证参数不同的输出,使未经有效验证的输出被节点放行,凭空生成约 3,998.5 枚 L-BTC,并经联邦 peg-out 通道兑出比特币主网上的 BTC。
原文追踪称,攻击者次日返还 3,400 BTC;截至发稿,剩余约 598.5 BTC 仍由其控制,约占兑出金额的 15%。攻击者随后通过链上留言索要 10% 的“赏金”。这属于攻击者的自我表述,并不意味着其白帽身份或赏金要求获得认可。
漏洞不在钱包私钥,而在验证结果的复用
Liquid 的正常发行路径是:用户将 BTC 存入联邦管理的多签钱包,Liquid 侧铸造等量 L-BTC;反向通过 peg-out 换回 BTC。
其机密交易使用 Pedersen 承诺隐藏输出金额,并通过范围证明约束金额的取值。节点还会检查输入与输出的承诺是否平衡。若无效的范围证明被放行,攻击者就可能利用异常金额承诺,在承诺求和仍然平衡的情况下制造本不应存在的资产。
范围证明通常有四千多字节,验证需要椭圆曲线运算。为了减少重复计算,Elements 在 2016 年 7 月 12 日引入了名为 Rangeproof caching 的提交,编号为 66ab07bd1b,缓存已经验证成功的结果。
这次攻击触及的信任边界,正是“缓存命中”能否等同于“当前这组参数已经验证通过”。据原文,受影响范围为 Elements 23.3.4 之前的全部版本,包括 8 月 3 日补丁后的构建;事件不涉及智能合约。
补齐字段之后,仍然缺少字段边界
原文展示的 Elements 23.3.3 及更早版本代码,将范围证明 proof 与金额承诺 commitment 直接拼接后输入 SHA-256。缓存键没有绑定资产、锁定脚本,也没有编码字段边界。
2026 年 8 月 3 日的修复提交 c26d719 加入了资产承诺和锁定脚本,但仍将四个字段直接拼接。由于证明和脚本都是变长字段,不同的参数组合仍可能产生完全相同的字节流。
这里并不是攻击者破解了 SHA-256,而是哈希之前的编码已经存在歧义:字段的分界发生变化,拼接结果却没有变化。
按照原文对 CachingRangeProofChecker::VerifyRangeProof 的分析,节点一旦查到缓存记录,就直接返回 true;后续的 secp256k1_rangeproof_verify 和 min_value 检查不会执行。因此,一个合法输出留下的验证结果,可能被另一个实际不同的输出借用。
节点启动时生成的随机盐也无法解决这一问题。盐被放在字段之前,而攻击者构造的两组字段原本就能拼出相同的字节流,因此在同一节点上加盐后仍然相同。
攻击如何跨过缓存检查
原文给出的核心交易时序如下,时间均为 UTC。
- 9 月 6 日 13:52:10,区块 4,050,335:两笔结构相同的 setup 交易入块。相关输出携带合法证明
P0、金额承诺C0和资产字段A,其脚本内嵌了后续攻击所需的数据。 - 13:53:10,区块 4,050,336:铸造交易出现。攻击者重新划分证明、金额承诺、资产承诺与脚本的边界,使其缓存键与合法输出一致。
- 13:54:10 和 14:00:10,区块 4,050,337 与 4,050,343:两笔交易归拢并花费相关 UTXO。原文称,后续 peg-out 请求经联邦授权,等值 BTC 在比特币主网上付出。
两组字段的关系可以概括为:
setup 输出:
proof = P0
commitment = C0
asset = A
scriptPubKey = 6a43 ‖ C1 ‖ X ‖ 6a
铸造输出:
proof = P0 ‖ C0 ‖ A ‖ 6a43
commitment = C1
asset = X
scriptPubKey = 6a
其中 ‖ 表示字节拼接。两组参数的字段内容和边界不同,但依次拼接后逐字节相同。原文称,铸造输出因此命中缓存,未经实际范围证明验证便被放行;攻击者再通过负值承诺输出使 Pedersen 承诺求和平衡,令约 3,998.5 枚 L-BTC 进入 UTXO 集。
缓存条目的生命周期也是攻击条件之一:交易进入内存池时,可以写入验证结果;连接区块时则查询缓存但不写入,命中的条目会被标记为可回收。原文据此分析,攻击者还需要向内存池投递携带相同载荷的交易,为目标节点重新填充缓存。
不过,作者明确表示,无法仅从区块数据反推出具体哪些交易承担了这种“播种”作用,它们可能没有入块。原文关于两笔 setup 交易为何被安排在同一区块的解释,也属于推测,而非已经确认的攻击意图。
资金去向:返还额与未返还额应分开看
原文将 Liquid 侧区块浏览器核验与 Bitcoin 侧 MistTrack 分析结合起来追踪资金,并说明 MistTrack 当时尚未覆盖 Liquid 链。
Liquid 侧相关地址为:
ex1q7kgx4ptje7px48tn0nsmc6se5pngdp3smpqa2w
据原文,该地址涉及 91 笔交易、247 个输出,截至发稿均已花费。核心攻击流程包含两笔 setup、一笔铸造及两笔归拢消费交易,此外还有 peg-out 和比特币主网支付。
约 3,998.5 BTC 的 peg-out 资金最终进入比特币地址:
bc1ql4mfu6aundtkksxklfajs2h3t9nzcd6gyqjlte
原文称,MistTrack 对该地址给出的风险评分为 100/100,评级为 Severe,并标记为恶意地址、涉及盗窃活动。这些是该分析工具的风险标签,不应与独立司法认定混同。
次日,3,400 BTC 转入原文所指的联邦 peg 钱包:
bc1qdlld6antmv4xug242ed83q7k4rqw50cwfns38szx4qu2f4jwaxxsuhwxxr
剩余约 598.5 BTC 在 9 月 8 日至 10 日、比特币区块 966,052 至 966,199 之间反复自我转账,并附带 OP_RETURN 明文。其中一条留言要求联邦“用自己的钱支付 10% 作为漏洞赏金”,留下 Telegram 账号 @MRBOND_1。
因此,按原文发稿时的追踪结果,应区分约 3,998.5 BTC 的兑出规模、3,400 BTC 的返还金额,以及约 598.5 BTC 的未返还余额,不能将兑出总额直接写成最终净损失。
修复重点:让缓存键完整表达验证输入
据原文,9 月 8 日的加固提交 94000967f6 改用带长度前缀的序列化哈希,使各字段边界不再依赖裸拼接。该提交还修复了 surjection proof 缓存键未绑定输入资产标签的问题。
同日发布的 Elements 23.3.4 除了调整缓存键编码,还加入 -norangeproofcache 开关,允许运维禁用范围证明缓存。
这次分析呈现的核心问题是:密码学验证路径上的正向结果缓存,本身也是安全边界。只要命中缓存就能跳过验证,缓存键就必须无歧义地绑定验证使用的全部参数及其边界。补齐字段只是其中一部分,字段如何编码同样决定了验证结果能否被安全复用。