Alert Source Discuss
⚠️ Draft Standards Track: Core

EIP-8142: Block-in-Blobs (BiB)

Ensures execution payload data availability via blobs

Authors Kevaundray Wedderburn (@kevaundray), Ignacio Hagopian (@jsign), Jihoon Song <jihoon.song@ethereum.org>, Francesco Risitano (@frisitano), Thomas Thiery (@soispoke) <thomas.thiery@ethereum.org>
Created 2026-01-29
Discussion Link https://ethereum-magicians.org/t/eip-8142-block-in-blobs-bib/27621
Requires EIP-4844, EIP-7594, EIP-7892, EIP-7928

Abstract

zkEVMs allow validators to verify the correctness of an execution payload using a proof, without downloading or executing the payload itself. However, removing the requirement to download the execution payload, also removes the implicit data availability (DA) guarantee; a block producer can publish a valid proof and withhold the execution-payload data since attesters no longer need them for consensus.

This EIP introduces Block-in-Blobs (BiB), a mechanism that requires the execution-payload data (transactions and BALs) to be published in blob data, in the same beacon block that carries the corresponding execution payload’s header. This ensures that execution payloads and their associated data are always published even when validators no longer require them to verify the state transition function (STF).

In short, BiB works by having the block producer encode the execution-payload data into blobs as part of the execution layer’s STF, requiring the beacon block’s blob KZG commitments to commit to those payload-blobs.

Motivation

Validation via re-execution

Today, validators verify execution payloads by:

1) Downloading the execution payload 2) Executing the payload locally 3) Checking the resulting state root and other fields against the fields in the header

Implicitly this guarantees execution payload availability because the payload cannot be verified unless the node downloads it.

Validation with zkEVMs

With zkEVMs, validators instead:

1) Download a proof attesting to the correctness of the execution payload 2) Download the execution payload header 3) Verify the proof with respects to the payload header (and other commitments)

In this model, validators no longer require access to the full execution payload data itself in order to verify its correctness.

The DA problem

Removing the re-execution requirement in consensus removes the implicit requirement that consensus clients download the execution payload.

A malicious or rational builder could:

  • Publish a valid proof for a valid execution payload
  • Withhold the execution-payload data entirely

Builders: Since builders will always need to re-execute in order to build blocks, a malicious builder would not publish the execution payload ensuring that they are the only ones that can build on top of the current chain.

RPC and indexers: Many nodes such as RPC providers and indexers cannot solely rely on execution proofs and must re-execute the execution payload.

BiB addresses this by making the execution payload available via blobs.

Specification

Terminology

Type-3 transaction: Refers to EIP-4844 blob-carrying transactions (transaction type 0x03). These transactions include blob versioned hashes that commit to blobs carrying user data.

Overview and Invariants

BiB ensures the proven payload is published:

  • The beacon block references a list of blob KZG commitments (via 4844/PeerDas)
  • A prefix of those commitments is reserved for the execution-payload data encoded into blobs
  • A zkEVM proof for the block must bind the proven execution payload to those prefixed blob commitments.

Payload availability invariant: A valid block implies there exists an ordered list of blobs whose bytes decode to the canonical execution-payload data, and the KZG commitments for these blobs match the first payload_blob_count blob commitments referenced by the block. The existing DAS mechanism will ensure that those blobs are available.

Parameters

Referenced Parameters

These parameters are defined in EIP-4844 and related specs:

Name Value Source
FIELD_ELEMENTS_PER_BLOB 4096 EIP-4844
BYTES_PER_FIELD_ELEMENT 32 EIP-4844
GAS_PER_BLOB 2**17 EIP-4844
MAX_BLOBS_PER_BLOCK Network-specific EIP-7892

