文章探讨了如何利用 EIP-8141(Frame 交易)消除隐私协议中的中继器,并分析了隐私交易在公共内存池准入、FOCIL 包含列表强制执行以及无状态节点验证这“三重门”中面临的挑战。作者提出了建立规范隐私池模式及优化 FOCIL 执行机制等建议,以增强隐私交易的抗审查性。
这篇文章将 EIP-8141 视为既定前提,而不是争论其优劣。目标是展示 Frame 交易如何改进隐私协议,以及公共内存池(mempool)规则和包含列表(inclusion list)强制执行需要做哪些改变才能使其真正发挥作用。
Frame 交易(EIP-8141)有望从隐私协议中消除中继者(relayers)。但是,管理公共内存池准入、FOCIL 强制执行以及无状态路线图的规则,各自对其支持的交易划定了不同的界限。本文分析了这些界限在哪里交汇,以及在哪里发生冲突,特别是对于保护隐私的交易而言。
像 Tornado Cash 和 Railgun 这样的隐私协议使用 zk-SNARKs 打破了存款人和提款人之间的链上联系。提款过程证明了对 Merkle 树中有效承诺的了解,而无需透露是哪一个。问题在于:提款者的地址是全新的,没有用于 Gas 的 ETH。如今,中继者(中心化的、可审查的第三方,通常在需要时离线)通过赞助提款交易来弥补这一差距。
EIP-8141 改变了经济模式。一个 Frame 交易的 VERIFY frame 以 STATICCALL 形式运行:只读,没有状态变更。如果 VERIFY 在付款被批准之前回滚(revert),则该交易在协议级别是无效的,永远不会进入区块,并且不会向任何人收取 Gas。一个隐私提款变为:
VERIFY frame(带有 tx.sender = pool,因此该 frame 针对矿池自身存储):从 SENDER frame 的 calldata 中读取 publicInputs,根据矿池存储的历史记录(SLOAD)验证 Merkle 根,确认 nullifier 尚未被使用(SLOAD),并针对这些 publicInputs 执行 Groth16 配对检查。如果一切通过,调用 APPROVE。SENDER frame:将 nullifier 标记为已使用,将净金额转移给接收者,并在矿池内部将费用记入赞助商的贷方。至关重要的是,费用不再需要来自外部赞助商。提款本身可以支付执行费用:SENDER frame 可以划拨一部分提款资金来覆盖 Gas,从而消除了对预注资发送者或第三方中继者的需求。赞助商的分成留在矿池中作为内部信用,稍后可以领取,因此提款只会发出一次出站转账而不是两次。
VERIFYonly_verifynull → tx.sender)ENTRY_POINTAPPROVE_EXECUTIONpublicInputs(root, nullifier, recipient, amount, sponsor, fee),执行 SLOAD acceptedRoots[root] 和 !nullifierHashes[nullifier],验证证明,APPROVE(APPROVE_EXECUTION)VERIFYpayENTRY_POINTAPPROVE_PAYMENTtarget = pool,selector = withdraw,编码的 sponsor = self,编码的 fee ≥ MIN_FEE。APPROVE(APPROVE_PAYMENT) — 在此处扣除赞助商的 ETHSENDERuser_opnull → tx.sender)tx.sender)APPROVE_SCOPE_NONEwithdraw(publicInputs)nullifierHashes[nullifier] = true,ERC20.transfer(recipient, amount − fee),sponsorCredits[sponsor][token] += fee对于无效证明,赞助商的风险为零(VERIFY 回滚,交易丢弃);对于重放证明,赞助商的风险也为零(nullifier 已被标记,VERIFY 回滚)。不需要信任,不需要中继者基础设施,也没有额外的审查层面。
一个以隐私为重点的 Frame 交易的抗审查之路要经过三个独立的关卡,每个关卡都有其自身的约束条件:
EIP-8141 的内存池规则借鉴了 ERC-7562 但完全剥离了质押和声誉,定义了什么可以通多公共 P2P 网络传播:
self_verify 或 only_verify + pay,可选地在前面加上 deploy)SLOAD 仅限于 tx.sender 存储VERIFY Gas 上限为 MAX_VERIFY_GAS (100,000)TIMESTAMP, NUMBER, BLOCKHASH, BALANCE, SELFBALANCE, SSTORE, TLOAD, TSTORE 等MAX_PENDING_TXS_USING_NON_CANONICAL_PAYMASTER (1) 个待处理交易任何违反这些规则的内容都会被公共内存池拒绝,但仍可以通过替代内存池(alt-mempools)或私有通道到达构建者(builders)。
FOCIL 保证交易包含:每个插槽(slot)有 16 个包含列表(IL)委员会成员根据他们看到的待处理交易构建包含列表。证明者(attesters)仅投票给包含 IL 交易的区块(或证明其在后置状态下无效)。
对于 EOA,FOCIL 的遗漏检查是对后置状态进行廉价的 nonce/balance 查询。对于 FrameTxs,遗漏检查需要重新运行 VERIFY 前缀。没有廉价的代理指标,因为交易有效性取决于执行,而不仅仅是发送者状态。FOCIL-frame-txs 提案通过五个约束条件定义了资格:
VERIFY frames 必须先于 DEFAULT/SENDER framesVERIFY Gas 有界:verify_gas(tx) <= MAX_VERIFY_GAS_PER_FRAMETX (100,000)VERIFY 预算:跨 IL 的累积 VERIFY Gas 上限为 MAX_VERIFY_GAS_PER_INCLUSION_LIST (250,000)chain_id,费用,无 blobsVERIFY 只能读取 tx.sender 和付款人账户状态(balance, nonce, code)以及它们的前 N 个存储槽(N = 2-4),与 AA-VOPS 缓存保持一致。从任何其他合约进行的存储读取都将使交易失去资格。违反任何约束条件的 FrameTxs 都会从 FOCIL 强制执行中豁免:它们的遗漏不能作为针对构建者的理由。
术语:
PS = 部分无状态(Partial Statelessness):持有部分状态,而非全部
VOPS = 仅有效性无状态(Validity-Only PS):持有足够的状态来验证来自 EOA 的交易
AA-VOPS = VOPS + 每个账户几个存储槽
在 ZKEVM 之后,节点不需要持有完整状态。例如,VOPS 节点仅存储约 8.4 GB(每个账户的 nonce, balance, codeHash)。AA-VOPS 通过在 trie 中维护每个账户的前 N 个存储槽来扩展这一点(因此某些 tx.sender 和付款人槽可用于本地 VERIFY 重播)。部分无状态(PS)节点还会额外跟踪选定合约的存储。节点类型决定了它可以本地重播哪些 VERIFY frames,从而决定了它可以准入哪些交易类别到其内存池并将其源引入类似 FOCIL 的包含列表中:
| 能力 | 全节点 | PS 节点 | AA-VOPS | VOPS |
|---|---|---|---|---|
EOA nonce/balance 检查 |
是 | 是 | 是 | 是 |
AA 钱包 VERIFY (tx.sender 存储) |
是 | 如果被跟踪 | N 个槽子集 |
否 |
| 规范支付者准入 | 是 | 是 | 是 | 是 |
| 规范支付者 VERIFY 追踪重放 | 是 | 如果被跟踪 | 否 | 否 |
| 隐私池存储 (roots, nullifiers) | 是 | 如果被跟踪 | 否 | 否 |
无法验证某种交易类型的节点无法在其内存池中维护该交易,也不应将其包含在 IL 中。该类别的抗审查性随着有能力的验证者比例的减少而降低。
在默认参数下,隐私提款无法通过所有三道关卡:
关卡 1(公共内存池):当 tx.sender = pool 时,VERIFY frame 对矿池 Merkle 根历史和 nullifierHashes 映射的 SLOAD 满足了仅限 tx.sender 存储的限制,但 Groth16 配对检查超过了 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 frame tx (ECDSA/P256) | 是 | 是 | 是 | 是 | 是 |
智能钱包 (tx.sender 存储) |
是 | 是 | 否 | 是 | 是 |
| 规范支付者赞助 | 是 | 是 | 否 | 否 | 如果被跟踪 |
| 隐私提款 | 否 | 否 | 否 | 否 | 如果被跟踪 |
因此,隐私交易被排除在抗审查的每一条默认路径之外。最需要抗审查的交易正是当前设计无法保护的交易。
默认方法是让构建者反复将排除的符合条件的 IL FrameTxs 追加到区块中,直到没有被遗漏的 FrameTx 在最终的后置状态下有效。成本在被排除的 FrameTxs 数量上是 O(k²)。这种二次方构建者成本迫使参数趋于保守:MAX_VERIFY_GAS_PER_FRAMETX 为 100,000,MAX_VERIFY_GAS_PER_INCLUSION_LIST 为 250,000。
假设有 25% 的对抗性 IL 委员会,每个 IL 可以在 MAX_BYTES_PER_INCLUSION_LIST 内容纳约 81 笔交易,但每个 IL 的 VERIFY Gas 预算仅允许 2 笔具有 MAX_VERIFY_GAS_PER_FRAMETX Gas 的 FrameTxs。开销虽适中,但这些参数使 FOCIL 对 FrameTxs 几乎毫无用处:100k 的 MAX_VERIFY_GAS_PER_FRAMETX 排除了 Groth16 证明和 PQ 签名。
后续方案消除了追加循环。取而代之的是,构建者通过发布一个 (tx_hash, claimed_index) 对来豁免每个被排除的交易,声明该交易在哪个区块索引处变得无效。证明者使用区块访问列表(EIP-7928)在 claimed_index 处重建状态并在此处重播 VERIFY 前缀。构建者成本从 O(k²) 降至 O(k)。
由于构建者成本不再是瓶颈,MAX_VERIFY_GAS_PER_INCLUSION_LIST 可以提高到 2^20(约 100 万 Gas)。这将高到足以让隐私协议的交易从 FOCIL 的抗审查保证中受益。
索引法开启了公共内存池规则与 FOCIL 强制执行之间的分离。公共内存池规则必须严格,因为交易可能在每个区块之后都需要重新验证。而在验证索引法下的 FOCIL 仅在一个固定的 claimed_index 处重播一次 VERIFY。
这意味着 IL 委员会成员可以从替代内存池中获取交易并将其包含在他们的 IL 中,即使默认的公共内存池不携带这些交易。但对于隐私交易来说,这需要放宽有界状态访问规则。AA-VOPS 无法弥补这一差距,因为隐私交易相关的 VERIFY frames 读取的是矿池合约存储,而不是发送者存储。
最实际的解决方案是规范池(canonical pool)模式,类似于规范支付者。一个通过代码哈希识别的规范合约可以作为多个隐私协议的共享注册表。如果设计得当,这样的池对于公共内存池和 FOCIL 强制执行都是安全的:针对该合约的 VERIFY frames 正好读取两个存储槽:acceptedRoots[R] 和 nullifierHashes[h]。
单调状态(Monotone state)是该设计能够抵抗大规模失效(mass-invalidation-resistant)的原因。用只增不减的 acceptedRoots 映射替换环形缓冲区(ring buffer),可以消除单次存款可能换出待处理提款所依赖的根的向量。
每个插槽有 16 个 IL 委员会成员,FOCIL 只需要 1/16 的诚实成员即可保证包含。但“诚实”也意味着“有能力”:一个想要包含隐私交易但运行 VOPS 节点的成员根本无法验证它。PS 节点解决了这个问题。一个跟踪一个规范隐私池的 PS 节点最多只增加几 MB。
VERIFY Gas 上限。 允许规范合约 frame 使用约 400k Gas 以适应 Groth16(约 250k)。MAX_VERIFY_GAS_PER_INCLUSION_LIST 提高到 2^20 (1M),让具有较高 VERIFY Gas 的单笔交易能够适应每个 IL 的预算。综合来看,这些措施为隐私 FrameTxs 提供了一条可行的抗审查路径:在规范合约异常下的公共内存池传播、由具备 PS 能力的 IL 成员包含,以及通过验证索引法进行的 FOCIL 强制执行。
有关规范隐私协议实现的示例(使用伪代码),请查看这里。
- 原文链接: ethresear.ch/t/frame-tra...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!
作者暂未设置收款二维码