EIP-8304与UTXO证明表:认证UTXO发现方案评估
状态与范围
EIP-8304 是一份草案提案。本文引用的原生 UTXO 设计是 Ethereum Research 的提案,而非最终确定的协议规范。本文描述集成的方案属于实验性质,不属于上述任一提案的组成部分。
已实现的配置包括:
- 标准的 EIP-8304 条目类型 0 至 6;
- 一个应用特定的四主题
UtxoCreated事件; - 使用格式版本 2 的逐区块 UPT 对象;
- 选定记录的多重证明(multiproofs);
- 以区块哈希为键的节点持久化;
- 批量 UPT 检索;
- 近期 openings 根检查;
- 进程内钱包缓存。
尚未包含:
- 独立认证的执行头;
- 钱包侧针对这些执行头验证账户和存储证明;
- 针对旧 openings 根的存档和批量路径服务;
- 标准化的 UPT RPC 或线上传输格式;
- UPT 可用性或保留网络;
- 无需收据即可构造最终每个输入的花费见证。
原型使用了 Ethrex 特定的 RPC,例如 ethrex_queryEip8304Table 和 ethrex_getUtxoProofs,以及早期全日志实验中的选定日志 RPC。这些并非标准的 Ethereum JSON-RPC 方法,也未由 EIP-8304 定义。
发现(Discovery)问题
假设 Alice 为 Bob 创建了一个 1 ETH 的 UTXO:
UtxoCreated(
source = Alice,
recipient = Bob,
index = 2191,
value = 1 ETH
)
Bob 的钱包至少需要获知:
- 全局 UTXO 索引;
- 来源方和接收方;
- 金额;
- 创建区块和事件位置;
- 该输出当前是否未被花费;
- 足够的认证证据,以拒绝伪造或遗漏的结果。
在本文中,EIP-8304 事件位置定义为:
(block_number, transaction_index, transaction_relative_log_index)
标准 JSON-RPC 日志对象使用区块相对的 logIndex,因此实现若需比较这两种表示形式,必须显式地进行归一化。
原生 UTXO 状态并不会完整保留每个 opening。它保留了每个全局索引的花费位,以及一个近期逐区块 openings 根的环。较旧的逐区块根可能会在之后被封装进批量根中。
一个 openings 根只有在记录和有效的 Merkle 路径都被获取之后,才能认证该记录。它不会揭示哪个 opening 属于 Bob,不会提供 opening 的金额,也不会提供缺失的兄弟哈希。
因此,发现过程包含四个不同的问题:
| 问题 | 所需证据 |
|---|---|
| Bob 在哪里被提及? | 一个完整的 EIP-8304 接收方范围证明 |
| 提交了哪些 opening 字段? | 一个针对区块原生 openings 根验证的 opening 记录证明 |
| 哪些 Merkle 材料可以重建该根? | 一个选定记录证明或共享的逐区块多重证明 |
| 该输出现在是否可花费? | 一个针对已认证规范状态验证的当前花费位值 |
当前设计中没有单一结构能够回答所有这四个问题。
将 EIP-8304 作为发现索引
EIP-8304 创建了有序的逐区块和聚合索引表。标准条目类型为:
| 类型 | 索引内容 |
|---|---|
| 0 | 区块哈希 |
| 1 | 交易哈希 |
| 2 | 发出日志的地址 |
| 3 | topics[0],通常是事件签名 |
| 4 | topics[1] |
| 5 | topics[2] |
| 6 | topics[3](如果存在) |
条目按其二进制编码的字典序排列。日志条目还携带其区块号、交易索引和交易相对日志索引。
这种排序允许服务器对每个包含 Bob 填充地址的类型 5 条目进行二分搜索。一个包含匹配区间、相邻的非匹配边界、相关叶子索引和表长度的证明,可以确定该表中没有匹配条目被遗漏。
仅凭接收方范围是不够的。任何合约都可以将 Bob 放入 topics[2]。在每个候选位置,钱包还必须至少认证:
- 类型 2:原生 UTXO 金库地址;
- 类型 3:
UtxoCreated事件签名。
当事件模式暴露更多 opening 字段时,可以请求其他主题条目。
原型探索的三种方法
以下是三种被评估的构造方案,并非所有可能的发现设计的详尽列表。
方法 1 — 标准 eth_getLogs
钱包使用以下条件在区块范围内请求日志:
- UTXO 金库地址;
UtxoCreated签名;- Bob 的接收方主题。
返回的日志包含解码 opening 所需的全部索引主题、事件数据、交易元数据和区块元数据。
此方法在操作上具有吸引力:
- 使用标准 RPC;
- 不需要新的可用性对象;
- 直接返回事件数据;
- 索引良好的提供商可以高效地回答常见查询。
其核心限制在于信任。eth_getLogs 并不证明提供商返回了完整的规范结果集。收据证明可以认证单个日志,但本身并不能证明范围内所有 Bob 事件都被返回。
在用于本地实验的 Ethrex 原型中,eth_getLogs 会检查区块头布隆过滤器,并为候选区块加载区块体和收据。因此,它应被描述为 Ethrex 收据扫描基线,而非对所有生产提供商日志索引架构的基准测试。
方法 2 — 带有自定义全日志条目的 EIP-8304
第一个经过认证的原型添加了一个非标准的类型 7 条目,用于提交完整的原始日志。概念上:
SHA256(
domain ||
log_address ||
encoded_topic_count || encoded_topics ||
encoded_data_length || data
)
一个可互操作的规范需要确定确切的域字节、整数宽度、字节顺序和列表编码。已实现的原型使用了域字符串 EIP8304_LOG_V1。
钱包首先证明完整的 Bob 接收方范围以及每个候选位置的相关条目。然后仅检索选定的原始日志,并针对其类型 7 承诺逐一检查。
这种构造提供了经过认证的日志载荷和范围完整性,但代价显著:
- 改变了 EIP-8304 的条目集合;
- 每个被索引的日志都会增加一个表条目;
- 选定的原始日志字节必须保持可用;
- 重复扫描仍会检索选定的日志,除非单独缓存;
- 自定义条目在载荷上是应用无关的,但在协议上是非标准的。
该原型作为比较点是有用的,但本文并不提议将其作为 EIP-8304 的扩展。
方法 3 — 带有 UTXO 证明表(UPT)的 EIP-8304
UPT 构造保持标准的 EIP-8304 类型 0 至 6 不变。
EIP-8304 仅用于搜索和范围完整性。Opening 数据则针对原生 UTXO openings 根单独认证。
这种分离反映了这些结构具有不同的排序和目的:
| 结构 | 排序 | 目的 | 认证承诺 |
|---|---|---|---|
| EIP-8304 表 | 编码条目内容和事件位置 | 查找每个提及 Bob 的事件 | EIP-8304 表根 |
| Opening 树 / UPT | 区块内的全局 UTXO 索引 | 恢复并证明 opening 字段 | 原生逐区块 openings 根 |
UPT 不是共识状态。它是一种内容和服务格式,用于已经由原生 openings 根提交的数据。
事件模式的区别
原型基准测试使用了这个应用特定的事件:
event UtxoCreated(
address indexed source,
address indexed recipient,
uint64 indexed index,
uint256 value
);
其日志布局为:
address = UTXO_VAULT
topics[0] = keccak256("UtxoCreated(address,address,uint64,uint256)")
topics[1] = left_pad_32(source)
topics[2] = left_pad_32(recipient)
topics[3] = left_pad_32(index)
data = uint256_be(value)
原生 UTXO 研究提案目前描述为:
topics = [UtxoCreated_signature, source, recipient]
data = (index, value)
这种差异影响的不仅仅是事件解码:
| 属性 | 当前研究提案 | 原型模式 |
|---|---|---|
source 可通过 EIP-8304 搜索 |
是,类型 4 | 是,类型 4 |
recipient 可通过 EIP-8304 搜索 |
是,类型 5 | 是,类型 5 |
index 可通过 EIP-8304 搜索 |
否 | 是,类型 6 |
| 每个事件额外的 EIP 表条目 | 否 | 是 |
| 从主题中精确关联索引到事件 | 否 | 是 |
在原型模式中,钱包会将 UPT 的 index、source 和 recipient 与同一事件位置上的类型 6、类型 4 和类型 5 条目进行交叉核对。
对于当前提案的模式,UPT 证明仍然认证 opening 字段,EIP 证明仍然认证由金库发出的 Bob 事件。然而,openings 叶子并不承诺交易相对事件位置。如果一个区块包含多个具有相同来源和接收方的输出,选择性服务可以在不破坏任一根的情况下置换它们的事件位置关联。
使用当前事件模式的生产设计必须决定是否需要精确的事件关联,如果需要,如何绑定它。选项包括收据/日志证明、在 opening 设计中显式提交位置、具有足够数据的规范派生规则,或更改事件模式。原型的类型 6 绑定不能作为当前原生提案的属性来呈现。
UTXO 证明表
Opening 树
Ethrex 原型将 opening 叶子定义为:
opening_leaf = keccak256(
uint64_be(index) ||
source ||
recipient ||
uint256_be(value)
)
其原像长度为 80 字节。叶子按全局 UTXO 索引排序,使用零哈希填充到下一个 2 的幂,并使用有序对进行折叠:
parent = keccak256(left || right)
这些字节级和填充规则是原型规则。它们必须成为原生 UTXO 规范的一部分,或者被该规范采用的任何规范 opening 树定义所取代。
记录格式
每个原型记录在传输编码前为 88 字节:
UtxoProofRecord
├── index: uint64 // 8 字节
├── source: address // 20 字节
├── recipient: address // 20 字节
├── value: uint256 // 32 字节
├── transaction_index: uint32 // 4 字节
└── transaction_relative_log_index: uint32 // 4 字节
--------
88 字节
区块级对象包含:
UtxoProofTable (原型格式版本 2)
├── format_version
├── chain_id
├── vault
├── block_number
├── block_hash
├── openings_root
├── records[] // 按全局 UTXO 索引升序
└── internal_nodes[] // 缓存的 opening 树内部哈希
对于 U > 0 条记录,设 P = next_power_of_two(U)。记录提供叶子原像,而原型存储 P - 1 个内部哈希。
内部节点数组是节点端证明生成的优化,并非加密必需的数据。保留所有记录的节点可以重建每个内部节点。完整的评估应衡量通过重建节省的存储与通过保留内部节点索引节省的 CPU 和延迟之间的权衡。
内容标识符
原型计算:
table_hash = SHA256("UPT_BLOCK_V2\0" || canonical_ssz_bytes)
表哈希标识一个完整的序列化 UPT 对象。它不是共识根,也不仅仅因为服务器将其与响应一起返回就认证该响应。
选定记录通过重新计算其 opening 叶子并将提供的多重证明折叠到独立认证的原生 openings 根来进行认证。表哈希对于内容寻址、清单、镜像或可用性网络仍然可能有用。
构建与持久化
区块执行后,原型 UPT 构建器:
- 从区块的收据中解析存活的
UtxoCreated日志; - 为每个解码的 opening 创建一条记录;
- 按全局 UTXO 索引对记录排序;
- 拒绝非连续的索引或重复的事件位置;
- 计算规范的 opening 叶子和根;
- 构建缓存的内部节点表示;
- 将对象以精确区块哈希为键存储在 RocksDB 中。
区块哈希键隔离了竞争分叉。规范的 RPC 处理在加载表之前解析当前规范哈希,尽管独立验证该哈希仍然是钱包的责任。
如果最近的 UPT 缺失,原型可以从保留的收据中重建它。这是一种实现便利,而非历史被修剪后的长期可用性解决方案。
接收方优先的查询协议
钱包不会请求表中每个金库地址或事件签名条目。这些范围随总 UTXO 流量扩展。它从一个完整的接收方范围开始:
ethrex_queryEip8304Table(
first_block,
table_size,
[{ typeId: 5, content: left_pad_32(Bob) }],
[2, 3, 4, 6]
)
[2, 3, 4, 6] 候选字段适用于原型的四主题事件。针对当前原生提案的查询不会请求类型 6。
对于每个表,服务返回:
- 每个匹配的类型 5 接收方条目;
- 存在时的紧邻上下范围边界;
- 每个匹配位置请求的候选条目类型;
- 关联交易哈希所需的交易条目;
- 表长度和叶子位置;
- 一个共享的表多重证明。
钱包验证表证明、完整的接收方区间和候选分类。它仅保留发出地址和签名标识原生 UTXO 事件的位置。
批量选定记录检索
在 EIP-8304 验证之后,原型在一个请求中发送所有未缓存的位置,受实现上限约束:
ethrex_getUtxoProofs([
{
blockNumber,
transactionIndex,
logIndex // 交易相对
},
...
])
服务器按区块对位置进行分组并返回:
blocks[]
├── formatVersion, chainId, vault
├── blockNumber, blockHash
├── openingsRoot, rootStorageSlot
├── tableHash, total recordCount
├── selected records[]
└── one shared proofNodes[] set
对于每个返回的区块,钱包必须验证:
- 预期的格式、链 ID、金库和区块分组;
- 每个请求的事件位置恰好产生一条记录;
- 没有返回意外或重复的记录;
- 所选事件模式下所有可用的主题字段与 EIP 证明匹配;
- opening 叶子可以从索引、来源、接收方和金额重新计算;
- 每个必需的多重证明节点都存在;
- 不接受未使用的证明节点;
- 多重证明折叠到已认证的 openings 根。
当前原型从其执行 RPC 获取 opening 根存储值。生产轻钱包必须额外针对可信执行头验证金库账户和存储证明。
信任根
完整的生产验证链为:
没有认证头和状态证明步骤,钱包仅证明提供商返回了相互一致的根和数据。它并不证明与规范 Ethereum 链的一致性。
这一区别同样适用于 EIP-8304 表根、openings 根、用于缓存的区块哈希以及花费位。
钱包缓存与重组处理
原型缓存:
- 按范围和表末尾区块哈希验证的 EIP-8304 查询结果;
- 按区块哈希、存储槽和根进行的表根查找;
- 按精确区块哈希和事件位置验证的 UPT 记录。
在重用表查询之前,它会重新读取表的末尾区块哈希。如果哈希发生变化,它会清除所有发现缓存。由于后代区块承诺其父链,覆盖范围内任何区块的变化也会改变后续的规范哈希。
此检查对于本地缓存失效很有用,但当两个哈希值来自同一 RPC 时,它本身并非无需信任的。生产钱包应将缓存键与已认证的规范头进行比较,并为已最终确定、安全和未最终确定的历史定义明确的策略。
缓存并非 UPT 独有。基于收据的钱包也可以保留解码的 openings,并且仅重新验证最近的后缀。任何未来的性能比较都应给予两种方法同等的持久化和重组策略。
端到端生命周期
1. 创建
- Alice 或协议结算为 Bob 创建输出。
- 金库分配下一个全局 UTXO 索引。
- 一个存活的
UtxoCreated事件记录该 opening。 - 如果执行回滚,日志和 opening 都不会存在。
Opening 的 source 是 UTXO 转换记录的主体,不一定是外部交易的发送者。这对于赞助交易和协议结算很重要。
2. 区块承诺
- EIP-8304 从规范收据中派生标准地址和主题条目。
- 条目被排序到相关的 EIP-8304 表中。
- 原生 UTXO 处理按全局索引对 openings 排序,并提交逐区块 openings 根。
- 非共识的 UPT 对象被构建并以区块哈希为键存储。
EIP-8304 和 openings 树源自相同的执行历史,但认证不同的声明。
3. 发现
- Bob 的钱包将其请求的历史分解为可用的对齐 EIP-8304 表。
- 它证明每个完整的 Bob 接收方范围。
- 它按金库和事件签名对候选位置进行分类。
- 它检索这些位置的选定 UPT 记录。
- 它获取并认证相关的 openings 根。
- 它验证 opening 多重证明和模式相关的连接。
- 它在已认证的区块身份下缓存已验证的记录。
4. 可花费性与花费
UPT 不存储花费状态,因为花费状态随时间变化。钱包为每个发现的全局索引获取当前花费位,并针对已认证的当前状态进行验证。
花费输入最终需要等同于以下数据:
index
creation_block
source
recipient
value
opening_position
opening_siblings
batch_siblings
对于最近的 UTXO,opening 证明终止于仍在近期环中的根。对于较旧的 UTXO,见证还需要从该逐区块根到其封装的批量根的路径。
当前的演示交易伪造器尚未将返回的共享 UPT 多重证明转换为每个输入的规范花费见证。它仍然读取创建区块的日志并重建 opening 树。因此,原型演示了发现证明的组合,而非完整的无收据花费流水线。
完整的 opening 证明示例
假设一个区块按全局索引升序创建了四个 UTXO:
position 0: #40 Alice -> Dave 2 ETH
position 1: #41 Carol -> Erin 0.5 ETH
position 2: #42 Alice -> Bob 1 ETH
position 3: #43 Frank -> George 3 ETH
在原型模式下,Bob 事件位置的 EIP-8304 条目包括:
type 2: UTXO_VAULT
type 3: UTXO_CREATED_TOPIC
type 4: left_pad_32(Alice)
type 5: left_pad_32(Bob)
type 6: left_pad_32(42)
选定的 UPT 记录为:
{
index: 42,
source: Alice,
recipient: Bob,
value: 1 ETH,
transaction_index: 7,
transaction_relative_log_index: 2
}
设 opening 叶子为 L40、L41、L42 和 L43:
openings_root R
/ \
H01 = H(L40,L41) H23 = H(L42,L43)
/ \ / \
L40 L41 L42 L43
L42 的选定证明需要等同于 L43 和 H01 的兄弟材料:
L42 = keccak256(uint64_be(42) || Alice || Bob || uint256_be(1 ETH))
H23 = keccak256(L42 || L43)
R = keccak256(H01 || H23)
assert R == authenticated_openings_root
原型仅在以下条件满足时才接受输出:
- EIP-8304 证明建立了完整的 Bob 范围和预期的金库事件;
- 原型的类型 6 条目与 UTXO 索引 42 一致;
- 选定的 opening 折叠到已认证的 openings 根;
- 当前已认证的花费位为清除状态。
类型 6 检查特定于原型模式。
安全属性
假设承诺根和规范头被独立认证,该设计可以应对:
| 提供商行为 | 钱包检查 |
|---|---|
| 从表中省略一个 Bob 事件 | 验证完整的排序接收方范围及其边界 |
| 返回来自另一个合约的事件 | 要求在相同证明位置的金库地址 |
| 返回不同的事件类型 | 要求在相同证明位置的创建签名 |
| 更改 opening 字段 | 重新计算叶子并针对 openings 根验证 |
| 省略请求的 UPT 记录 | 要求每个选定事件位置恰好一条记录 |
| 添加无关的证明材料 | 拒绝未使用或重复的多重证明节点 |
| 重用来自另一个分叉的数据 | 通过已认证的规范区块身份键控缓存 |
| 返回过期的可花费性 | 花费前在最近的认证头处验证花费状态 |
| 扣留 UPT 或表字节 | 承诺无法阻止;使用其他来源或保留的钱包数据 |
承诺提供完整性,并且对于排序的 EIP 范围,提供查询完整性。它们不提供数据可用性。
原型性能观察
本地 devnet 实验最好被解释为实现观察,而非通用性能结果。
两种认证方法是在不同的 devnet 运行中测量的,因此每种方法应与同一行的收据基线进行比较,而非直接与其他认证方法比较。冷值为首次扫描测量值;热值为重复扫描的中位数。
| 实验 | 窗口 | 冷延迟,收据 / 证明路径 | 冷响应,收据 / 证明路径 | 热中位数,收据 / 证明路径 | 热响应,收据 / 证明路径 |
|---|---|---|---|---|---|
| 自定义全日志类型 7 | 100 区块 | 12.592 / 268.189 毫秒 | 134,642 / 1,947,887 字节 | 8.790 / 10.711 毫秒 | 134,642 / 129,770 字节 |
| 自定义全日志类型 7 | 150 区块 | 11.828 / 426.530 毫秒 | 226,906 / 3,033,305 字节 | 9.903 / 12.630 毫秒 | 226,906 / 218,698 字节 |
| EIP-8304 + UPT | 100 区块 | 15.563 / 307.525 毫秒 | 188,625 / 658,745 字节 | 15.727 / 5.889 毫秒 | 188,625 / 7,500 字节 |
| EIP-8304 + UPT | 150 区块 | 14.387 / 386.561 毫秒 | 296,973 / 1,027,430 字节 | 15.757 / 4.777 毫秒 | 296,973 / 11,323 字节 |
未缓存的发现
标准收据日志路径在首次本地扫描中更快。它执行的证明构造和验证更少,传输的数据也比 UPT 路径少。
两种认证方法都额外支付了以下成本:
- 将区块窗口分解为 EIP-8304 表;
- 构造完整的范围证据;
- 传输表条目和多重证明;
- 在钱包中验证表证明;
- 检索并认证选定的载荷或 UPT 记录。
UPT opening 证明本身相对紧凑。冷响应的大部分来自 EIP-8304 候选条目和 JSON 表示,而非选定的 opening 多重证明。
重复发现
自定义全日志方法继续检索选定的原始日志,因此保留了与匹配数量成比例的载荷成本。
UPT 原型缓存了已验证的表结果和选定的 UTXO 记录。因此,重复相同的历史扫描主要需要区块身份检查,并在本地重用 opening 数据。在该缓存策略下,UPT 路径完成得更快,并且检索的响应字节数远少于新的 eth_getLogs 调用。
这证明经过认证的 UPT 记录是可缓存和可重用的。这不是缓存等效比较:实验没有为收据路径提供钱包侧解码日志缓存。一个已经保留相同历史输出的收据钱包同样会避免再次下载它们。
实验支持的内容
原型支持以下有限结论:
- EIP-8304 范围证明和原生 opening 证明可以在一个钱包流程中组合。
- 选定的 UPT 记录可以替代原始日志检索来获取 opening 字段。
- 共享的逐区块多重证明避免了为每个 UTXO 发送独立的 opening 路径。
- 在当前实现中,证明构造和验证主导了未缓存的认证发现。
- 当缓存与已认证的规范区块身份绑定时,已验证的 opening 记录可以安全重用。
它并未证明 UPT 通常比 eth_getLogs 更快,也未证明一种认证方法在跨实现中比另一种更快,也未证明测量的行为会延续到远程主网基础设施。
局限性与待办工作
- 草案依赖: EIP-8304 和原生 UTXO 设计都可能发生变化。
- 原型事件模式: 基准测试依赖于当前原生提案中不存在的索引 UTXO 索引。
- 无可信头链: 根和规范哈希由同一执行端点提供。
- 仅近期根: 旧 openings 和批量路径未被服务。
- 无 UPT 可用性模型: 承诺不保证保留或服务。
- 无缓存等效基线: 收据结果未像 UPT 记录那样被缓存。
- 本地环境: 网络延迟、远程提供商限制和异构客户端未被建模。
- 测量集较小: 实验不足以支持稳定的尾部延迟声明。
- 不同的认证运行: 自定义类型 7 和 UPT 原型是在不同的 devnet 历史上测量的。
- 密集工作负载: 其他活动密度和表对齐可能产生不同的成本。
- 无花费状态基准测试: 测量的扫描发现了 openings,但未计算认证的当前余额。
- 无最终花费见证集成: 交易伪造器仍从日志中重建 opening 路径。
- 无节点成本模型: UPT 存储、构建时间、同步、修剪和拒绝服务限制需要评估。
- 无标准化编码: UPT 对象、证明节点格式和 RPC 方法仍为实现特定。
结论
EIP-8304 和原生 openings 根解决了 UTXO 发现的互补部分。
EIP-8304 可以证明覆盖表中每个提及 Bob 的事件都被返回。Openings 根可以证明选定 UTXO 的字段。UTXO 证明表通过使选定的 opening 记录和共享的 Merkle 材料可用,连接了这些承诺,而无需钱包下载完整的收据或原始日志。
该构造作为一种认证发现方法很有前景,但其价值主要在于证明组合和选择性数据检索——而非已确立的普遍速度优势。生产设计仍需要精确的模式绑定、认证头和状态证明、旧根见证服务、可用性激励、标准化编码,以及从发现证明到花费见证的完整路径。
参考文献
- EIP-8304:无需信任的日志和交易索引
- 以太坊上的原生 UTXO
- EIP-8141:帧交易
- 原文链接: ethresear.ch/t/an-evalua...
- 鸿途知科网 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
版权声明
本文仅代表作者观点,不代表区块链技术网立场。
本文系作者授权本站发表,未经许可,不得转载。
鸿途知科网
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。