Note on MAX_BLOBS_PER_BLOCK: This constant represents the maximum number of blobs (both payload-blobs and type-3 transaction blobs) that can be included in a block. Per EIP-7892, the execution layer’s blobSchedule.max MUST equal the consensus layer’s MAX_BLOBS_PER_BLOCK configuration value at any given time. This value may change across forks (e.g., initially 6 in EIP-4844, potentially increased in subsequent blob throughput increase proposals).

Derived Constants

Name Value Description
USABLE_BYTES_PER_FIELD_ELEMENT BYTES_PER_FIELD_ELEMENT - 1 (31) Usable bytes per field element (final byte must be zero to stay under BLS modulus)
USABLE_BYTES_PER_BLOB FIELD_ELEMENTS_PER_BLOB * USABLE_BYTES_PER_FIELD_ELEMENT Total usable bytes per blob

Execution Layer

Summary: The execution layer is modified in the following ways:

  • The EL header now has a payload_blob_count field so that we can accurately compute the total blob_gas_used. We include the payload-blobs in this calculation and not just type-3 transactions, so that blob_gas_used accurately represents how many blobs the CL used.
  • engine_getPayload computes the payload-blobs when building a block, sets payload_blob_count in the header, and returns the payload blobs (with their commitments and proofs) alongside type-3 transaction blobs in the response.
  • engine_newPayload takes the ExecutionPayload and before passing it to the EL STF, it computes the payload-blobs, checks that the amount of blobs needed is equal to the payload_blob_count value in the ExecutionPayload header and verifies that the expected version hashes match.

Referenced Helpers

The execution layer uses methods and classes defined in the corresponding consensus 4844/7594 specs.

Specifically, we use the following methods from polynomial-commitments.md:

And the following methods from polynomial-commitments-sampling.md:

And the following methods from beacon-chain.md:

Helpers

bytes_to_blobs
def bytes_to_blobs(data: bytes) -> List[Blob]:
    """
    Pack arbitrary bytes into one or more blobs.
    Remaining space in final blob is zero-padded.
    """
    blobs = []
    offset = 0

    while offset < len(data):
        chunk = data[offset : offset + USABLE_BYTES_PER_BLOB]
        blob = chunk_to_blob(chunk)
        blobs.append(blob)
        offset += USABLE_BYTES_PER_BLOB

    return blobs


def chunk_to_blob(data: bytes) -> Blob:
    """
    Pack up to USABLE_BYTES_PER_BLOB bytes into a single blob.
    If data is shorter than USABLE_BYTES_PER_BLOB, it is zero-padded.
    Each 31-byte chunk is stored in bytes [0:31] of a field element,
    with byte [31] (the final byte) set to zero to ensure value < BLS modulus.
    """
    assert len(data) <= USABLE_BYTES_PER_BLOB

    # Pad to exactly USABLE_BYTES_PER_BLOB if needed
    if len(data) < USABLE_BYTES_PER_BLOB:
        data = data + bytes(USABLE_BYTES_PER_BLOB - len(data))

    blob = bytearray(FIELD_ELEMENTS_PER_BLOB * BYTES_PER_FIELD_ELEMENT)

    for i in range(FIELD_ELEMENTS_PER_BLOB):
        chunk_start = i * USABLE_BYTES_PER_FIELD_ELEMENT
        chunk = data[chunk_start : chunk_start + USABLE_BYTES_PER_FIELD_ELEMENT]

        # Store 31 data bytes in [0:31], the final byte [31] stays zero
        blob[i * BYTES_PER_FIELD_ELEMENT : i * BYTES_PER_FIELD_ELEMENT + USABLE_BYTES_PER_FIELD_ELEMENT] = chunk

    return Blob(blob)
blobs_to_bytes
def blobs_to_bytes(blobs: List[Blob]) -> bytes:
    """
    Unpack blobs back to bytes.
    Returns all usable bytes from all blobs (including any padding).
    """
    raw = bytearray()

    for blob in blobs:
        raw.extend(blob_to_chunk(blob))

    return bytes(raw)


