本文介绍了以太坊中KZG承诺和证明的目的和作用,特别是在peerDAS和EIP-4844背景下如何使用它们来验证数据列的完整性。文章解释了在节点接收数据列时,如何利用KZG承诺和证明来确保每个单元格确实属于承诺的blob,并对比了KZG承诺和传统的Merkle证明,突出了KZG在存储和效率上的优势,阐述了在以太坊数据可用性采样(DAS)中扮演的关键角色。
查克·诺里斯不会生成 KZG 证明。宇宙只是简单地调整以匹配他的计算。
之前,我们看到一个携带 blob 的区块同时包含一个 KZG 承诺和一个 KZG 证明。
例子:
这个区块与 5
个 blob 相关联,每个 blob 都包含一个 KZG 承诺和一个 KZG 证明。
在本节中,我们将简要讨论 KZG 承诺和证明旨在解决的问题。但是,我们不会深入探讨它们如何工作的技术细节,因为这涉及到椭圆曲线、椭圆曲线配对、“陶的幂”仪式以及其他素域算术等概念。作为用户,将这些 KZG 工具视为黑盒完全没问题。
对于有兴趣深入研究 KZG 承诺和证明内部结构的读者,我推荐以下资源:
想象一下一个包含 blob 携带交易的信标区块。我们需要一种方法让该区块对这些 blob 进行承诺(即,将该区块与这些 blob 链接起来)。处理这个问题的一个简单方法是将每个 blob 的哈希值存储在信标区块中。与 5
个 blob 关联的区块将包含 5
个哈希值,每个 blob 对应一个。
当一个信标节点收到一个 blob 时,它可以很容易地检查该 blob 对应哪个区块。如果该区块中列出的所有 blob 都可用,则可以认为该区块可用。
但是,当信标节点存储数据列而不是 blob 时,这种简单的方法不再有效。
启用 peerDAS 后,信标节点不再通过 gossip 接收完整的 blob。相反,它接收数据列。数据列中的每个单元格代表扩展 blob 的 1/128
。
为了确保数据的完整性,信标节点必须验证数据列中的每个单元格是否真的属于一个 blob。
当构建一个包含一些 blob 携带交易的区块时,对于每个 blob,区块构建者都会创建一个 KZG 承诺和一个 KZG 证明。
KZK 承诺被添加到信标区块主体中:
class BeaconBlockBody(Container):
...
blob_kzg_commitments: List[KZGCommitment, MAX_BLOB_COMMITMENTS_PER_BLOCK] # [Deneb 中的新功能:EIP4844]
同时,区块构建者创建 128
个数据列 sidecar。
数据列 sidecar 是以下结构:
class DataColumnSidecar(Container):
index: ColumnIndex # 扩展矩阵中列的索引
column: List[Cell, MAX_BLOB_COMMITMENTS_PER_BLOCK]
kzg_commitments: List[KZGCommitment, MAX_BLOB_COMMITMENTS_PER_BLOCK]
kzg_proofs: List[KZGProof, MAX_BLOB_COMMITMENTS_PER_BLOCK]
signed_block_header: SignedBeaconBlockHeader
kzg_commitments_inclusion_proof: Vector[Bytes32, KZG_COMMITMENTS_INCLUSION_PROOF_DEPTH]
我们不会在这里深入探讨太多细节(你可以阅读前面给出的链接以获取更多信息),但我们可以简单地承认 KZG 承诺是一个 48 字节
的数据片段。一个 blob 恰好有一个唯一的 48 字节
KZG 承诺。
KZG 证明也是如此(48 字节
的数据片段,每个 blob 都是唯一的)。
以下是该过程的工作方式:
步骤 1:区块构建者构建一个包含(例如)6
个 blob 的区块。
步骤 2:使用多项式擦除编码,区块构建者将每个 blob 扩展为扩展 blob。
步骤 3:将扩展的 blob 分成 128
个单元格,然后将它们分组为 128
个数据列。每个数据列包含 6
个单元格,每个单元格来自一个扩展的 blob。
步骤 4:区块构建者将 128
个数据列中的每一个都包装到数据列 sidecar 中,并将它们发送到相应的 data_column_sidecar_<n>
主题,其中 n=0..127
。
这里的关键点是,对于给定的 blob,KZG 承诺和证明是固定的。发送到数据列 sidecar #2
的 KZG 承诺和证明与发送到数据列 sidecar #125
的完全相同,即使数据列的内容在 sidecar #2
和 sidecar #125
之间有所不同。
步骤 5:当节点收到索引为 i
的数据列时,它必须确保收到的数据列中的每个单元格确实对应于已承诺 blob 的第 i
个单元格,然后才能将相应的区块声明为可用。
例子:
如果一个节点收到一个包含 6
个单元格的数据列,其列索引为 4
,那么在声明该区块可用之前,该节点必须验证以下内容:
#0
对应于 blob #0
的单元格 #4
。#1
对应于 blob #1
的单元格 #4
。#5
对应于 blob #5
的单元格 #4
。✨ 好消息! 验证一个单元格是否真正属于一个 blob——而不需要检索整个 blob——这正是 KZG 承诺和 KZG 证明的设计目的。✨
当一个节点收到一个数据列 sidecar 时,它首先确保 sidecar 中的每个 KZG 承诺(每个 blob 一个)与关联区块中的相应承诺匹配。然后,它运行一个函数来验证每个数据列单元格的 KZG 证明。
要验证特定的 KZG 证明,此函数需要以下参数:
此函数返回一个布尔值。如果单元格的内容与索引、blob 的 KZG 承诺及其证明一致,则该函数返回 True
。在这种情况下,信标节点可以确信该单元格确实属于与该 KZG 承诺相关联的 blob。由于已针对区块验证了该承诺,因此信标节点可以确保该 blob 正确链接到该区块。
在实践中,KZG 证明不是逐个单元格验证的,而是批量验证的。
def verify_data_column_sidecar_kzg_proofs(sidecar: DataColumnSidecar) -> bool:
"""
验证 KZG 证明是否正确。
"""
# 列索引也代表单元格索引
cell_indices = [CellIndex(sidecar.index)] * len(sidecar.column)
# 批量验证单元格是否与相应的承诺和证明匹配
return verify_cell_kzg_proof_batch(
commitments_bytes=sidecar.kzg_commitments,
cell_indices=cell_indices,
cells=sidecar.column,
proofs_bytes=sidecar.kzg_proofs,
)
值得注意的是,从技术上讲,使用 Merkle 证明而不是 KZG 承诺和证明可以实现相同的目标。
例如,我们可以为每个单元格提供一个 Merkle 证明,并要求接收数据列 sidecar 的信标节点针对单元格的内容验证 Merkle 证明。这会起作用。
但是,这种方法存在重大缺陷:
128
个 Merkle 证明,并且 6
个 blob 有 768
个 Merkle 证明。使用 KZG 承诺和证明的优点:
48 B
大小,与 blob 的大小无关。虽然 KZG 承诺和证明在数学上更复杂,但与仅使用 Merkle 证明的等效解决方案相比,它们需要的存储空间要少几个数量级。
- 原文链接: hackmd.io/@manunalepa/ry...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!