以太坊流动性质押:验证者存款风险与防范措施

本文详细探讨了以太坊流动性质押架构中的一个潜在风险,特别是在存款和取款操作中可能导致用户资金被盗的情形。文章提出了一系列防范措施,并分析了当前的解决方案及其有效性,为开发者提供了深入的技术见解。

概述

以太坊流动质押架构有一个不明显的细微之处,这可能使协议开发者忽视关键的安全措施,导致用户资金被盗。本文清楚地解释了这个问题,并概述了降低相关风险的实用措施。

存款和取款操作中的风险特性

对于不熟悉存款过程的人来说,这里有一个技术概述:当进行存款时,用户向信标存款合约提交一笔交易,该交易包含关键数据,例如验证者的公钥(48 字节)、取款凭证(32 字节)、存款签名(96 字节)和存款金额(以 ETH 为单位)。合约验证该签名,并将存款纳入Merkle树结构,更新 deposit_root 并增加 deposit_count,从而创建一个防篡改的链上记录。

需要强调两个具体点:

  1. 可以向同一个验证者进行多次存款,每笔存款都结合成一个累计的活跃质押余额。
  2. 当资金从质押中取出时,取款地址由存款时提供的取款凭证指定。

当多个存款带有不同的取款凭证(可能属于不同用户)发送到同一验证者时,出现了一个关键问题。这种情况造成与不同取款凭证相关的存款及其质押奖励集中在同一验证者下。紧迫的问题是:最终将由谁的取款凭证收到被取出的资金?

虽然有人可能会假设拥有不同的机制来控制取款访问—例如基于股份的池或直接在信标存款合约中执行的政策—但目前的以太坊实现只是使用第一次存款的取款凭证,而忽略所有后续的存款。

这可能会带来令人不安的后果。攻击者可以预料到其他人的存款即将到来,借此通过使用自己的取款凭证发起一个小额存款进行抢跑。结果,攻击者控制了对该验证者的所有后续存款,从而通过将其提取到攻击者控制的地址来有效窃取用户的资金,使原存款人一无所获。

为了利用这个漏洞,攻击者必须能够签署与验证者相关的数据。如果攻击者也是验证者的创建者,满足这个要求就轻而易举,没有挑战。

即使攻击者不是验证者的原始创建者,攻击也不能完全排除,在评估风险时仍应考虑。例如,验证者可能由提供 API 的服务运营,允许授权用户请求验证者签名以进行存款和取款操作。

我们如何防范?

1. 私有内存池 / MEV

当攻击者预期某事件并提交一笔提前执行的交易,从而启用系统开发者未意图的动作时,这种场景通常被归类为抢跑攻击。

通常,抢跑攻击可以通过使用私有内存池或 MEV 解决方案来减轻,因为它们会隐藏交易,防止潜在攻击者预料和利用它。

然而,在这个具体情况下,这些措施完全无效。原本创建验证者的攻击者不可避免地会了解到任何用户的存款意图,因为明确需要验证者的签名,因此攻击者甚至不需要监控内存池中的交易。

判决:完全无效

2. 避免使用第三方验证者

当验证者直接由流动质押协议本身或密切相关的方管理时,依赖他们的诚实和适当行为不会引入实质性的额外风险。尽管这似乎与去中心化和无信任原则相矛盾,但流动质押协议本质上涉及一定程度的信任。

从这个角度来看,信任由协议控制的验证者通常是合理的,因为倾向于恶意行为的管理员—如果此类行为者确实出现—可能会更喜欢简单的方法来窃取用户资金,而不是追求这种更复杂的攻击场景。

确保验证者永远不会给予潜在攻击者请求验证者签名以进行存款的能力也是至关重要的。理想情况下,验证者应完全避免向外部用户公开任何 API 或接口。

判决:实用可靠

3. 通过信标存款合约验证 deposit_root

不幸的是,信标存款合约不允许链上验证来确定存款是否是对验证者的第一笔存款。然而,可以使用 get_deposit_root() 或 get_deposit_count() 方法在链上监控存款事件,因为这些值在每笔存款交易中发生变化。

通过创建监控存款事件的链下基础设施(例如通过 DepositEvent 日志),开发者可以确保在给定验证者下没有存款。一旦这个状态得到验证,开发者可以快速提交包含保护措施的存款交易,以便在交易的确认期间如果 deposit_root 发生变化则回滚:

bytes32 actualRoot = depositContract.get_deposit_root();
if (expectedDepositRoot != actualRoot) {
    revert InvalidDepositRoot(actualRoot, expectedDepositRoot);
}

在这里,expectedDepositRoot 是在确认状态安全(即没有发生攻击)时由链下基础设施确定的。如果攻击者试图抢跑存款,deposit_root 将会发生变化,从而导致交易回滚。

然而,这种方法也有其缺点。目前,由于不协调的流动质押项目所进行的无关存款导致假回滚的可能性相对较高。以太坊信标链上平均每天约有 2000 笔存款交易。由于以太坊每 12 秒大约会产生一个新区块,这就导致每天有 7200 个区块。因此,在某一个区块中至少发生一次存款(并且 deposit_root 发生变化)的概率约为 25%。

这种情况将导致偶尔的假阳性,需要重试交易。开发者在优化代码时可能会考虑这一点—例如,尽早进行 deposit_root 检查,以最小化与不必要计算相关的Gas费用。

deposit_root 检查通过如果存款状态意外变化则回滚交易,保护抵御外部的抢跑攻击。然而,它并不能防止存款调用者自己执行恶意存款,因为他们控制着交易的时机和内容。为了减轻这种内部风险,可以引入多签名批准机制,要求多个独立方对于在提交存款之前的 expectedDepositRoot 达成一致—从而分散信任,降低单方面行为的潜力。

判决:可靠但运作复杂

4. 在验证者创建时预先进行小额存款

对抗攻击的另一种有效方式是在创建验证者时立即进行最小存款(1 ETH)。由于取款凭证由第一次存款永久设置,因此进行这笔预先存款确保从一开始就锁定了正确的凭证,无法被恶意行为者覆盖。

即使之后有人尝试使用不同的取款凭证进行存款,也不会产生影响—未来的取款仅使用第一次存款的凭证。

这种方法的主要缺点是资本效率低。初始的 1 ETH 存款处于闲置状态,直到达到验证者激活所需的完整 32 ETH 之前并不会开始产生质押奖励。这可能导致将验证者上线的延迟,尤其是在用户存款逐渐到来的情况下。

尽管如此,如果某个项目愿意牺牲资本效率以换取更强的安全保障——尤其是在早期阶段或管理少量验证者时——这种方法是一个扎实且直接的解决方案。

判决:可靠,但资本效率低

5. 协议层面修复

由于问题的根源在于以太坊的核心架构——特别是在信标链如何处理验证者存款和取款凭证上——长期和稳健的解决方案需要对以太坊协议本身进行更改。

这样的协议层面修复可能涉及修改存款合约或验证者注册逻辑,以允许每笔存款使用独立的取款凭证,或对验证者初始化施加更严格的规则。

然而,尽管这个问题自2021年以来就已知晓,截至撰写本文时(2025年)尚未实施修复。这表明解决这个问题目前并不是以太坊核心开发者的高优先事项,这可能是由于所涉及的复杂性以及与其他协议挑战相比影响范围相对有限。

判决:理论上理想,但在实践中不可用

结论

在实践中,目前的流动质押协议主要依赖两种方法:避免使用第三方验证者(方法 #2)以及在提交存款之前进行链下验证 deposit_root(方法 #3),有时甚至没有额外的去中心化保护措施。

预先小额存款策略(方法 #4)从安全的角度看是可靠的,但由于对资本效率的关注,一般在现实世界的实施中避免使用——项目不愿意将资金锁定在等待全面激活的闲置验证者中。

MEV / 私有内存池选项(方法 #1)在本文中提及,并不是作为可行的防御,而是特别强调它在这种情况下无效,不应被依赖。

最后,尽管最优雅的解决方案应是协议层面的修复(方法 #5),自 2021 年以来这个问题仍然未得到解决,表明这并不是以太坊核心开发的当前优先事项。

  • MixBytes 是谁?

MixBytes 是一个由区块链审计和安全研究专家组成的团队,专注于为 EVM 兼容和 Substrate 基础项目提供全面的智能合约审计和技术顾问服务。请加入我们的 X,了解最新的行业趋势和见解。

  • 原文链接: mixbytes.io/blog/ethereu...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
mixbytes
mixbytes
Empowering Web3 businesses to build hack-resistant projects.