def blob_to_chunk(blob: Blob) -> bytes:
    """
    Extract the 31 usable bytes from each field element.
    Validates that the final byte is zero for each field element.
    """
    result = bytearray()

    for i in range(FIELD_ELEMENTS_PER_BLOB):
        # Validate final byte is zero
        assert blob[i * BYTES_PER_FIELD_ELEMENT + USABLE_BYTES_PER_FIELD_ELEMENT] == 0x00, "Invalid blob: final byte must be zero"

        # Extract usable data bytes
        result.extend(blob[i * BYTES_PER_FIELD_ELEMENT : i * BYTES_PER_FIELD_ELEMENT + USABLE_BYTES_PER_FIELD_ELEMENT])

    return bytes(result)
get_execution_payload_data
def get_execution_payload_data(payload: ExecutionPayload) -> ExecutionPayloadData:
    """
    Extract the data from an ExecutionPayload that must be made available via blobs.
    """
    return ExecutionPayloadData(
        blockAccessList=payload.blockAccessList,
        transactions=payload.transactions,
    )
execution_payload_data_to_blobs
def execution_payload_data_to_blobs(data: ExecutionPayloadData) -> List[Blob]:
    """
    Canonically encode the execution-payload data into an ordered list of blobs.

    Encoding steps:
      1. bal_bytes = data.blockAccessList
      2. transactions_bytes = RLP.encode(data.transactions)
      3. Create 10-byte header: [2 bytes version][4 bytes BAL length][4 bytes tx length]
      4. payload_bytes = header + bal_bytes + transactions_bytes
      5. return bytes_to_blobs(payload_bytes)

    The first blob will contain (in order):
      - [2 bytes] Payload encoding version (currently 0)
      - [4 bytes] BAL data length
      - [4 bytes] Transaction data length
      - [variable] BAL data (may span multiple blobs)
      - [variable] Transaction data (may span multiple blobs)

    This allows extracting just the BAL data without transactions.

    Note: blockAccessList is already RLP-encoded per EIP-7928. Transactions are RLP-encoded as a list.
    """
    bal_bytes = data.blockAccessList
    transactions_bytes = RLP.encode(data.transactions)

    # Create 10-byte header: [2 bytes version][4 bytes BAL length][4 bytes tx length]
    version = (0).to_bytes(2, 'little')
    bal_length = len(bal_bytes).to_bytes(4, 'little')
    txs_length = len(transactions_bytes).to_bytes(4, 'little')
    header = version + bal_length + txs_length

    # Combine header + data
    payload_bytes = header + bal_bytes + transactions_bytes

    return bytes_to_blobs(payload_bytes)
blobs_to_execution_payload_data
def blobs_to_execution_payload_data(blobs: List[Blob]) -> ExecutionPayloadData:
    """
    Canonically decode an ordered list of blobs into execution-payload data.

    Decoding steps:
      1. raw = blobs_to_bytes(blobs)
      2. Read 10-byte header: [2 bytes version][4 bytes BAL length][4 bytes tx length]
      3. Validate version is 0
      4. Split into bal_bytes and transactions_bytes
      5. return ExecutionPayloadData(blockAccessList, transactions)
    """
    # Extract raw bytes from blobs
    raw = blobs_to_bytes(blobs)

    # Read 10-byte header
    version = int.from_bytes(raw[0:2], 'little')
    assert version == 0, f"Unsupported payload encoding version: {version}"

    bal_length = int.from_bytes(raw[2:6], 'little')
    txs_length = int.from_bytes(raw[6:10], 'little')

    # Extract data portions
    bal_bytes = raw[10 : 10 + bal_length]
    transactions_bytes = raw[10 + bal_length : 10 + bal_length + txs_length]

    # Decode transactions
    transactions = RLP.decode(transactions_bytes)

    return ExecutionPayloadData(blockAccessList=bal_bytes, transactions=transactions)
extract_data_from_type_3_tx

