KZG承诺与Blob聚合

  • SpireLabs
  • 发布于 2025-05-30 14:15
  • 阅读 19

本文解释了在EIP-4844 blob聚合过程中,rollup提交的原始数据的KZG承诺保持有效的原因。文章通过代码示例演示了如何使用 alloys crate 对rollup数据进行blob编码、计算KZG承诺、聚合数据、解聚合数据,并验证原始数据的KZG承诺在聚合前后保持不变,证明只要数据可以无损恢复,blob聚合就不会影响其KZG承诺的有效性。

简而言之:

Blob 聚合不会修改由 Rollup 提交的原始数据,因此它们的 KZG 承诺在聚合之后仍然有效——只要数据可以无损恢复。

背景

EIP-4844 Blob 具有 128KB 的固定大小。 由于此数据太大而无法传递,因此会计算对 Blob 数据的较轻承诺,并将其用于 Blob 验证。 KZG 承诺允许验证者和节点有效地证明某些数据(如 Blob)存在且未被更改,而无需实际在链上传输或存储完整数据。

但这引出了一个问题,当 Blob 聚合到共享 Blob 中时,它的 KZG 承诺会发生什么? 简而言之,答案是什么也不会发生。 在聚合过程中,Blob 的数据不会发生任何改变,从而在解包后改变其 KZG 承诺。

为了理解为什么会这样,我们需要了解数据是如何编码到 Blob 中,以及如何计算 KZG 承诺。 EIP-4844 中的 Blob 具有 4096 个字段元素的固定长度,每个字段元素为 32 字节,总计 128KB。

KZG 承诺对由这 4096 个固定大小的字段元素构成的多项式进行运算,而不是直接对任意大小的数据进行运算,因此如果 Rollup 的原始数据小于 128KB,则必须先填充或编码数据到正好 128KB,然后才能计算 KZG 承诺。 这就是像 alloys-rs 这样的库在将数据编码到 EIP-4844 Blob 中时所做的事情。

要记住的一点是,Blob 编码和 kzg 承诺计算都是确定性过程。 只要输入的 Rollup 数据没有改变,kzg 承诺就不会改变。 正如我们将看到的,聚合/解聚合过程不会改变原始 Rollup 数据。

例子

概述

我们将通过一个简单的代码示例来演示开源的 alloys crate, reth 在其 eip4844 支持中就在内部使用了它。

在本示例中,我们将采用两个数据字符串:

let string1 = “来自 Rollup 1 的交易数据”

let string2 = “来自 Rollup 2 的交易数据”

然后我们将单独地“blobify”这些字符串,并计算它们的 kzg 承诺。 然后,我们将通过连接它们来做一种非常基本的聚合形式,blobify 连接的数据,然后反转该过程以表明 kzg 承诺保持不变。

演示

我们从一些模拟的 Rollup 数据开始,并将其转换为字节:

let rollup_1_data = "来自 Rollup 1 的交易数据";
let rollup_2_data = "来自 Rollup 2 的交易数据";
let rollup_1_data_len = rollup_1_data.len();
let rollup_2_data_len = rollup_2_data.len();

let rollup_1_data_bytes = rollup_1_data.as_bytes();
let rollup_2_data_bytes = rollup_2_data.as_bytes();

接下来,我们使用 alloy 的 SimpleCoder 和 SidecarBuilder 将每个 Rollup 的数据编码为 128KB 的 Blob,并检索其 KZG 承诺:

let mut rollup_1_blob_builder = SidecarBuilder::<SimpleCoder>::new();
rollup_1_blob_builder.ingest(rollup_1_data_bytes);

let rollup_1_sidecar: BlobTransactionSidecar = rollup_1_blob_builder.build()?;

let rollup_1_blob = rollup_1_sidecar.blobs.get(0).ok_or("Sidecar1 没有 blobs")?;
let rollup_1_commitment = rollup_1_sidecar.commitments.get(0).ok_or("Sidecar1 没有承诺")?;

我们对 Rollup 2 也执行相同的操作。 值得注意的是这两个 KZG 承诺,因为这些是 Rollup 将会跟踪的。

接下来,我们通过连接原始 Rollup 数据字符串的原始字节来“聚合”Rollup 数据(不是这些字符串的 Blob 编码版本)

let mut aggregated_data_bytes = Vec::new();
aggregated_data_bytes.extend_from_slice(rollup_1_data_bytes);
aggregated_data_bytes.extend_from_slice(rollup_2_data_bytes);

