diff --git a/beacon_chain/gossip_processing/block_processor.nim b/beacon_chain/gossip_processing/block_processor.nim
index 39871f9bf..cc3e69b3d 100644
--- a/beacon_chain/gossip_processing/block_processor.nim
+++ b/beacon_chain/gossip_processing/block_processor.nim
@@ -9,7 +9,7 @@
 
 import
   chronicles, chronos, metrics,
-  ../spec/[forks, signatures, signatures_batch],
+  ../spec/[forks, helpers_el, signatures, signatures_batch],
   ../sszdump
 
 from std/deques import Deque, addLast, contains, initDeque, items, len, shrink
@@ -548,8 +548,11 @@ proc storeBlock(
       if signedBlock.message.is_execution_block:
         template payload(): auto = signedBlock.message.body.execution_payload
 
-        template returnWithError(msg: string): untyped =
-          debug msg, executionPayload = shortLog(payload)
+        template returnWithError(msg: string, extraMsg = ""): untyped =
+          if extraMsg != "":
+            debug msg, reason = extraMsg, executionPayload = shortLog(payload)
+          else:
+            debug msg, executionPayload = shortLog(payload)
           self[].dumpInvalidBlock(signedBlock)
           doAssert strictVerification notin dag.updateFlags
           self.consensusManager.quarantine[].addUnviable(signedBlock.root)
@@ -563,10 +566,16 @@ proc storeBlock(
           returnWithError "Execution block hash validation failed"
 
         # [New in Deneb:EIP4844]
-        # TODO run https://github.com/ethereum/consensus-specs/blob/v1.3.0/specs/deneb/beacon-chain.md#blob-kzg-commitments
-        # https://github.com/ethereum/execution-apis/blob/main/src/engine/experimental/blob-extension.md#specification
-        # "This validation MUST be instantly run in all cases even during active
-        # sync process."
+        when typeof(signedBlock).kind >= ConsensusFork.Deneb:
+          let blobsRes = signedBlock.message.is_valid_versioned_hashes
+          if blobsRes.isErr:
+            returnWithError "Blob versioned hashes invalid", blobsRes.error
+        else:
+          # If there are EIP-4844 (type 3) transactions in the payload with
+          # versioned hashes, the transactions would be rejected by the EL
+          # based on payload timestamp (only allowed post Deneb);
+          # There are no `blob_kzg_commitments` before Deneb to compare against
+          discard
 
   let newPayloadTick = Moment.now()
 
diff --git a/beacon_chain/spec/helpers_el.nim b/beacon_chain/spec/helpers_el.nim
new file mode 100644
index 000000000..d33d1b39f
--- /dev/null
+++ b/beacon_chain/spec/helpers_el.nim
@@ -0,0 +1,46 @@
+# beacon_chain
+# Copyright (c) 2024 Status Research & Development GmbH
+# Licensed and distributed under either of
+#   * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
+#   * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
+# at your option. This file may not be copied, modified, or distributed except according to those terms.
+
+{.push raises: [].}
+
+import
+  std/typetraits,
+  eth/common/eth_types_rlp,
+  "."/[helpers, state_transition_block]
+
+func readExecutionTransaction(
+    txBytes: bellatrix.Transaction): Result[ExecutionTransaction, string] =
+  # Nim 2.0.8: `rlp.decode(distinctBase(txBytes), ExecutionTransaction)`
+  # uses the generic `read` from `rlp.nim` instead of the specific `read`
+  # from `eth_types_rlp.nim`, leading to compilation error.
+  # Doing this in two steps works around this resolution order issue.
+  var rlp = rlpFromBytes(distinctBase(txBytes))
+  try:
+    ok rlp.read(ExecutionTransaction)
+  except RlpError as exc:
+    err("Invalid transaction: " & exc.msg)
+
+# https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.4/specs/deneb/beacon-chain.md#is_valid_versioned_hashes
+func is_valid_versioned_hashes*(blck: ForkyBeaconBlock): Result[void, string] =
+  static: doAssert typeof(blck).kind >= ConsensusFork.Deneb
+  template transactions: untyped = blck.body.execution_payload.transactions
+  template commitments: untyped = blck.body.blob_kzg_commitments
+
+  var i = 0
+  for txBytes in transactions:
+    if txBytes.len == 0 or txBytes[0] != TxEip4844.byte:
+      continue  # Only blob transactions may have blobs
+    let tx = ? txBytes.readExecutionTransaction()
+    for vHash in tx.versionedHashes:
+      if commitments.len <= i:
+        return err("Extra blobs without matching `blob_kzg_commitments`")
+      if vHash.data != kzg_commitment_to_versioned_hash(commitments[i]):
+        return err("Invalid `blob_versioned_hash` at index " & $i)
+      inc i
+  if i != commitments.len:
+    return err("Extra `blob_kzg_commitments` without matching blobs")
+  ok()