This method will be used to implement the modified logic in engine_getPayload.

def extract_data_from_type_3_tx(transactions: List[Transaction]) -> Tuple[List[Blob], List[KZGCommitment], List[List[KZGProof]]]:
    """
    Extract blobs, KZG commitments, and cell proofs from type-3 (blob) transactions.

    Returns a tuple of (blobs, commitments, cell_proofs) in the order they appear
    in the transaction list.

    Implementation note: This is not new logic - a function(s) like this should already be available in your existing getPayload/blob bundle implementation.
    """
    ...

Invertibility invariant: execution_payload_data_to_blobs and blobs_to_execution_payload_data are mutual inverses on valid execution-payload data.

Data structures

ExecutionPayloadData

Execution-payload data refers to the subset of the ExecutionPayload that must be made available via blobs. This includes:

  • bals (Block Access List added in EIP-7928)
  • transactions

See What is included in execution-payload data? in the Rationale for details.

class ExecutionPayloadData(Container):
    # Block Access List (BAL) from EIP-7928, RLP-encoded
    blockAccessList: bytes
    # List of transactions, each transaction is RLP-encoded
    transactions: List[Transaction, MAX_TRANSACTIONS_PER_PAYLOAD]

Note: This is not an SSZ Container - fields are RLP-encoded as described in the encoding functions. The MAX_TRANSACTIONS_PER_PAYLOAD bound is inherited from the ExecutionPayload field limit defined in the consensus specification.

ExecutionPayloadHeader

This EIP adds a new field to the ExecutionPayloadHeader:

  • payload_blob_count : uint64

Semantics:

  • Let blob_kzg_commitments be the ordered list of kzg commitments referenced by the beacon block
  • The first payload_blob_count entries of blob_kzg_commitments are the payload-blob commitments (i.e. commitments to the blobs that correspond to the payload data)
  • The remaining entries (if any) are for type-3 blob transactions.
BlobsBundle

For the zkEVM-optimized variant of engine_getPayload, this EIP extends BlobsBundle with an additional field:

  • payload_kzg_proofs: List[KZGProof]

Semantics:

  • Contains random point KZG proofs for payload blobs only (not type-3 transaction blobs)
  • Used as private inputs to the zkEVM circuit for payload consistency verification via verify_blob_kzg_proof_batch
  • The length of this list equals payload_blob_count

Validation

On the execution layer, the block validation rules are modified as follows:

def validate_block(block: Block):
  # Initialize blob_gas_used to account for payload-blobs
  blob_gas_used = block.payload_blob_count * GAS_PER_BLOB

  # ... rest of validation, including executing transactions
  # Type-3 transactions will add their blob gas to blob_gas_used during execution

Rationale: Instead of starting blob_gas_used at 0, we initialize it with the gas consumed by payload-blobs. As transactions execute, type-3 transactions will increment blob_gas_used by their blob gas usage. The final blob_gas_used value thus equals: (payload_blob_count * GAS_PER_BLOB) + (type-3 blob gas).

Verification: The EL STF in isolation trusts that payload_blob_count in the header is correct, as it cannot verify blob contents in validate_block. Correctness of payload_blob_count is enforced at the Engine API boundary (in engine_newPayload), which recomputes the payload blobs from execution-payload data and verifies consistency with the beacon block’s blob commitments.

Note: This change does not affect Consensus Layer blob accounting rules; it only ensures that blob_gas_used in the execution payload header accurately reflects total blob usage, including both payload-blobs and type-3 transaction blobs.

Engine API

This section specifies two equivalent formulations of new_payload. Implementers choose one based on their execution context:

  • Native execution variant: Uses blob_to_kzg_commitment directly. Suitable for pre mandatory proofs implementations.
  • zkEVM-optimized variant: Uses polynomial openings via verify_blob_kzg_proof_batch. Avoids the multiscalar multiplication (MSM) which is expensive to prove in a zkEVM circuit.