然后,我们完成将此聚合数据编码为 Blob 并计算其 KZG 承诺的过程。 请注意,在此示例中,此“聚合”KZG 承诺对我们没有多大用处,尽管它将不同于 Rollup 1 或 Rollup 2 的单个 KZG 承诺。

最后,我们通过剥离其 Blob 编码并手动解析 Rollup 1 和 Rollup 2 的字节来解聚合聚合的 Blob。

    let mut coder = SimpleCoder::default();
    let decoded_data = coder.decode_all(&[owned_aggregated_blob])
        .and_then(|v| v.into_iter().next())
        .ok_or_else(|| eyre::eyre!("无法解码或在聚合的 blob 中查找数据"))?;

    let decoded_data_str = String::from_utf8(decoded_data)?;

    println!("解码后的数据: {}", decoded_data_str);
    // 输出 解码后的数据:来自 Rollup 1 的交易数据来自 Rollup 2 的交易数据

    // 我们知道原始数据的长度,因此我们可以将解码后的数据拆分为两个原始字符串
    // 实际上,聚合的 blob 将偏移量和长度编码为通用标头格式
    let rollup_1_data_after_aggregation = decoded_data_str[..rollup_1_data_len].to_string();
    let rollup_2_data_after_aggregation = decoded_data_str[rollup_1_data_len..].to_string();

    println!("聚合和解码后 Rollup 1 的数据: {}", rollup_1_data_after_aggregation);
    println!("聚合和解码后 Rollup 2 的数据: {}", rollup_2_data_after_aggregation);

我们现在拥有 Rollup 提交的原始字符串数据。 如果这是交易数据,则 Rollup 节点可以开始通过其推导管道的其余部分运行它。 作为最后一步,我们将每个字符串重新编码为 Blob,以便我们可以计算其 KZG 承诺,并确认自 Rollup 最初提交以来它没有更改。

    // 现在,让我们将两个原始字符串重新编码为 Blob 并验证 KZG 承诺
    let mut rollup_1_blob_builder = SidecarBuilder::<SimpleCoder>::new();
    rollup_1_blob_builder.ingest(rollup_1_data_after_aggregation.as_bytes());

    let rollup_1_sidecar: BlobTransactionSidecar = rollup_1_blob_builder.build()?;

    let rollup_1_blob = rollup_1_sidecar.blobs.get(0).ok_or("Sidecar1 没有 blobs")?;
    let rollup_1_commitment_after_aggregation = rollup_1_sidecar.commitments.get(0).ok_or("Sidecar1 没有承诺")?;

    // println!("编码后 Rollup 1 的 Blob: {}", hex::encode(rollup_1_blob));
    println!("编码后 Rollup 1 的承诺: {}", hex::encode(rollup_1_commitment));

    let mut rollup_2_blob_builder = SidecarBuilder::<SimpleCoder>::new();
    rollup_2_blob_builder.ingest(rollup_2_data_after_aggregation.as_bytes());

    let rollup_2_sidecar: BlobTransactionSidecar = rollup_2_blob_builder.build()?;

    let rollup_2_blob = rollup_2_sidecar.blobs.get(0).ok_or("Sidecar2 没有 blobs")?;
    let rollup_2_commitment_after_aggregation = rollup_2_sidecar.commitments.get(0).ok_or("Sidecar2 没有承诺")?;

    // println!("编码后 Rollup 2 的 Blob: {}", hex::encode(rollup_2_blob));
    println!("编码后 Rollup 2 的承诺: {}", hex::encode(rollup_2_commitment));

    // 断言聚合前后承诺相同
    assert_eq!(rollup_1_commitment, rollup_1_commitment_after_aggregation);
    assert_eq!(rollup_2_commitment, rollup_2_commitment_after_aggregation);

完整代码:

GitHub - spire-labs/kzg-commitment-example \ \ 通过在 GitHub 上创建一个帐户来为 spire-labs/kzg-commitment-example 开发做出贡献。\ \ https://github.com\ \ GitHub - spire-labs/kzg-commitment-example

结论

我们已经看到,只要聚合过程不以任何方式不可逆地改变 Rollup 的数据,聚合就可以保留 Rollup Blob 的原始 KZG 承诺。

最后的想法

如果你是 L2 并且希望在不更改代码的情况下节省以太坊 DA 的成本,请立即开始使用我们的 https://spire.deform.cc/DABuilder/

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

0 条评论

请先 登录 后评论
SpireLabs
SpireLabs
江湖只有他的大名,没有他的介绍。