Add PeerDAS KZG lib integration (construction & KZG verification) (#6212)

* Add peerdas KZG library and use it for data column construction and cell kzg verification (#5701, #5941, #6118, #6179)

Co-authored-by: kevaundray <kevtheappdev@gmail.com>

* Update `rust_eth_kzg` crate to published version.

* Update kzg metrics buckets.

* Merge branch 'unstable' into peerdas-kzg

* Update KZG version to fix windows mem allocation.

* Refactor common logic from build sidecar and reconstruction. Remove unnecessary `needless_lifetimes`.

Co-authored-by: realbigsean <sean@sigmaprime.io>

* Copy existing trusted setup into `PeerDASTrustedSetup` for consistency and maintain `--trusted-setup` functionality.

* Merge branch 'unstable' into peerdas-kzg

* Merge branch 'peerdas-kzg' of github.com:jimmygchen/lighthouse into peerdas-kzg

* Merge branch 'unstable' into peerdas-kzg

* Merge branch 'unstable' into peerdas-kzg

* Load PeerDAS KZG only if PeerDAS is enabled.
This commit is contained in:
Jimmy Chen
2024-08-13 10:16:17 +10:00
committed by GitHub
parent ff15c78ced
commit 6dc614fede
11 changed files with 627 additions and 284 deletions

View File

@@ -17,3 +17,13 @@ ethereum_serde_utils = { workspace = true }
hex = { workspace = true }
ethereum_hashing = { workspace = true }
c-kzg = { workspace = true }
rust_eth_kzg = { workspace = true }
[dev-dependencies]
criterion = { workspace = true }
serde_json = { workspace = true }
eth2_network_config = { workspace = true }
[[bench]]
name = "benchmark"
harness = false

View File

@@ -0,0 +1,31 @@
use c_kzg::KzgSettings;
use criterion::{criterion_group, criterion_main, Criterion};
use eth2_network_config::TRUSTED_SETUP_BYTES;
use kzg::TrustedSetup;
use rust_eth_kzg::{DASContext, TrustedSetup as PeerDASTrustedSetup};
pub fn bench_init_context(c: &mut Criterion) {
let trusted_setup: TrustedSetup = serde_json::from_reader(TRUSTED_SETUP_BYTES)
.map_err(|e| format!("Unable to read trusted setup file: {}", e))
.expect("should have trusted setup");
c.bench_function(&format!("Initialize context rust_eth_kzg"), |b| {
b.iter(|| {
const NUM_THREADS: usize = 1;
let trusted_setup = PeerDASTrustedSetup::from(&trusted_setup);
DASContext::with_threads(&trusted_setup, NUM_THREADS)
})
});
c.bench_function(&format!("Initialize context c-kzg (4844)"), |b| {
b.iter(|| {
let trusted_setup: TrustedSetup = serde_json::from_reader(TRUSTED_SETUP_BYTES)
.map_err(|e| format!("Unable to read trusted setup file: {}", e))
.expect("should have trusted setup");
KzgSettings::load_trusted_setup(&trusted_setup.g1_points(), &trusted_setup.g2_points())
.unwrap()
})
});
}
criterion_group!(benches, bench_init_context);
criterion_main!(benches);

View File

@@ -2,6 +2,7 @@ mod kzg_commitment;
mod kzg_proof;
mod trusted_setup;
use rust_eth_kzg::{CellIndex, DASContext};
use std::fmt::Debug;
pub use crate::{
@@ -9,18 +10,35 @@ pub use crate::{
kzg_proof::KzgProof,
trusted_setup::TrustedSetup,
};
pub use c_kzg::{
Blob, Bytes32, Bytes48, KzgSettings, BYTES_PER_BLOB, BYTES_PER_COMMITMENT,
BYTES_PER_FIELD_ELEMENT, BYTES_PER_PROOF, FIELD_ELEMENTS_PER_BLOB,
};
pub use rust_eth_kzg::{
constants::{BYTES_PER_CELL, CELLS_PER_EXT_BLOB},
Cell, CellIndex as CellID, CellRef, TrustedSetup as PeerDASTrustedSetup,
};
pub type CellsAndKzgProofs = ([Cell; CELLS_PER_EXT_BLOB], [KzgProof; CELLS_PER_EXT_BLOB]);
pub type KzgBlobRef<'a> = &'a [u8; BYTES_PER_BLOB];
#[derive(Debug)]
pub enum Error {
/// An error from the underlying kzg library.
Kzg(c_kzg::Error),
/// A prover/verifier error from the rust-eth-kzg library.
PeerDASKZG(rust_eth_kzg::Error),
/// The kzg verification failed
KzgVerificationFailed,
/// Misc indexing error
InconsistentArrayLength(String),
/// Error reconstructing data columns.
ReconstructFailed(String),
/// Kzg was not initialized with PeerDAS enabled.
DASContextUninitialized,
}
impl From<c_kzg::Error> for Error {
@@ -29,32 +47,11 @@ impl From<c_kzg::Error> for Error {
}
}
pub const CELLS_PER_EXT_BLOB: usize = 128;
// TODO(das): use proper crypto once ckzg merges das branch
#[allow(dead_code)]
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
pub struct Cell {
bytes: [u8; 2048usize],
}
impl Cell {
pub fn from_bytes(b: &[u8]) -> Result<Self, Error> {
Ok(Self {
bytes: b
.try_into()
.map_err(|_| Error::Kzg(c_kzg::Error::MismatchLength("".to_owned())))?,
})
}
pub fn into_inner(self) -> [u8; 2048usize] {
self.bytes
}
}
/// A wrapper over a kzg library that holds the trusted setup parameters.
#[derive(Debug)]
pub struct Kzg {
trusted_setup: KzgSettings,
context: Option<DASContext>,
}
impl Kzg {
@@ -65,9 +62,36 @@ impl Kzg {
&trusted_setup.g1_points(),
&trusted_setup.g2_points(),
)?,
context: None,
})
}
pub fn new_from_trusted_setup_das_enabled(trusted_setup: TrustedSetup) -> Result<Self, Error> {
// Initialize the trusted setup using default parameters
//
// Note: One can also use `from_json` to initialize it from the consensus-specs
// json string.
let peerdas_trusted_setup = PeerDASTrustedSetup::from(&trusted_setup);
// Set the number of threads to be used
//
// we set it to 1 to match the c-kzg performance
const NUM_THREADS: usize = 1;
let context = DASContext::with_threads(&peerdas_trusted_setup, NUM_THREADS);
Ok(Self {
trusted_setup: KzgSettings::load_trusted_setup(
&trusted_setup.g1_points(),
&trusted_setup.g2_points(),
)?,
context: Some(context),
})
}
fn context(&self) -> Result<&DASContext, Error> {
self.context.as_ref().ok_or(Error::DASContextUninitialized)
}
/// Compute the kzg proof given a blob and its kzg commitment.
pub fn compute_blob_kzg_proof(
&self,
@@ -167,21 +191,18 @@ impl Kzg {
}
/// Computes the cells and associated proofs for a given `blob` at index `index`.
#[allow(clippy::type_complexity)]
pub fn compute_cells_and_proofs(
&self,
_blob: &Blob,
) -> Result<
(
Box<[Cell; CELLS_PER_EXT_BLOB]>,
Box<[KzgProof; CELLS_PER_EXT_BLOB]>,
),
Error,
> {
// TODO(das): use proper crypto once ckzg merges das branch
let cells = Box::new(core::array::from_fn(|_| Cell { bytes: [0u8; 2048] }));
let proofs = Box::new([KzgProof([0u8; BYTES_PER_PROOF]); CELLS_PER_EXT_BLOB]);
Ok((cells, proofs))
blob: KzgBlobRef<'_>,
) -> Result<CellsAndKzgProofs, Error> {
let (cells, proofs) = self
.context()?
.compute_cells_and_kzg_proofs(blob)
.map_err(Error::PeerDASKZG)?;
// Convert the proof type to a c-kzg proof type
let c_kzg_proof = proofs.map(KzgProof);
Ok((cells, c_kzg_proof))
}
/// Verifies a batch of cell-proof-commitment triplets.
@@ -191,35 +212,43 @@ impl Kzg {
/// to the data column index.
pub fn verify_cell_proof_batch(
&self,
_cells: &[Cell],
_kzg_proofs: &[Bytes48],
_coordinates: &[(u64, u64)],
_kzg_commitments: &[Bytes48],
cells: &[CellRef<'_>],
kzg_proofs: &[Bytes48],
columns: Vec<CellIndex>,
kzg_commitments: &[Bytes48],
) -> Result<(), Error> {
// TODO(das): use proper crypto once ckzg merges das branch
Ok(())
let proofs: Vec<_> = kzg_proofs.iter().map(|proof| proof.as_ref()).collect();
let commitments: Vec<_> = kzg_commitments
.iter()
.map(|commitment| commitment.as_ref())
.collect();
let verification_result = self.context()?.verify_cell_kzg_proof_batch(
commitments.to_vec(),
columns,
cells.to_vec(),
proofs.to_vec(),
);
// Modify the result so it matches roughly what the previous method was doing.
match verification_result {
Ok(_) => Ok(()),
Err(e) if e.invalid_proof() => Err(Error::KzgVerificationFailed),
Err(e) => Err(Error::PeerDASKZG(e)),
}
}
pub fn cells_to_blob(&self, _cells: &[Cell; CELLS_PER_EXT_BLOB]) -> Result<Blob, Error> {
// TODO(das): use proper crypto once ckzg merges das branch
Ok(Blob::new([0u8; 131072usize]))
}
pub fn recover_all_cells(
pub fn recover_cells_and_compute_kzg_proofs(
&self,
_cell_ids: &[u64],
_cells: &[Cell],
) -> Result<Box<[Cell; CELLS_PER_EXT_BLOB]>, Error> {
// TODO(das): use proper crypto once ckzg merges das branch
let cells = Box::new(core::array::from_fn(|_| Cell { bytes: [0u8; 2048] }));
Ok(cells)
}
}
impl TryFrom<TrustedSetup> for Kzg {
type Error = Error;
fn try_from(trusted_setup: TrustedSetup) -> Result<Self, Self::Error> {
Kzg::new_from_trusted_setup(trusted_setup)
cell_ids: &[u64],
cells: &[CellRef<'_>],
) -> Result<CellsAndKzgProofs, Error> {
let (cells, proofs) = self
.context()?
.recover_cells_and_proofs(cell_ids.to_vec(), cells.to_vec())
.map_err(Error::PeerDASKZG)?;
// Convert the proof type to a c-kzg proof type
let c_kzg_proof = proofs.map(KzgProof);
Ok((cells, c_kzg_proof))
}
}

View File

@@ -1,3 +1,4 @@
use crate::PeerDASTrustedSetup;
use c_kzg::{BYTES_PER_G1_POINT, BYTES_PER_G2_POINT};
use serde::{
de::{self, Deserializer, Visitor},
@@ -43,6 +44,28 @@ impl TrustedSetup {
}
}
impl From<&TrustedSetup> for PeerDASTrustedSetup {
fn from(trusted_setup: &TrustedSetup) -> Self {
Self {
g1_monomial: trusted_setup
.g1_monomial_points
.iter()
.map(|g1_point| format!("0x{}", hex::encode(g1_point.0)))
.collect::<Vec<_>>(),
g1_lagrange: trusted_setup
.g1_points
.iter()
.map(|g1_point| format!("0x{}", hex::encode(g1_point.0)))
.collect::<Vec<_>>(),
g2_monomial: trusted_setup
.g2_points
.iter()
.map(|g2_point| format!("0x{}", hex::encode(g2_point.0)))
.collect::<Vec<_>>(),
}
}
}
impl Serialize for G1Point {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where