Both variants enforce identical validity conditions. A block valid under one is valid under the other.

engine_getPayload - Native Variant

The builder must compute the payload blob count when constructing the block:

def get_payload(payload_id: PayloadId) -> GetPayloadResponse:
    # 1. Build the block (select transactions, etc.)
    payload = build_execution_payload(payload_id)

    # 2. Compute payload blobs to determine count
    payload_data = get_execution_payload_data(payload)
    payload_blobs = execution_payload_data_to_blobs(payload_data)
    payload_blob_count = len(payload_blobs)

    # 4. Set the count in the header
    payload.payload_blob_count = payload_blob_count

    # 5. Compute blob commitments and cell proofs for payload blobs
    payload_commitments = [blob_to_kzg_commitment(b) for b in payload_blobs]
    payload_versioned_hashes = [kzg_commitment_to_versioned_hash(c) for c in payload_commitments]
    # Compute cells and cell proofs
    payload_cells_and_proofs = [compute_cells_and_kzg_proofs(b) for b in payload_blobs]
    payload_cell_proofs = [proofs for _, proofs in payload_cells_and_proofs]

    # 6. Extract type-3 transaction blobs, commitments, and cell proofs
    type3_blobs, type3_commitments, type3_cell_proofs = extract_data_from_type_3_tx(payload.transactions)
    type3_versioned_hashes = []
    for tx in payload.transactions:
        if tx.type == BLOB_TX_TYPE:
            type3_versioned_hashes.extend(tx.blob_versioned_hashes)

    # 7. Combine: payload blobs first, then type-3 blobs
    all_blobs = payload_blobs + type3_blobs
    all_commitments = payload_commitments + type3_commitments
    all_cell_proofs = payload_cell_proofs + type3_cell_proofs
    all_blob_versioned_hashes = payload_versioned_hashes + type3_versioned_hashes

    return GetPayloadResponse(
        execution_payload=payload,
        blobs_bundle=BlobsBundle(
            commitments=all_commitments,
            blobs=all_blobs,
            proofs=all_cell_proofs
        ),
        block_value=calculate_block_value(payload)
    )

Note: The builder must account for payload blob usage when selecting type-3 transactions to ensure the total blob count does not exceed MAX_BLOBS_PER_BLOCK.

engine_getPayload - zkEVM-Optimized Variant

For the zkEVM-optimized variant, the builder must additionally compute random point KZG proofs for the payload blobs, which will be used as private inputs in the zkEVM circuit for payload consistency verification.

This variant extends BlobsBundle with an additional payload_kzg_proofs field containing random point KZG proofs for payload blobs.

def get_payload_zk(payload_id: PayloadId) -> GetPayloadResponse:
    # 1. Build the block (select transactions, etc.)
    payload = build_execution_payload(payload_id)

    # 2. Compute payload blobs to determine count
    payload_data = get_execution_payload_data(payload)
    payload_blobs = execution_payload_data_to_blobs(payload_data)
    payload_blob_count = len(payload_blobs)

    # 4. Set the count in the header
    payload.payload_blob_count = payload_blob_count

    # 5. Compute commitments, cell proofs, and random point proofs for payload blobs
    payload_commitments = [blob_to_kzg_commitment(b) for b in payload_blobs]
    payload_versioned_hashes = [kzg_commitment_to_versioned_hash(c) for c in payload_commitments]
    # Cell proofs
    payload_cells_and_proofs = [compute_cells_and_kzg_proofs(b) for b in payload_blobs]
    payload_cell_proofs = [proofs for _, proofs in payload_cells_and_proofs]
    # Random point proofs for payload consistency verification
    payload_random_point_proofs = [compute_blob_kzg_proof(b, c) for b, c in zip(payload_blobs, payload_commitments)]

    # 6. Extract type-3 transaction blobs, commitments, and cell proofs
    type3_blobs, type3_commitments, type3_cell_proofs = extract_data_from_type_3_tx(payload.transactions)
    type3_versioned_hashes = []
    for tx in payload.transactions:
        if tx.type == BLOB_TX_TYPE:
            type3_versioned_hashes.extend(tx.blob_versioned_hashes)

    # 7. Combine: payload blobs first, then type-3 blobs
    all_blobs = payload_blobs + type3_blobs
    all_commitments = payload_commitments + type3_commitments
    all_cell_proofs = payload_cell_proofs + type3_cell_proofs

    return GetPayloadResponse(
        execution_payload=payload,
        blobs_bundle=BlobsBundle(
            commitments=all_commitments,
            blobs=all_blobs,
            proofs=all_cell_proofs,
            payload_kzg_proofs=payload_random_point_proofs
        ),
        block_value=calculate_block_value(payload)
    )

