diff --git a/Cargo.lock b/Cargo.lock index db3771222..0e669154f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -892,10 +892,11 @@ dependencies = [ [[package]] name = "c-kzg" version = "0.1.0" -source = "git+https://github.com/ethereum/c-kzg-4844?rev=69f6155d7524247be9d3f54ab3bfbe33a0345622#69f6155d7524247be9d3f54ab3bfbe33a0345622" +source = "git+https://github.com/ethereum/c-kzg-4844?rev=549739fcb3aaec6fe5651e1912f05c604b45621b#549739fcb3aaec6fe5651e1912f05c604b45621b" dependencies = [ "hex", "libc", + "serde", ] [[package]] diff --git a/beacon_node/beacon_chain/src/kzg_utils.rs b/beacon_node/beacon_chain/src/kzg_utils.rs index 58ff79e0f..3536420a1 100644 --- a/beacon_node/beacon_chain/src/kzg_utils.rs +++ b/beacon_node/beacon_chain/src/kzg_utils.rs @@ -1,6 +1,8 @@ use kzg::{Error as KzgError, Kzg, BYTES_PER_BLOB}; -use types::{Blob, BlobsSidecar, EthSpec, Hash256, KzgCommitment, KzgProof, Slot}; +use types::{Blob, EthSpec, KzgCommitment, KzgProof}; +/// Converts a blob ssz List object to an array to be used with the kzg +/// crypto library. fn ssz_blob_to_crypto_blob(blob: Blob) -> kzg::Blob { let blob_vec: Vec = blob.into(); let mut arr = [0; BYTES_PER_BLOB]; @@ -8,47 +10,48 @@ fn ssz_blob_to_crypto_blob(blob: Blob) -> kzg::Blob { arr.into() } -pub fn validate_blobs_sidecar( +/// Validate a single blob-commitment-proof triplet from a `BlobSidecar`. +pub fn validate_blob( kzg: &Kzg, - slot: Slot, - beacon_block_root: Hash256, - expected_kzg_commitments: &[KzgCommitment], - blobs_sidecar: &BlobsSidecar, + blob: Blob, + kzg_commitment: KzgCommitment, + kzg_proof: KzgProof, ) -> Result { - if slot != blobs_sidecar.beacon_block_slot - || beacon_block_root != blobs_sidecar.beacon_block_root - || blobs_sidecar.blobs.len() != expected_kzg_commitments.len() - { - return Ok(false); - } - - let blobs = blobs_sidecar - .blobs - .clone() // TODO(pawan): avoid this clone - .into_iter() - .map(|blob| ssz_blob_to_crypto_blob::(blob)) - .collect::>(); - - kzg.verify_aggregate_kzg_proof( - &blobs, - expected_kzg_commitments, - blobs_sidecar.kzg_aggregated_proof, + kzg.verify_blob_kzg_proof( + ssz_blob_to_crypto_blob::(blob), + kzg_commitment, + kzg_proof, ) } -pub fn compute_aggregate_kzg_proof( +/// Validate a batch of blob-commitment-proof triplets from multiple `BlobSidecars`. +pub fn validate_blobs( kzg: &Kzg, + expected_kzg_commitments: &[KzgCommitment], blobs: &[Blob], -) -> Result { + kzg_proofs: &[KzgProof], +) -> Result { let blobs = blobs .iter() - .map(|blob| ssz_blob_to_crypto_blob::(blob.clone())) // TODO(pawan): avoid this clone + .map(|blob| ssz_blob_to_crypto_blob::(blob.clone())) // Avoid this clone .collect::>(); - kzg.compute_aggregate_kzg_proof(&blobs) + kzg.verify_blob_kzg_proof_batch(&blobs, expected_kzg_commitments, kzg_proofs) } -pub fn blob_to_kzg_commitment(kzg: &Kzg, blob: Blob) -> KzgCommitment { - let blob = ssz_blob_to_crypto_blob::(blob); - kzg.blob_to_kzg_commitment(blob) +/// Compute the kzg proof given an ssz blob and its kzg commitment. +pub fn compute_blob_kzg_proof( + kzg: &Kzg, + blob: Blob, + kzg_commitment: KzgCommitment, +) -> Result { + kzg.compute_blob_kzg_proof(ssz_blob_to_crypto_blob::(blob), kzg_commitment) +} + +/// Compute the kzg commitment for a given blob. +pub fn blob_to_kzg_commitment( + kzg: &Kzg, + blob: Blob, +) -> Result { + kzg.blob_to_kzg_commitment(ssz_blob_to_crypto_blob::(blob)) } diff --git a/crypto/kzg/Cargo.toml b/crypto/kzg/Cargo.toml index d37fda1ea..0645b3a2b 100644 --- a/crypto/kzg/Cargo.toml +++ b/crypto/kzg/Cargo.toml @@ -16,7 +16,7 @@ serde_derive = "1.0.116" eth2_serde_utils = "0.1.1" hex = "0.4.2" eth2_hashing = "0.3.0" -c-kzg = {git = "https://github.com/ethereum/c-kzg-4844", rev = "69f6155d7524247be9d3f54ab3bfbe33a0345622" } +c-kzg = {git = "https://github.com/ethereum/c-kzg-4844", rev = "549739fcb3aaec6fe5651e1912f05c604b45621b" } arbitrary = { version = "1.0", features = ["derive"], optional = true } [features] diff --git a/crypto/kzg/src/kzg_commitment.rs b/crypto/kzg/src/kzg_commitment.rs index 7a43d7009..3a18fe8b5 100644 --- a/crypto/kzg/src/kzg_commitment.rs +++ b/crypto/kzg/src/kzg_commitment.rs @@ -1,3 +1,4 @@ +use c_kzg::{Bytes48, BYTES_PER_COMMITMENT}; use derivative::Derivative; use serde::de::{Deserialize, Deserializer}; use serde::ser::{Serialize, Serializer}; @@ -7,12 +8,16 @@ use std::fmt::{Debug, Display, Formatter}; use std::str::FromStr; use tree_hash::{PackedEncoding, TreeHash}; -const KZG_COMMITMENT_BYTES_LEN: usize = 48; - #[derive(Derivative, Clone, Encode, Decode)] #[derivative(PartialEq, Eq, Hash)] #[ssz(struct_behaviour = "transparent")] -pub struct KzgCommitment(pub [u8; KZG_COMMITMENT_BYTES_LEN]); +pub struct KzgCommitment(pub [u8; BYTES_PER_COMMITMENT]); + +impl From for Bytes48 { + fn from(value: KzgCommitment) -> Self { + value.0.into() + } +} impl Display for KzgCommitment { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { @@ -22,13 +27,13 @@ impl Display for KzgCommitment { impl Default for KzgCommitment { fn default() -> Self { - KzgCommitment([0; KZG_COMMITMENT_BYTES_LEN]) + KzgCommitment([0; BYTES_PER_COMMITMENT]) } } impl TreeHash for KzgCommitment { fn tree_hash_type() -> tree_hash::TreeHashType { - <[u8; KZG_COMMITMENT_BYTES_LEN] as TreeHash>::tree_hash_type() + <[u8; BYTES_PER_COMMITMENT] as TreeHash>::tree_hash_type() } fn tree_hash_packed_encoding(&self) -> PackedEncoding { @@ -36,7 +41,7 @@ impl TreeHash for KzgCommitment { } fn tree_hash_packing_factor() -> usize { - <[u8; KZG_COMMITMENT_BYTES_LEN] as TreeHash>::tree_hash_packing_factor() + <[u8; BYTES_PER_COMMITMENT] as TreeHash>::tree_hash_packing_factor() } fn tree_hash_root(&self) -> tree_hash::Hash256 { @@ -86,15 +91,15 @@ impl FromStr for KzgCommitment { fn from_str(s: &str) -> Result { if let Some(stripped) = s.strip_prefix("0x") { let bytes = hex::decode(stripped).map_err(|e| e.to_string())?; - if bytes.len() == KZG_COMMITMENT_BYTES_LEN { - let mut kzg_commitment_bytes = [0; KZG_COMMITMENT_BYTES_LEN]; + if bytes.len() == BYTES_PER_COMMITMENT { + let mut kzg_commitment_bytes = [0; BYTES_PER_COMMITMENT]; kzg_commitment_bytes[..].copy_from_slice(&bytes); Ok(Self(kzg_commitment_bytes)) } else { Err(format!( "InvalidByteLength: got {}, expected {}", bytes.len(), - KZG_COMMITMENT_BYTES_LEN + BYTES_PER_COMMITMENT )) } } else { @@ -112,7 +117,7 @@ impl Debug for KzgCommitment { #[cfg(feature = "arbitrary")] impl arbitrary::Arbitrary<'_> for KzgCommitment { fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result { - let mut bytes = [0u8; KZG_COMMITMENT_BYTES_LEN]; + let mut bytes = [0u8; BYTES_PER_COMMITMENT]; u.fill_buffer(&mut bytes)?; Ok(KzgCommitment(bytes)) } diff --git a/crypto/kzg/src/kzg_proof.rs b/crypto/kzg/src/kzg_proof.rs index 2dac5669b..7c6eb59ab 100644 --- a/crypto/kzg/src/kzg_proof.rs +++ b/crypto/kzg/src/kzg_proof.rs @@ -1,3 +1,4 @@ +use c_kzg::{Bytes48, BYTES_PER_PROOF}; use serde::de::{Deserialize, Deserializer}; use serde::ser::{Serialize, Serializer}; use ssz_derive::{Decode, Encode}; @@ -6,15 +7,19 @@ use std::fmt::Debug; use std::str::FromStr; use tree_hash::{PackedEncoding, TreeHash}; -const KZG_PROOF_BYTES_LEN: usize = 48; - #[derive(PartialEq, Hash, Clone, Copy, Encode, Decode)] #[ssz(struct_behaviour = "transparent")] -pub struct KzgProof(pub [u8; KZG_PROOF_BYTES_LEN]); +pub struct KzgProof(pub [u8; BYTES_PER_PROOF]); + +impl From for Bytes48 { + fn from(value: KzgProof) -> Self { + value.0.into() + } +} impl KzgProof { pub fn empty() -> Self { - let mut bytes = [0; KZG_PROOF_BYTES_LEN]; + let mut bytes = [0; BYTES_PER_PROOF]; bytes[0] = 192; Self(bytes) } @@ -28,25 +33,25 @@ impl fmt::Display for KzgProof { impl Default for KzgProof { fn default() -> Self { - KzgProof([0; KZG_PROOF_BYTES_LEN]) + KzgProof([0; BYTES_PER_PROOF]) } } -impl From<[u8; KZG_PROOF_BYTES_LEN]> for KzgProof { - fn from(bytes: [u8; KZG_PROOF_BYTES_LEN]) -> Self { +impl From<[u8; BYTES_PER_PROOF]> for KzgProof { + fn from(bytes: [u8; BYTES_PER_PROOF]) -> Self { Self(bytes) } } -impl Into<[u8; KZG_PROOF_BYTES_LEN]> for KzgProof { - fn into(self) -> [u8; KZG_PROOF_BYTES_LEN] { +impl Into<[u8; BYTES_PER_PROOF]> for KzgProof { + fn into(self) -> [u8; BYTES_PER_PROOF] { self.0 } } impl TreeHash for KzgProof { fn tree_hash_type() -> tree_hash::TreeHashType { - <[u8; KZG_PROOF_BYTES_LEN]>::tree_hash_type() + <[u8; BYTES_PER_PROOF]>::tree_hash_type() } fn tree_hash_packed_encoding(&self) -> PackedEncoding { @@ -54,7 +59,7 @@ impl TreeHash for KzgProof { } fn tree_hash_packing_factor() -> usize { - <[u8; KZG_PROOF_BYTES_LEN]>::tree_hash_packing_factor() + <[u8; BYTES_PER_PROOF]>::tree_hash_packing_factor() } fn tree_hash_root(&self) -> tree_hash::Hash256 { @@ -104,15 +109,15 @@ impl FromStr for KzgProof { fn from_str(s: &str) -> Result { if let Some(stripped) = s.strip_prefix("0x") { let bytes = hex::decode(stripped).map_err(|e| e.to_string())?; - if bytes.len() == KZG_PROOF_BYTES_LEN { - let mut kzg_proof_bytes = [0; KZG_PROOF_BYTES_LEN]; + if bytes.len() == BYTES_PER_PROOF { + let mut kzg_proof_bytes = [0; BYTES_PER_PROOF]; kzg_proof_bytes[..].copy_from_slice(&bytes); Ok(Self(kzg_proof_bytes)) } else { Err(format!( "InvalidByteLength: got {}, expected {}", bytes.len(), - KZG_PROOF_BYTES_LEN + BYTES_PER_PROOF )) } } else { @@ -130,7 +135,7 @@ impl Debug for KzgProof { #[cfg(feature = "arbitrary")] impl arbitrary::Arbitrary<'_> for KzgProof { fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result { - let mut bytes = [0u8; KZG_PROOF_BYTES_LEN]; + let mut bytes = [0u8; BYTES_PER_PROOF]; u.fill_buffer(&mut bytes)?; Ok(KzgProof(bytes)) } diff --git a/crypto/kzg/src/lib.rs b/crypto/kzg/src/lib.rs index b6d62e053..cd28c8529 100644 --- a/crypto/kzg/src/lib.rs +++ b/crypto/kzg/src/lib.rs @@ -3,6 +3,7 @@ mod kzg_proof; mod trusted_setup; pub use crate::{kzg_commitment::KzgCommitment, kzg_proof::KzgProof, trusted_setup::TrustedSetup}; +use c_kzg::Bytes48; pub use c_kzg::{ Blob, Error as CKzgError, KZGSettings, BYTES_PER_BLOB, BYTES_PER_FIELD_ELEMENT, FIELD_ELEMENTS_PER_BLOB, @@ -13,9 +14,9 @@ use std::path::PathBuf; pub enum Error { InvalidTrustedSetup(CKzgError), InvalidKzgProof(CKzgError), - InvalidLength(String), + InvalidBytes(CKzgError), KzgProofComputationFailed(CKzgError), - InvalidBlob(String), + InvalidBlob(CKzgError), } /// A wrapper over a kzg library that holds the trusted setup parameters. @@ -51,40 +52,67 @@ impl Kzg { }) } - /// Compute the aggregated kzg proof given an array of blobs. - pub fn compute_aggregate_kzg_proof(&self, blobs: &[Blob]) -> Result { - c_kzg::KZGProof::compute_aggregate_kzg_proof(blobs, &self.trusted_setup) + /// Compute the kzg proof given a blob and its kzg commitment. + pub fn compute_blob_kzg_proof( + &self, + blob: Blob, + kzg_commitment: KzgCommitment, + ) -> Result { + c_kzg::KZGProof::compute_blob_kzg_proof(blob, kzg_commitment.into(), &self.trusted_setup) .map_err(Error::KzgProofComputationFailed) - .map(|proof| KzgProof(proof.to_bytes())) + .map(|proof| KzgProof(proof.to_bytes().into_inner())) } - /// Verify an aggregate kzg proof given the blobs that generated the proof, the kzg commitments - /// and the kzg proof. - pub fn verify_aggregate_kzg_proof( + /// Verify a kzg proof given the blob, kzg commitment and kzg proof. + pub fn verify_blob_kzg_proof( + &self, + blob: Blob, + kzg_commitment: KzgCommitment, + kzg_proof: KzgProof, + ) -> Result { + c_kzg::KZGProof::verify_blob_kzg_proof( + blob, + kzg_commitment.into(), + kzg_proof.into(), + &self.trusted_setup, + ) + .map_err(Error::InvalidKzgProof) + } + + /// Verify a batch of blob commitment proof triplets. + /// + /// Note: This method is slightly faster than calling `Self::verify_blob_kzg_proof` in a loop sequentially. + /// TODO(pawan): test performance against a parallelized rayon impl. + pub fn verify_blob_kzg_proof_batch( &self, blobs: &[Blob], - expected_kzg_commitments: &[KzgCommitment], - kzg_aggregated_proof: KzgProof, + kzg_commitments: &[KzgCommitment], + kzg_proofs: &[KzgProof], ) -> Result { - if blobs.len() != expected_kzg_commitments.len() { - return Err(Error::InvalidLength( - "blobs and expected_kzg_commitments should be of same size".to_string(), - )); - } - let commitments = expected_kzg_commitments + let commitments_bytes = kzg_commitments .iter() - .map(|comm| comm.0.into()) - .collect::>(); - let proof: c_kzg::KZGProof = kzg_aggregated_proof.0.into(); - proof - .verify_aggregate_kzg_proof(blobs, &commitments, &self.trusted_setup) - .map_err(Error::InvalidKzgProof) + .map(|comm| Bytes48::from_bytes(&comm.0)) + .collect::, _>>() + .map_err(Error::InvalidBytes)?; + + let proofs_bytes = kzg_proofs + .iter() + .map(|proof| Bytes48::from_bytes(&proof.0)) + .collect::, _>>() + .map_err(Error::InvalidBytes)?; + c_kzg::KZGProof::verify_blob_kzg_proof_batch( + blobs, + &commitments_bytes, + &proofs_bytes, + &self.trusted_setup, + ) + .map_err(Error::InvalidKzgProof) } /// Converts a blob to a kzg commitment. - pub fn blob_to_kzg_commitment(&self, blob: Blob) -> KzgCommitment { - KzgCommitment( - c_kzg::KZGCommitment::blob_to_kzg_commitment(blob, &self.trusted_setup).to_bytes(), - ) + pub fn blob_to_kzg_commitment(&self, blob: Blob) -> Result { + c_kzg::KZGCommitment::blob_to_kzg_commitment(blob, &self.trusted_setup) + .map_err(Error::InvalidBlob) + .map(|com| KzgCommitment(com.to_bytes().into_inner())) } }