Frame Transactions与隐私三道关
本文分析了Frame Transactions(EIP-8141)如何通过VERIFY和SENDER帧消除隐私协议中的中继器,并探讨了隐私交易在公共mempool准入、FOCIL强制包含和节点验证能力三个关卡中面临的障碍。
本文将 EIP-8141 作为既定前提,而非对其表示支持或反对。目标是展示框架交易如何改进隐私协议,以及公共内存池规则和包含列表执行需要做出哪些改变才能使其真正生效。
框架交易如何消除中继器
像 Tornado Cash 和 Railgun 这样的隐私协议使用 zk-SNARK 打破了存款人和提款人之间的链上关联。提款证明知道 Merkle 树中的一个有效承诺,但不揭示是哪一个。问题是:提款人的地址是全新的,没有 ETH 用于支付 Gas。今天,中继器(中心化、可审查的第三方,通常在需要时离线)通过赞助提款交易来弥补这一差距。
EIP-8141 改变了经济模式。框架交易的 VERIFY 帧作为 STATICCALL 运行:只读,无状态变更。如果 VERIFY 在支付获得批准之前回滚,则该交易在协议级别无效,永远不会进入区块,也不会向任何人收取 Gas。隐私提款变为:
VERIFY帧(tx.sender = pool,因此帧针对池本身的存储):从SENDER帧的 calldata 读取 publicInputs,根据池存储的历史记录验证 Merkle 根(SLOAD),确认 nullifier 尚未被使用(SLOAD),并针对这些 publicInputs 执行 Groth16 配对检查。如果一切通过,则调用APPROVE。SENDER帧:将 nullifier 标记为已使用,将净额转账给接收者,并将费用记入池内的赞助方。
关键的是,费用不再需要来自外部赞助方。提款本身可以支付执行费用:SENDER 帧可以将部分提款资金用于支付 Gas,从而消除了对预先注资的发送方或第三方中继器的需求。赞助方的份额作为内部信用保留在池中,可稍后领取,因此提款只产生一次对外转账,而不是两次。
| # | 模式 | 子类 | 目标 | 调用者 | 标志 | 数据 | 角色 |
|---|---|---|---|---|---|---|---|
| 0 | VERIFY |
only_verify |
池(null → tx.sender) |
ENTRY_POINT |
APPROVE_EXECUTION |
SNARK 证明 | 从帧 2 的 calldata 读取 publicInputs(root, nullifier, recipient, amount, sponsor, fee),SLOAD 检查 acceptedRoots[root] 和 !nullifierHashes[nullifier],验证证明,APPROVE(APPROVE_EXECUTION) |
| 1 | VERIFY |
pay |
赞助方 | ENTRY_POINT |
APPROVE_PAYMENT |
(空/赞助方策略数据) | 内省帧 2:target = 池,selector = withdraw,编码的 sponsor = 自身,编码的 fee ≥ MIN_FEE。APPROVE(APPROVE_PAYMENT) — 此处扣除赞助方的 ETH |
| 2 | SENDER |
user_op |
池(null → tx.sender) |
池(tx.sender) |
APPROVE_SCOPE_NONE |
withdraw(publicInputs) |
将 nullifierHashes[nullifier] 标记为 true,ERC20.transfer(recipient, amount − fee),sponsorCredits[sponsor][token] += fee |
对于无效证明(VERIFY 回滚,交易被丢弃)和重放证明(nullifier 已标记,VERIFY 回滚),赞助方的风险为零。无需信任,无需中继基础设施,也没有额外的审查面。
三个包含门
面向隐私的框架交易通往抗审查的路径需要经过三个独立的门,每个门都有其自身的约束:
门 1:公共内存池准入
EIP-8141 的内存池规则,借鉴了 ERC-7562 但完全去掉了质押和声誉,定义了哪些交易可以通过公共 P2P 网络传播:
- 验证前缀必须匹配已识别的模式(
self_verify或only_verify+pay,可选地在前面加上deploy) SLOAD仅限于tx.sender的存储- 总
VERIFYGas 上限为MAX_VERIFY_GAS(100,000) - 禁止的操作码:
TIMESTAMP、NUMBER、BLOCKHASH、BALANCE、SELFBALANCE、SSTORE、TLOAD、TSTORE等 - 规范的付款方通过精确的运行时代码哈希识别,具有时间锁提款和节点端待处理余额跟踪
- 非规范付款方最多允许
MAX_PENDING_TXS_USING_NON_CANONICAL_PAYMASTER(1)个待处理交易
任何违反这些规则的交易将被拒绝进入公共内存池,但仍可通过替代内存池或私人渠道到达构建者。
门 2:FOCIL 执行
FOCIL 保证交易包含:每个插槽有 16 名 IL 委员会成员,根据他们对待处理交易的视图构建包含列表。证明者只投票给包含 IL 交易(或在后状态下证明其无效)的区块。
对于 EOA,FOCIL 的遗漏检查是对 post-state 进行的廉价 nonce/balance 查找。对于 FrameTxs,遗漏检查需要重放 VERIFY 前缀。没有廉价的代理,因为交易有效性取决于执行,而不仅仅是发送方状态。FOCIL-frame-txs 提案 通过五个约束定义了资格:
- 验证前缀排序:
VERIFY帧必须在DEFAULT/SENDER帧之前 - 每笔交易的
VERIFYGas 上限:verify_gas(tx) <= MAX_VERIFY_GAS_PER_FRAMETX(100,000) - 每个 IL 的
VERIFY预算:整个 IL 中累计的VERIFYGas 上限为MAX_VERIFY_GAS_PER_INCLUSION_LIST(250,000) - 基本交易健全性:
chain_id、费用、无 blobs - 有限状态访问:
VERIFY只能读取tx.sender和付款人账户状态(balance、nonce、code)以及它们的前N个存储槽(N= 2-4),与 AA-VOPS 缓存对齐。从任何其他合约读取存储将使交易失去资格。
违反任何约束的 FrameTxs 将被免除 FOCIL 执行:它们的遗漏不能归咎于构建者。
门 3:节点验证能力
术语:
PS = 部分无状态:持有部分状态,而非全部
VOPS = 仅有效性 PS:持有足够的状态以验证来自 EOA 的交易
AA-VOPS = VOPS + 每个账户的几个存储槽
后 ZKEVM 时代,节点不需要持有完整状态。例如,VOPS 节点仅存储约 8.4 GB(每个账户的 nonce、balance、codeHash)。AA-VOPS 通过为 trie 中的每个账户维护前 N 个存储槽来扩展这一点(因此某些 tx.sender 和付款人槽位在本地可用于 VERIFY 重放)。部分有状态(PS)节点额外跟踪所选合约的存储。节点类型决定了它可以本地重放哪些 VERIFY 帧,因此也决定了它可以接纳到其内存池并作为 FOCIL 类包含列表来源的交易类别:
| 能力 | 全节点 | PS 节点 | AA-VOPS | VOPS |
|---|---|---|---|---|
EOA nonce/balance 检查 |
是 | 是 | 是 | 是 |
AA 钱包 VERIFY(tx.sender 存储) |
是 | 如果跟踪 | N 槽子集 |
否 |
| 规范付款方准入(代码哈希匹配 + 余额预留) | 是 | 是 | 是 | 是 |
| 规范付款方 VERIFY 追踪重放 | 是 | 如果跟踪 | 否 | 否 |
| 隐私池存储(根、nullifiers) | 是 | 如果跟踪 | 否 | 否 |
无法验证某类交易的节点无法在其内存池中维护该类交易,也不应将其包含在 IL 中。该类交易的抗审查能力随有能力验证的验证者比例下降。
参见 通过无状态视角看框架交易 以获取关于哪些节点类型可以支持哪些 EIP-8141 内存池策略的详细分析。
为什么隐私交易会失败所有三个门
隐私提款在默认参数下会失败所有三个门:
门 1(公共内存池):由于 tx.sender = pool,VERIFY 帧对池的 Merkle 根历史和 nullifierHashes 映射的 SLOAD 满足仅 tx.sender 存储的限制,但 Groth16 配对检查超过了 EIP-8141 的验证追踪规则中定义的 100k MAX_VERIFY_GAS 上限。被拒绝。
门 2(FOCIL 资格):Groth16 配对检查超过了每笔交易的 100k VERIFY Gas 预算。即使 tx.sender = pool 使池的存储与有限状态访问规则一致,Gas 上限本身就使交易失去资格。免除执行。
门 3(节点能力):VOPS 和 AA-VOPS 节点不持有池合约存储。只有跟踪池的 PS 节点或全节点才能验证。
| 交易类型 | 公共内存池 | FOCIL 资格 | VOPS | AA-VOPS | PS(跟踪池) |
|---|---|---|---|---|---|
| EOA 框架交易(ECDSA/P256) | 是 | 是 | 是 | 是 | 是 |
智能钱包(tx.sender 存储,<= N 槽) |
是 | 是 | 否 | 是 | 是 |
| 规范付款方赞助 | 是 | 是 | 否 | 否 | 如果跟踪 |
| 隐私提款 | 否 | 否 | 否 | 否 | 如果跟踪 |
因此,隐私交易被 排除 在所有默认的抗审查路径之外。最需要抗审查的交易正是当前设计无法保护的那些。
这并非不可避免。差距源于 FOCIL 如何执行 FrameTxs 的包含,而 FOCIL-frame-txs 提案 提供了两种方法,对可执行的内容有不同的影响。两者之间的选择决定了隐私相关的 FrameTxs 是否有通往抗审查的路径。
FOCIL 执行:两种方法
两种方法都解决同一个问题:构建者如何证明他们没有审查符合条件的 IL FrameTx,以及证明者如何验证这些声明。
append-loop 方法
默认方法 让构建者迭代地将排除的符合条件的 IL FrameTxs 附加到区块中,直到在最终的 post-state 中没有遗漏的 FrameTx 有效。成本是排除的 FrameTxs 数量的 O(k²)。这种二次构建者成本迫使采用保守的参数:MAX_VERIFY_GAS_PER_FRAMETX 为 100_000,MAX_VERIFY_GAS_PER_INCLUSION_LIST 为 250_000。
假设 25% 的恶意 IL 委员会(16 名成员中有 4 名,这在 1% 的质押份额下大约每月发生一次)和约 100 字节的交易:每个 IL 在 MAX_BYTES_PER_INCLUSION_LIST(8 KiB)内可容纳约 81 笔交易,但每个 IL 的 VERIFY Gas 预算仅允许 2 笔消耗 MAX_VERIFY_GAS_PER_FRAMETX Gas 的 FrameTxs(2 * 100k = 200k <= 250k)。4 个恶意 IL 产生 k=8 个无效 FrameTxs。append loop 运行 k(k+1)/2 = 36 次 VERIFY 重放:3.6M Gas(约区块的 6%)。证明者工作量:4 * 250k = 1M Gas(约 1.7%)。开销适中,但这些参数使 FOCIL 对 FrameTxs 基本无能为力:MAX_VERIFY_GAS_PER_FRAMETX 为 100k 排除了 Groth16 证明、PQ 签名以及任何非平凡的验证逻辑,而 MAX_VERIFY_GAS_PER_INCLUSION_LIST 为 250k 将每个 IL 限制为仅 2 个全 Gas 的 FrameTxs。一个只能容纳 2 个可执行 FrameTxs 的 IL 几乎无法执行账户抽象。
validation-index 方法
后续方法 消除了 append loop。取而代之的是,构建者通过发布一个 (tx_hash, claimed_index) 对来 免责 每个排除的交易,声明该交易在哪个区块索引处变得无效。证明者使用区块访问列表(EIP-7928)在 claimed_index 处重建状态,并在那里重放 VERIFY 前缀。如果 VERIFY 成功,则构建者撒谎,区块不满足 IL 条件。构建者成本从 O(k²) 降至 O(k)。代价是增加了协议复杂性:索引声明是一个额外的网络工件,engine_newPayload 需要能够接受它们以及 IL 交易,并且执行层需要相应地验证它们。
由于构建者成本不再是瓶颈,MAX_VERIFY_GAS_PER_INCLUSION_LIST 可以提高到 2^20(约 1M Gas),每笔交易的上限变得不那么关键,因为每个排除的交易在其 claimed_index 处只被验证一次,而不是迭代验证。每个 IL 的 2^20 Gas 足以让隐私协议的交易受益于 FOCIL 的抗审查保证。
同样的 25% 恶意场景:现在每个 IL 在每笔交易 100k Gas 的预算下最多可容纳 10 笔 FrameTxs(10 * 100k = 1M <= 2^20)。四个恶意 IL × 10 = 40 个排除的 FrameTxs。构建者成本是线性的:40 * 100k = 4M Gas(约区块的 6.7%)。证明者成本:4 * 2^20 ≈ 4.2M Gas(约 7%)。所有 16 个 IL 的最坏情况:16 * 2^20 = 2^24 ≈ 16.8M Gas(约 28%)。每个 IL 预算增加约 4 倍完全消除了二次构建者的激增。
节点能力作为限制因素
索引方法开启了公共内存池规则和 FOCIL 执行之间的分离。公共内存池规则必须严格,因为交易可能需要在每个区块后重新验证,因此状态依赖必须小而确定。验证索引方法下的 FOCIL 在固定的 claimed_index 处恰好重放 VERIFY 一次。没有持续的维护成本,这正是它能承受更广泛状态访问和更高 Gas 预算的原因。反过来,FOCIL 验证位于证明者关键路径上(在 t=4s 证明截止时间内),而内存池验证是异步运行的。更高的 MAX_VERIFY_GAS_PER_INCLUSION_LIST 直接消耗证明者的时间预算。
这意味着 IL 委员会成员可以从替代内存池(包括面向隐私的)获取交易并将其包含在他们的 IL 中,即使默认的公共内存池不携带它们。但要使这对隐私交易有效,需要放宽有限状态访问规则(仅限 tx.sender 和付款人账户的读取)。并且执行包含的节点需要实际持有状态来验证这些交易。
AA-VOPS 无法弥合这一差距。它缓存每个账户的 N 个存储槽,这对简单的钱包验证(所有者密钥、nonce)足够,但对隐私协议不够。隐私交易相关的 VERIFY 帧读取池合约存储(根环形缓冲区、nullifier 映射),而不是发送方存储。任何 N 值都无法解决这个问题:读取的目标是另一个完全不同的合约。即使 AA-VOPS 为每个合约缓存了 N 个槽,也无济于事:nullifier 映射由哈希键控,因此访问的槽是不可预测的,并且分散在存储 trie 中。
一个规范的隐私池
最实际的解决方案是一个规范池模式,类似于规范付款方。一个由代码哈希识别的规范合约,可以作为多个隐私协议的共享注册表:以统一、可审计的布局存储每个池的仅追加 Merkle 根和 nullifier 集。设计得当,这样的池对于公共内存池和 FOCIL 执行都是安全的:针对此合约的 VERIFY 帧恰好读取两个存储槽:acceptedRoots[R] 和 nullifierHashes[h],两者都在 calldata 可导出的键上。访问模式是有限的、可预测的,并且是_单调的_:VERIFY 读取的每个槽只经历 false → true 的转换。它不需要节点跟踪 N 个不同的池合约,而是将问题简化为一个已知地址和已知存储布局的注册表,使得部分有状态对于隐私在最小成本下变得实用。
单调状态使设计能够抵抗大规模失效。今天的隐私协议使用一个最近 Merkle 根的滚动环形缓冲区,在每次存款时驱逐最旧的条目。在 EIP-8141 下,这种驱逐是一个大规模失效向量:一次存款(或攻击者垃圾邮件)可以轮换出一个依赖于数十个待处理赞助提款的根,同时将它们全部从内存池中驱逐。用仅追加的 acceptedRoots 映射替换环形缓冲区完全消除了这个向量。针对任何历史根生成的提款证明永远有效;nullifier 集已经防止了双花,因此接受旧根在密码学上是安全的。可能使待处理提款无效的唯一状态变化是对其_特定_ nullifierHashes[h] 槽的写入,这需要拥有票据的秘密。这是每笔交易且针对单独目标的,绝不是大规模的。
一个实现细节:当前隐私协议中常用的 Merkle 根环形缓冲区必须替换为仅追加映射,以避免大规模失效。就大小而言,这没有问题:例如,最常用的 Tornado Cash 合约(1-ETH 池)有 81,881 次存款。这大约是 10.5 MB 的原始键值数据(每个在
acceptedRoots和nullifierHashes中有 81,881 个条目,每个条目约 64 字节),或者在状态 trie 中存储后约为 25-40 MB。跟踪此池的 PS 节点承诺的额外状态远低于 50 MB,相对于约 8.4 GB 的 VOPS 基线来说微不足道。
每个插槽有 16 名 IL 委员会成员,FOCIL 只需要 1/16 的诚实成员就能保证包含。但这里的“诚实”也意味着“有能力”:一个希望包含隐私交易但运行 VOPS 或 AA-VOPS 节点的成员根本无法验证它。1/16 的诚实假设变成了 1/16 的_能力_假设。PS 节点解决了这个问题。它们不需要完整状态,只需要它们关心的合约。一个跟踪一个规范隐私池的 PS 节点最多增加几 MB。基础设施成本微不足道;问题在于是否有足够的验证者选择这样做。
规范隐私池的替代方案是让交易发送者附带所需的状态(存储)以及针对最新状态根的 Merkle 证明。这种方法由于几个原因存在不足,最重要的是这些证明在每个区块后都会失效,必须不断重新计算。
提议的变更
- 将规范合约例外扩展到隐私池。 EIP-8141 已经通过运行时代码哈希匹配承认了规范付款方,免除了它们严格的公共内存池规则,因为它们的状态依赖已知是安全的。
- 提高规范合约帧的每笔交易
VERIFYGas 上限。MAX_VERIFY_GAS = 100_000不足以满足 Groth16(约 250k)。规范合约具有固定的验证路径,因此它们最坏情况的VERIFY成本是代码哈希的静态属性。无效证明消耗的 Gas 与有效证明相同,然后被丢弃,没有放大效应。为通用帧保留 100k;为规范合约帧允许例如 ~400k,留出足够空间,并在内存池中限制其数量。 - 采用验证索引 FOCIL 执行,接受增加的协议复杂性(
(tx_hash, claimed_index)映射)以消除迫使FrameTx采用保守 Gas 预算的二次构建者开销。 - 将
MAX_VERIFY_GAS_PER_INCLUSION_LIST提高到2^20(=1M),让具有更高VERIFYGas(Groth16、PQ 签名)的单个交易适合每个 IL 的预算。 - 放宽规范隐私池合约的有限状态访问规则,允许 IL 委员会成员从替代内存池获取这些交易,并使运行跟踪这些合约的 PS 节点的证明者能够执行它们的包含。
这些措施共同为隐私 FrameTxs 提供了一条可行的抗审查路径:在规范合约例外下通过公共内存池传播,由具备 PS 能力的 IL 成员包含,以及通过验证索引方法进行 FOCIL 执行,同时不削弱非规范 FrameTxs 的 DoS 抵抗能力。
类似的论点也适用于后量子交易。PQ 签名很大且验证成本高昂,100k
VERIFYGas 不足以覆盖。与隐私协议一样,PQ 交易需要从统一上限中豁免,要么通过规范化验证合约,要么添加规范预编译。
当前 FrameTxs 的公共内存池规则、VOPS 和 AA-VOPS 对隐私协议来说过于僵化。规范化隐私池可以改变这一点。通过代码哈希识别它们,公共内存池可以安全地验证它们的 VERIFY 帧,而跟踪一小部分高价值合约(隐私池、规范付款方)的 PS 节点可以为它们构建包含列表。它带来的好处是:为最需要抗审查的交易提供抗审查能力。
附录
为什么替代内存池会失败
- 碎片化不具组合性:每个需要更重验证的交易(隐私、PQ 等)可能都需要有自己的替代内存池。节点运营商挑选赢家,支持这些节点的对等体数量会减少,最弱的替代内存池就成为依赖它的任何东西的审查向量。没有内置激励,替代内存池只能通过要求志愿者无私地运行来扩展,甚至可能要求他们购买更强大、更昂贵的硬件。
- 匿名集崩溃:匿名集从“所有以太坊节点”降级为“支持隐私内存池的节点”,并且对等连接在网络层面泄露。
- 在网络层面极易审查:单用途替代内存池的引导节点、DNS 记录和对等列表是 ISP 或国家可以通过一条规则阻断的瓶颈。公共内存池难以审查,因为它对整个网络具有承载作用;替代内存池则不然。隐私流量最终落在结构上比它取代的中继器更容易离线的基础设施上。
- FOCIL 的 1/16 假设被打破:及时包含仅在至少一名 IL 委员会成员与替代内存池对等_并且_能够验证交易时成立。“1/16 诚实”的假设变成了“1/16 诚实、订阅且有能力”,这比 EIP-7805 所承诺的要弱得多。如果 IL 构建者被宣传为低硬件节点,这尤其成问题。
- 中继器并未消失,只是转移了:替代内存池的交易仍然需要到达构建者。跨边界转发的桥接节点正是框架交易本应消除的信任和审查面,只是向下移动了一层。
Frame Tx <> 公共内存池决策树:
规范隐私池示例
关于规范隐私协议实现的示例(使用伪代码),请查看此处。
- 原文链接: ethresear.ch/t/frame-tra...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~