Note for implementors:

  • The payload_kzg_proofs field contains KZG opening proofs for payload blobs only. It is used for payload consistency verification via verify_blob_kzg_proof_batch.
  • The builder/prover should extract the first payload_blob_count commitments from all_commitments (i.e., all_commitments[:payload_blob_count]). This corresponds to the payload_kzg_commitments parameter in the zkEVM variant of engine_newPayload.
engine_newPayload - Native Execution Variant
def new_payload(
    payload: ExecutionPayload,
    expected_blob_versioned_hashes: List[VersionedHash],
    ...
) -> PayloadStatus:

    # 1. Derive payload blobs and commitments
    payload_data = get_execution_payload_data(payload)
    payload_blobs = execution_payload_data_to_blobs(payload_data)
    payload_blob_count = len(payload_blobs)
    payload_commitments = [blob_to_kzg_commitment(b) for b in payload_blobs]
    payload_versioned_hashes = [kzg_commitment_to_versioned_hash(c) for c in payload_commitments]

    # 2. Verify payload_blob_count matches header
    assert payload_blob_count == payload.payload_blob_count

    # 3. Extract type-3 tx versioned hashes
    type3_versioned_hashes = []
    for tx in payload.transactions:
        if tx.type == BLOB_TX_TYPE:
            type3_versioned_hashes.extend(tx.blob_versioned_hashes)

    # 4. Verify versioned hashes: payload blobs first, then type-3
    assert expected_blob_versioned_hashes == payload_versioned_hashes + type3_versioned_hashes

    # 5. Run EL STF (which now checks correct blob_gas_used blob limit using header.payload_blob_count)
    return execute_payload(payload)
engine_newPayload - zkEVM-Optimized Variant

Note: Once zkEVM proofs are required for consensus, newPayload will be executed inside a zkEVM to generate a proof, rather than being executed natively by validators. This variant is designed to be cheaper in that context.

This variant replaces the MSM in blob_to_kzg_commitment with polynomial opening proofs, which are cheaper to verify inside a zkEVM. The payload, commitments and KZG proofs are private inputs to the zkEVM circuit, while the corresponding versioned hashes (and payload header) are public inputs.

def new_payload(
    payload: ExecutionPayload,
    expected_blob_versioned_hashes: List[VersionedHash], # public input

    # BiB additions: prefix metadata for payload blobs
    payload_kzg_commitments: List[KZGCommitment],  # private input
    payload_kzg_proofs: List[KZGProof],            # private input
    ...
) -> PayloadStatus:

    # 0. Declared payload blob count from the header
    n = payload.payload_blob_count
    assert len(payload_kzg_commitments) == n
    assert len(payload_kzg_proofs) == n

    # 1. Construct payload blobs from execution-payload data
    payload_data = get_execution_payload_data(payload)
    payload_blobs = execution_payload_data_to_blobs(payload_data)
    assert len(payload_blobs) == n

    # 2. Check the commitments correspond to the expected versioned hash prefix
    payload_versioned_hashes = [
        kzg_commitment_to_versioned_hash(c) for c in payload_kzg_commitments
    ]
    assert expected_blob_versioned_hashes[:n] == payload_versioned_hashes

    # 3. Verify blob–commitment consistency using batch KZG proof verification
    assert verify_blob_kzg_proof_batch(
        blobs=payload_blobs,
        commitments=payload_kzg_commitments,
        proofs=payload_kzg_proofs
    )

    # 4. Proceed with standard EL payload validation / execution
    return execute_payload(payload)

