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:
Downloading the execution payload
Executing the payload locally
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:
Download a proof attesting to the correctness of the execution payload
Download the execution payload header
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)
Summary: The execution layer is modified in the following ways:
The ExecutionPayloadHeader now has a payload_blob_count field to track how many blobs are used for the execution payload data, enabling verification of the correct number of payload-blobs at the Engine API boundary.
engine_getPayload computes the payload-blobs when building a block, sets payload_blob_count in the ExecutionPayloadHeader, 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 ExecutionPayloadHeader 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.
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 blobsdef 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:
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
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 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 ExecutionPayloadHeader 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
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.