Skip to content

Commit

Permalink
feat: use alloy types for BlobTransactionSidecar (#673)
Browse files Browse the repository at this point in the history
* feat: use alloy types for BlobTransactionSidecar

* type hints

* chore: no-std fix

* Update crates/eips/src/eip4844/sidecar.rs

Co-authored-by: Oliver Nordbjerg <[email protected]>

* use core

---------

Co-authored-by: Oliver Nordbjerg <[email protected]>
  • Loading branch information
mattsse and onbjerg authored May 3, 2024
1 parent c9b2a5b commit 44b8a6d
Show file tree
Hide file tree
Showing 7 changed files with 184 additions and 173 deletions.
6 changes: 3 additions & 3 deletions crates/consensus/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@ alloy-rlp.workspace = true
alloy-eips.workspace = true
alloy-serde = { workspace = true, optional = true }

sha2 = { version = "0.10", default-features = false }

# kzg
thiserror = { workspace = true, optional = true }
c-kzg = { workspace = true, features = ["serde"], optional = true }
Expand All @@ -30,15 +28,17 @@ arbitrary = { workspace = true, features = ["derive"], optional = true }
serde = { workspace = true, features = ["derive"], optional = true }

[dev-dependencies]
alloy-primitives = { workspace = true, features = ["arbitrary", "rand"] }
alloy-signer.workspace = true

arbitrary = { workspace = true, features = ["derive"] }
k256.workspace = true
tokio = { workspace = true, features = ["macros"] }
serde_json.workspace = true

[features]
default = ["std"]
std = ["alloy-eips/std", "sha2/std", "c-kzg?/std"]
std = ["alloy-eips/std", "c-kzg?/std"]
k256 = ["alloy-primitives/k256"]
kzg = ["dep:c-kzg", "dep:thiserror", "alloy-eips/kzg", "std"]
arbitrary = ["std", "dep:arbitrary", "alloy-eips/arbitrary"]
Expand Down
190 changes: 27 additions & 163 deletions crates/consensus/src/transaction/eip4844.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,31 +5,24 @@ pub mod utils;

use crate::{SignableTransaction, Signed, Transaction, TxType};

use alloy_eips::{
eip2930::AccessList,
eip4844::{BYTES_PER_BLOB, BYTES_PER_COMMITMENT, BYTES_PER_PROOF, DATA_GAS_PER_BLOB},
};
use alloy_primitives::{
keccak256, Address, Bytes, ChainId, FixedBytes, Signature, TxKind, B256, U256,
};
use alloy_eips::{eip2930::AccessList, eip4844::DATA_GAS_PER_BLOB};
use alloy_primitives::{keccak256, Address, Bytes, ChainId, Signature, TxKind, B256, U256};
use alloy_rlp::{length_of_length, BufMut, Decodable, Encodable, Header};
use core::mem;
use sha2::{Digest, Sha256};

#[doc(inline)]
pub use alloy_eips::eip4844::BlobTransactionSidecar;

#[cfg(not(feature = "std"))]
use alloc::vec::Vec;

#[cfg(not(feature = "kzg"))]
pub use alloy_eips::eip4844::{Blob, Bytes48};

#[cfg(feature = "kzg")]
use c_kzg::{KzgCommitment, KzgProof, KzgSettings};
#[cfg(feature = "kzg")]
use std::ops::Deref;

#[cfg(feature = "kzg")]
pub use c_kzg::{Blob, Bytes48};

/// An error that can occur when validating a [TxEip4844Variant].
#[derive(Debug, thiserror::Error)]
#[cfg(feature = "kzg")]
Expand Down Expand Up @@ -462,7 +455,8 @@ impl TxEip4844 {
let commitment = KzgCommitment::from(*commitment.deref());

// calculate & verify versioned hash
let calculated_versioned_hash = kzg_to_versioned_hash(commitment.as_slice());
let calculated_versioned_hash =
alloy_eips::eip4844::kzg_to_versioned_hash(commitment.as_slice());
if *versioned_hash != calculated_versioned_hash {
return Err(BlobTransactionValidationError::WrongVersionedHash {
have: *versioned_hash,
Expand All @@ -471,12 +465,20 @@ impl TxEip4844 {
}
}

let res = KzgProof::verify_blob_kzg_proof_batch(
sidecar.blobs.as_slice(),
sidecar.commitments.as_slice(),
sidecar.proofs.as_slice(),
proof_settings,
)
// SAFETY: ALL types have the same size
let res = unsafe {
KzgProof::verify_blob_kzg_proof_batch(
// blobs
std::mem::transmute::<&[Blob], &[c_kzg::Blob]>(sidecar.blobs.as_slice()),
// commitments
std::mem::transmute::<&[Bytes48], &[c_kzg::Bytes48]>(
sidecar.commitments.as_slice(),
),
// proofs
std::mem::transmute::<&[Bytes48], &[c_kzg::Bytes48]>(sidecar.proofs.as_slice()),
proof_settings,
)
}
.map_err(BlobTransactionValidationError::KZGError)?;

if res {
Expand Down Expand Up @@ -859,7 +861,7 @@ impl TxEip4844WithSidecar {
// now write the fields
self.tx.encode_fields(out);
signature.write_rlp_vrs(out);
self.sidecar.encode_inner(out);
self.sidecar.encode(out);
}

/// Decodes the transaction from RLP bytes, including the signature.
Expand All @@ -884,7 +886,7 @@ impl TxEip4844WithSidecar {
let inner_tx = TxEip4844::decode_signed_fields(buf)?;

// decode the sidecar
let sidecar = BlobTransactionSidecar::decode_inner(buf)?;
let sidecar = BlobTransactionSidecar::decode(buf)?;

if buf.len() + header.payload_length != original_len {
return Err(alloy_rlp::Error::ListLengthMismatch {
Expand Down Expand Up @@ -972,155 +974,17 @@ impl Transaction for TxEip4844WithSidecar {
}
}

/// This represents a set of blobs, and its corresponding commitments and proofs.
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
#[repr(C)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct BlobTransactionSidecar {
/// The blob data.
pub blobs: Vec<Blob>,
/// The blob commitments.
pub commitments: Vec<Bytes48>,
/// The blob proofs.
pub proofs: Vec<Bytes48>,
}

impl BlobTransactionSidecar {
/// Constructs a new [BlobTransactionSidecar] from a set of blobs, commitments, and proofs.
pub fn new(blobs: Vec<Blob>, commitments: Vec<Bytes48>, proofs: Vec<Bytes48>) -> Self {
Self { blobs, commitments, proofs }
}

/// Returns an iterator over the versioned hashes of the commitments.
pub fn versioned_hashes(&self) -> impl Iterator<Item = B256> + '_ {
self.commitments.iter().map(|c| kzg_to_versioned_hash(c.as_slice()))
}

/// Returns the versioned hash for the blob at the given index, if it
/// exists.
pub fn versioned_hash_for_blob(&self, blob_index: usize) -> Option<B256> {
self.commitments.get(blob_index).map(|c| kzg_to_versioned_hash(c.as_slice()))
}

/// Encodes the inner [BlobTransactionSidecar] fields as RLP bytes, without a RLP header.
///
/// This encodes the fields in the following order:
/// - `blobs`
/// - `commitments`
/// - `proofs`
#[inline]
pub(crate) fn encode_inner(&self, out: &mut dyn BufMut) {
BlobTransactionSidecarRlp::wrap_ref(self).encode(out);
}

/// Decodes the inner [BlobTransactionSidecar] fields from RLP bytes, without a RLP header.
///
/// This decodes the fields in the following order:
/// - `blobs`
/// - `commitments`
/// - `proofs`
pub(crate) fn decode_inner(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
Ok(BlobTransactionSidecarRlp::decode(buf)?.unwrap())
}

/// Outputs the RLP length of the [BlobTransactionSidecar] fields, without a RLP header.
pub fn fields_len(&self) -> usize {
BlobTransactionSidecarRlp::wrap_ref(self).fields_len()
}

/// Calculates a size heuristic for the in-memory size of the [BlobTransactionSidecar].
#[inline]
pub fn size(&self) -> usize {
self.blobs.len() * BYTES_PER_BLOB + // blobs
self.commitments.len() * BYTES_PER_COMMITMENT + // commitments
self.proofs.len() * BYTES_PER_PROOF // proofs
}
}

impl Encodable for BlobTransactionSidecar {
/// Encodes the inner [BlobTransactionSidecar] fields as RLP bytes, without a RLP header.
fn encode(&self, s: &mut dyn BufMut) {
self.encode_inner(s);
}

fn length(&self) -> usize {
self.fields_len()
}
}

impl Decodable for BlobTransactionSidecar {
/// Decodes the inner [BlobTransactionSidecar] fields from RLP bytes, without a RLP header.
fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
Self::decode_inner(buf)
}
}

// Wrapper for c-kzg rlp
#[repr(C)]
struct BlobTransactionSidecarRlp {
blobs: Vec<FixedBytes<BYTES_PER_BLOB>>,
commitments: Vec<FixedBytes<BYTES_PER_COMMITMENT>>,
proofs: Vec<FixedBytes<BYTES_PER_PROOF>>,
}

const _: [(); mem::size_of::<BlobTransactionSidecar>()] =
[(); mem::size_of::<BlobTransactionSidecarRlp>()];

impl BlobTransactionSidecarRlp {
const fn wrap_ref(other: &BlobTransactionSidecar) -> &Self {
// SAFETY: Same repr and size
unsafe { &*(other as *const BlobTransactionSidecar).cast::<Self>() }
}

fn unwrap(self) -> BlobTransactionSidecar {
// SAFETY: Same repr and size
unsafe { mem::transmute(self) }
}

fn encode(&self, out: &mut dyn BufMut) {
// Encode the blobs, commitments, and proofs
self.blobs.encode(out);
self.commitments.encode(out);
self.proofs.encode(out);
}

fn fields_len(&self) -> usize {
self.blobs.length() + self.commitments.length() + self.proofs.length()
}

fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
Ok(Self {
blobs: Decodable::decode(buf)?,
commitments: Decodable::decode(buf)?,
proofs: Decodable::decode(buf)?,
})
}
}

/// Calculates the versioned hash for a KzgCommitment
///
/// Specified in [EIP-4844](https://eips.ethereum.org/EIPS/eip-4844#header-extension)
pub(crate) fn kzg_to_versioned_hash(commitment: &[u8]) -> B256 {
debug_assert_eq!(commitment.len(), 48, "commitment length is not 48");
let mut res = Sha256::digest(commitment);
res[0] = alloy_eips::eip4844::VERSIONED_HASH_VERSION_KZG;
B256::new(res.into())
}

#[cfg(test)]
mod tests {
use super::{BlobTransactionSidecar, TxEip4844, TxEip4844WithSidecar};
use crate::{SignableTransaction, TxEip4844Variant, TxEnvelope};
use alloy_eips::eip2930::AccessList;
use alloy_eips::{
eip2930::AccessList,
eip4844::{Blob, Bytes48},
};
use alloy_primitives::{address, b256, bytes, Signature, U256};
use alloy_rlp::{Decodable, Encodable};

#[cfg(not(feature = "kzg"))]
use alloy_eips::eip4844::{Blob, Bytes48};

#[cfg(feature = "kzg")]
use c_kzg::{Blob, Bytes48};

#[test]
fn different_sidecar_same_hash() {
// this should make sure that the hash calculated for the `into_signed` conversion does not
Expand Down
21 changes: 16 additions & 5 deletions crates/consensus/src/transaction/eip4844/builder.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
#[cfg(not(feature = "kzg"))]
use alloy_eips::eip4844::Blob;
#[cfg(feature = "kzg")]
use c_kzg::{Blob, KzgCommitment, KzgProof};
use c_kzg::{KzgCommitment, KzgProof};

#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
Expand Down Expand Up @@ -351,13 +350,25 @@ impl<T: SidecarCoder> SidecarBuilder<T> {
let mut commitments = Vec::with_capacity(self.inner.blobs.len());
let mut proofs = Vec::with_capacity(self.inner.blobs.len());
for blob in self.inner.blobs.iter() {
// SAFETY: same size
let blob = unsafe { std::mem::transmute::<&Blob, &c_kzg::Blob>(blob) };
let commitment = KzgCommitment::blob_to_kzg_commitment(blob, settings)?;
let proof = KzgProof::compute_blob_kzg_proof(blob, &commitment.to_bytes(), settings)?;
commitments.push(commitment.to_bytes());
proofs.push(proof.to_bytes());

// SAFETY: same size
unsafe {
commitments.push(
std::mem::transmute::<c_kzg::Bytes48, alloy_eips::eip4844::Bytes48>(
commitment.to_bytes(),
),
);
proofs.push(std::mem::transmute::<c_kzg::Bytes48, alloy_eips::eip4844::Bytes48>(
proof.to_bytes(),
));
}
}

Ok(crate::BlobTransactionSidecar { blobs: self.inner.blobs, commitments, proofs })
Ok(crate::BlobTransactionSidecar::new(self.inner.blobs, commitments, proofs))
}

/// Build the sidecar from the data, with default (Ethereum Mainnet)
Expand Down
2 changes: 1 addition & 1 deletion crates/consensus/src/transaction/envelope.rs
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,7 @@ mod tests {
use super::*;
use crate::transaction::SignableTransaction;
use alloy_eips::eip2930::{AccessList, AccessListItem};
use alloy_primitives::{hex, Address, Bytes, Signature, TxKind, B256, U256};
use alloy_primitives::{hex, Address, Bytes, Signature, TxKind, U256};
use std::{fs, path::PathBuf, vec};

#[cfg(not(feature = "std"))]
Expand Down
2 changes: 2 additions & 0 deletions crates/eips/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ alloy-primitives = { workspace = true, features = ["rlp"], default-features = fa
alloy-rlp = { workspace = true, features = ["derive"], default-features = false }
alloy-serde.workspace = true

sha2 = { workspace = true, default-features = false }

# serde
serde = { workspace = true, default-features = false, optional = true }

Expand Down
22 changes: 21 additions & 1 deletion crates/eips/src/eip4844/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@ pub mod env_settings;
#[cfg(feature = "kzg")]
pub mod trusted_setup_points;

use alloy_primitives::{b256, FixedBytes, U256};
/// Contains sidecar related types
mod sidecar;
pub use sidecar::BlobTransactionSidecar;

use alloy_primitives::{b256, FixedBytes, B256, U256};

/// The modulus of the BLS group used in the KZG commitment scheme. All field
/// elements contained in a blob MUST be STRICTLY LESS than this value.
Expand Down Expand Up @@ -75,6 +79,22 @@ pub type Blob = FixedBytes<BYTES_PER_BLOB>;
/// A commitment/proof serialized as 0x-prefixed hex string
pub type Bytes48 = FixedBytes<48>;

/// Calculates the versioned hash for a KzgCommitment of 48 bytes.
///
/// Specified in [EIP-4844](https://eips.ethereum.org/EIPS/eip-4844#header-extension)
///
/// # Panics
///
/// If the given commitment is not 48 bytes long.
pub fn kzg_to_versioned_hash(commitment: &[u8]) -> B256 {
use sha2::Digest;

debug_assert_eq!(commitment.len(), 48, "commitment length is not 48");
let mut res = sha2::Sha256::digest(commitment);
res[0] = VERSIONED_HASH_VERSION_KZG;
B256::new(res.into())
}

/// Calculates the `excess_blob_gas` from the parent header's `blob_gas_used` and `excess_blob_gas`.
///
/// See also [the EIP-4844 helpers](https://eips.ethereum.org/EIPS/eip-4844#helpers)
Expand Down
Loading

0 comments on commit 44b8a6d

Please sign in to comment.