Consensus Layer

Validation

The consensus layer does not introduce new blob specific validation rules for payload-blobs beyond what we have for EIP-4844/EIP-7594.

The Consensus Layer relies on payload_blob_count in the execution payload header to interpret the ordering of blob commitments, but otherwise treats payload blobs identically to other blobs for availability and networking.

Networking

BiB reuses the existing blob networking mechanism.

We note the following for consideration:

  • Once proofs are made mandatory, a mechanism will be needed for execution payload retrieval. EIP-7732 introduces an execution_payload gossip topic that we can use for this purpose. However, in the context of mandatory proofs where a super majority of stake operates zk validators (which only listen to header topics), a malicious builder could publish only the payload in blobs and gossip the execution payload header without gossiping on the execution_payload topic. This would allow zk validators to attest, but other nodes depending on the full payload from the gossip topic would be unable to progress.

  • To mitigate this, nodes can implement a fallback mechanism: if they don’t receive the payload on the execution_payload topic, they reconstruct it from the first payload_blob_count blobs and then seed the execution_payload topic themselves. This creates a resilient system where every node acts as a “bridge node” when needed, similar to how rollups use L2 gossip as a fast path but fall back to L1 data availability.

  • Unlike most type-3 blob transactions, payload-blobs will not have been propagated to the network before a block is built. Depending on the deadlines imposed by ePBS, this may imply higher bandwidth requirements from block builders.

Fee Accounting

BiB introduces protocol mandated blob usage, rather than user initiated via type-3 transactions. Fee accounting for payload-blobs differ in nature from transaction blob fees as a result.

Who pays?

This EIP does not mandate that payload-blobs pay a per-blob fee like transaction blobs.

Instead payload-blobs are treated as a protocol-accepted cost when constructing the block. In particular:

  • Payload-blobs do not correspond to a user transaction and therefore do not naturally map to a user-paid blob fee.
  • The cost of including payload-blobs, in terms of blob gas usage, is accepted by the protocol as a necessary cost for maintaining data availability.

Do payload-blobs compete with transaction blobs for capacity?

Because payload blobs consume blob gas, they directly influence blob congestion and the blob base fee.

Can a builder artificially inflate blob gas usage?

A potential concern is whether a malicious builder could create artificially large execution payloads to inflate blob gas usage.

This attack is economically constrained: to increase the size of the execution payload, a builder must include additional transactions with calldata. Since calldata costs execution gas, the builder would need to pay for this additional data through the normal gas mechanism. The cost of including extra calldata makes it economically unfavorable to artificially inflate payload size solely to manipulate blob fees.

Open questions and future considerations

  • Pre-zk the cost to validate payload blobs is also felt by validators too. So these blobs are in some sense heavier than normal blobs. Should this be priced into blob_gas_used?
  • Networking related: Payload-blobs require higher bandwidth due to the fact that they will not have been in the public mempool
  • Explicit protocol level pricing for payload blobs

Rationale

What is included in execution-payload data?

Execution-payload data includes bals (Block Access Lists from EIP-7928) and transactions.

Why transactions? Transaction data is the only component of the execution payload that cannot be derived from other components and is not provided by the consensus layer.

Why BALs? While BALs are technically the output of executing transactions and could be recomputed, once zkEVM proofs become mandatory for consensus, validators no longer execute payloads. A malicious builder could publish a valid proof and withhold both the execution payload data and the BALs. This would prevent other builders from constructing subsequent blocks and prevent RPC providers from serving the full state. Including BALs in payload-blobs ensures they remain available.

