|
| 1 | +use crate::BlockProcessingError; |
| 2 | +use eth2_hashing::hash_fixed; |
| 3 | +use itertools::{EitherOrBoth, Itertools}; |
| 4 | +use safe_arith::SafeArith; |
| 5 | +use ssz::Decode; |
| 6 | +use ssz_types::VariableList; |
| 7 | +use types::consts::eip4844::{BLOB_TX_TYPE, VERSIONED_HASH_VERSION_KZG}; |
| 8 | +use types::{ |
| 9 | + AbstractExecPayload, BeaconBlockBodyRef, EthSpec, ExecPayload, FullPayload, FullPayloadRef, |
| 10 | + KzgCommitment, Transaction, Transactions, VersionedHash, |
| 11 | +}; |
| 12 | + |
| 13 | +pub fn process_blob_kzg_commitments<T: EthSpec, Payload: AbstractExecPayload<T>>( |
| 14 | + block_body: BeaconBlockBodyRef<T, Payload>, |
| 15 | +) -> Result<(), BlockProcessingError> { |
| 16 | + if let (Ok(payload), Ok(kzg_commitments)) = ( |
| 17 | + block_body.execution_payload(), |
| 18 | + block_body.blob_kzg_commitments(), |
| 19 | + ) { |
| 20 | + if let Some(transactions) = payload.transactions() { |
| 21 | + if !verify_kzg_commitments_against_transactions::<T>(transactions, kzg_commitments)? { |
| 22 | + return Err(BlockProcessingError::BlobVersionHashMismatch); |
| 23 | + } |
| 24 | + } |
| 25 | + } |
| 26 | + |
| 27 | + Ok(()) |
| 28 | +} |
| 29 | + |
| 30 | +pub fn verify_kzg_commitments_against_transactions<T: EthSpec>( |
| 31 | + transactions: &Transactions<T>, |
| 32 | + kzg_commitments: &VariableList<KzgCommitment, T::MaxBlobsPerBlock>, |
| 33 | +) -> Result<bool, BlockProcessingError> { |
| 34 | + let nested_iter = transactions |
| 35 | + .into_iter() |
| 36 | + .filter(|tx| { |
| 37 | + tx.get(0) |
| 38 | + .map(|tx_type| *tx_type == BLOB_TX_TYPE) |
| 39 | + .unwrap_or(false) |
| 40 | + }) |
| 41 | + .map(|tx| tx_peek_blob_versioned_hashes::<T>(tx)); |
| 42 | + |
| 43 | + itertools::process_results(nested_iter, |iter| { |
| 44 | + let zipped_iter = iter |
| 45 | + .flatten() |
| 46 | + // Need to use `itertools::zip_longest` here because just zipping hides if one iter is shorter |
| 47 | + // and `itertools::zip_eq` panics. |
| 48 | + .zip_longest(kzg_commitments.into_iter()) |
| 49 | + .enumerate() |
| 50 | + .map(|(index, next)| match next { |
| 51 | + EitherOrBoth::Both(hash, commitment) => Ok((hash?, commitment)), |
| 52 | + // The number of versioned hashes from the blob transactions exceeds the number of |
| 53 | + // commitments in the block. |
| 54 | + EitherOrBoth::Left(_) => Err(BlockProcessingError::BlobNumCommitmentsMismatch { |
| 55 | + commitments_processed_in_block: index, |
| 56 | + commitments_processed_in_transactions: index.safe_add(1)?, |
| 57 | + }), |
| 58 | + // The number of commitments in the block exceeds the number of versioned hashes |
| 59 | + // in the blob transactions. |
| 60 | + EitherOrBoth::Right(_) => Err(BlockProcessingError::BlobNumCommitmentsMismatch { |
| 61 | + commitments_processed_in_block: index.safe_add(1)?, |
| 62 | + commitments_processed_in_transactions: index, |
| 63 | + }), |
| 64 | + }); |
| 65 | + |
| 66 | + itertools::process_results(zipped_iter, |mut iter| { |
| 67 | + iter.all(|(tx_versioned_hash, commitment)| { |
| 68 | + tx_versioned_hash == kzg_commitment_to_versioned_hash(commitment) |
| 69 | + }) |
| 70 | + }) |
| 71 | + })? |
| 72 | +} |
| 73 | + |
| 74 | +/// Only transactions of type `BLOB_TX_TYPE` should be passed into this function. |
| 75 | +fn tx_peek_blob_versioned_hashes<T: EthSpec>( |
| 76 | + opaque_tx: &Transaction<T::MaxBytesPerTransaction>, |
| 77 | +) -> Result< |
| 78 | + impl IntoIterator<Item = Result<VersionedHash, BlockProcessingError>> + '_, |
| 79 | + BlockProcessingError, |
| 80 | +> { |
| 81 | + let tx_len = opaque_tx.len(); |
| 82 | + let message_offset = 1.safe_add(u32::from_ssz_bytes(opaque_tx.get(1..5).ok_or( |
| 83 | + BlockProcessingError::BlobVersionHashIndexOutOfBounds { |
| 84 | + length: tx_len, |
| 85 | + index: 5, |
| 86 | + }, |
| 87 | + )?)?)?; |
| 88 | + |
| 89 | + let message_offset_usize = message_offset as usize; |
| 90 | + |
| 91 | + // field offset: 32 + 8 + 32 + 32 + 8 + 4 + 32 + 4 + 4 + 32 = 188 |
| 92 | + let blob_versioned_hashes_offset = message_offset.safe_add(u32::from_ssz_bytes( |
| 93 | + opaque_tx |
| 94 | + .get(message_offset_usize.safe_add(188)?..message_offset_usize.safe_add(192)?) |
| 95 | + .ok_or(BlockProcessingError::BlobVersionHashIndexOutOfBounds { |
| 96 | + length: tx_len, |
| 97 | + index: message_offset_usize.safe_add(192)?, |
| 98 | + })?, |
| 99 | + )?)?; |
| 100 | + |
| 101 | + let num_hashes = tx_len |
| 102 | + .safe_sub(blob_versioned_hashes_offset as usize)? |
| 103 | + .safe_div(32)?; |
| 104 | + |
| 105 | + Ok((0..num_hashes).into_iter().map(move |i| { |
| 106 | + let next_version_hash_index = |
| 107 | + (blob_versioned_hashes_offset as usize).safe_add(i.safe_mul(32)?)?; |
| 108 | + let bytes = opaque_tx |
| 109 | + .get(next_version_hash_index..next_version_hash_index.safe_add(32)?) |
| 110 | + .ok_or(BlockProcessingError::BlobVersionHashIndexOutOfBounds { |
| 111 | + length: tx_len, |
| 112 | + index: (next_version_hash_index as usize).safe_add(32)?, |
| 113 | + })?; |
| 114 | + Ok(VersionedHash::from_slice(bytes)) |
| 115 | + })) |
| 116 | +} |
| 117 | + |
| 118 | +fn kzg_commitment_to_versioned_hash(kzg_commitment: &KzgCommitment) -> VersionedHash { |
| 119 | + let mut hashed_commitment = hash_fixed(&kzg_commitment.0); |
| 120 | + hashed_commitment[0] = VERSIONED_HASH_VERSION_KZG; |
| 121 | + VersionedHash::from(hashed_commitment) |
| 122 | +} |
0 commit comments