Why not withdrawals? Withdrawals can be derived on the consensus layer.

Why not execution requests? Execution requests can be recomputed from transactions and do not suffer from the same withholding attack as BALs because they are required by the consensus layer for validation.

Why not the header? The header cannot be put into blobs because it contains payload_blob_count, which depends on the number of blobs; causing a circular dependency.

Encoding optimization: The encoding includes an 8-byte header: [4 bytes BAL length] [4 bytes transaction length]. This allows extracting just the tx data after fetching the first blob.

TODO: We could also put the number of blobs that the BAL occupies in the execution payload header.

Builder discretion vs reserving k blobs

This EIP specifies that the block builder choose payload_blob_count, subject to the constraint imposed by MAX_BLOBS_PER_BLOCK.

An alternative would have been to always reserve k blobs, where k corresponds to the worst case execution payload size. While this provides better predictability, it reduces flexibility under blob congestion.

Why not encode execution-payload data inside the core EL execution logic?

Doing it in the EL STF would require payload-blob commitments or versioned hashes to be made visible inside the core execution logic, rather than being handled at the Engine API boundary.

When zkEVM proofs become mandatory, why can’t zk-attestors download the full execution payload?

The execution payload grows linearly with the gas limit. Requiring attesters to download the payload for DA would create an increasing bandwidth burden as the gas limit grows.

Compression algorithm for encoding execution-payload data

Compression can be used on the serialized execution-payload data. This (in theory) should allow the usage of less payload-blobs, depending on the compression ratio. The tradeoffs being:

  • That we will use more CPU/proving cycles for decompression
  • A breaking change since we want to decompress on the hot-path. What this means is that the transactions would need to be compressed in the payload, and then decompressed when we attempt to validate it.

Whether we should use a compression algorithm and which one requires more investigation, in particular we need to investigate:

  • The average compression ratios achieved
  • The proving cycle overhead
  • The invasiveness of now requiring consensus aware objects to be compressed when passed for validation.

For now we recommend using no compression algorithm and operating on uncompressed data.

Serialization algorithm for encoding execution-payload data

Serialization of the execution-payload data uses RLP. Since transactions in the ExecutionPayload are already RLP-encoded, we simply RLP-encode the list of transaction bytes without any additional transformation.

While a more zk-friendly serialization algorithm could be beneficial in the future, this EIP uses RLP for simplicity. Once EIP-7807 (SSZ execution blocks) is implemented, the encoding can be updated to SSZ-serialize the list of SSZ-encoded transaction bytes.

Backwards Compatibility

This requires changes to the execution payload header and the EL STF; so requires a fork. Nodes that do not implement BiB will not be able to validate blocks after activation.

Test Cases

TODO

Reference Implementation

TODO

Security Considerations

Interaction with blob congestion and denial-of-service

Payload-blobs consume blob gas and therefore are subject to the same congestion control mechanisms and blob limits as transaction blobs.

As a byproduct, this ensures that a malicious block producer cannot make arbitrarily large execution payloads without accounting for blob gas limits. While a block producer could theoretically drive up the blob base fee by creating large payloads, this attack is economically constrained by calldata costs (see Fee Accounting for details).

Data withholding

An attacker cannot withhold execution payload data without also withholding blob data, which would violate existing DAS guarantees and cause the block to be rejected by the consensus layer.

Copyright and related rights waived via CC0.

Citation

Please cite this document as:

Kevaundray Wedderburn (@kevaundray), Ignacio Hagopian (@jsign), Jihoon Song <jihoon.song@ethereum.org>, Francesco Risitano (@frisitano), Thomas Thiery (@soispoke) <thomas.thiery@ethereum.org>, "EIP-8142: Block-in-Blobs (BiB) [DRAFT]," Ethereum Improvement Proposals, no. 8142, January 2026. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-8142.