Skip to content

SSZ hash_tree_root

Consensus-layer merkleization (SHA-256). The standard specification follows the recursive SSZ definition directly: a subtree is either its leaf, the precomputed zero root at that depth, or the hash of its two children. Source references are indexed only when recursion reaches a live leaf, so no leaf list or explicit Merkle frontier is materialized.

type htr_depth

The largest Merkle depth required by the supported execution-layer SSZ schemas. ByteList[2^30] is the widest one, with 2^25 chunks.

type htr_depth = range(0, 25)

type htr_leaf_count

A leaf count in any supported execution-layer SSZ Merkle tree.

type htr_leaf_count = range(0, 33554432)

Constants

The limits and byte offsets below are the SSZ dimensions of execution-layer requests and their constituent fields.

let HTR_BYTE_LIST_LIMIT

let HTR_BYTE_LIST_LIMIT : int(1073741824) = 1073741824

let HTR_DEPOSIT_LENGTH

let HTR_DEPOSIT_LENGTH : int(192) = 192

let HTR_DEPOSIT_PUBKEY

let HTR_DEPOSIT_PUBKEY : int(0) = 0

let HTR_DEPOSIT_PUBKEY_LENGTH

let HTR_DEPOSIT_PUBKEY_LENGTH : int(48) = 48

let HTR_DEPOSIT_WITHDRAWAL_CREDENTIALS

let HTR_DEPOSIT_WITHDRAWAL_CREDENTIALS : int(48) = 48

let HTR_DEPOSIT_AMOUNT

let HTR_DEPOSIT_AMOUNT : int(80) = 80

let HTR_DEPOSIT_SIGNATURE

let HTR_DEPOSIT_SIGNATURE : int(88) = 88

let HTR_DEPOSIT_SIGNATURE_LENGTH

let HTR_DEPOSIT_SIGNATURE_LENGTH : int(96) = 96

let HTR_DEPOSIT_INDEX

let HTR_DEPOSIT_INDEX : int(184) = 184

let HTR_WITHDRAWAL_REQUEST_LENGTH

let HTR_WITHDRAWAL_REQUEST_LENGTH : int(76) = 76

let HTR_REQUEST_SOURCE_ADDRESS

let HTR_REQUEST_SOURCE_ADDRESS : int(0) = 0

let HTR_REQUEST_SOURCE_PUBKEY

let HTR_REQUEST_SOURCE_PUBKEY : int(20) = 20

let HTR_REQUEST_PUBKEY_LENGTH

let HTR_REQUEST_PUBKEY_LENGTH : int(48) = 48

let HTR_WITHDRAWAL_REQUEST_AMOUNT

let HTR_WITHDRAWAL_REQUEST_AMOUNT : int(68) = 68

let HTR_CONSOLIDATION_REQUEST_LENGTH

let HTR_CONSOLIDATION_REQUEST_LENGTH : int(116) = 116

let HTR_CONSOLIDATION_REQUEST_TARGET_PUBKEY

let HTR_CONSOLIDATION_REQUEST_TARGET_PUBKEY : int(68) = 68

let HTR_BUILDER_DEPOSIT_LENGTH

let HTR_BUILDER_DEPOSIT_LENGTH : int(184) = 184

let HTR_BUILDER_DEPOSIT_PUBKEY

let HTR_BUILDER_DEPOSIT_PUBKEY : int(0) = 0

let HTR_BUILDER_DEPOSIT_WITHDRAWAL_CREDENTIALS

let HTR_BUILDER_DEPOSIT_WITHDRAWAL_CREDENTIALS : int(48) = 48

let HTR_BUILDER_DEPOSIT_AMOUNT

let HTR_BUILDER_DEPOSIT_AMOUNT : int(80) = 80

let HTR_BUILDER_DEPOSIT_SIGNATURE

let HTR_BUILDER_DEPOSIT_SIGNATURE : int(88) = 88

let HTR_BUILDER_EXIT_LENGTH

let HTR_BUILDER_EXIT_LENGTH : int(68) = 68

let HTR_BUILDER_EXIT_SOURCE_ADDRESS

let HTR_BUILDER_EXIT_SOURCE_ADDRESS : int(0) = 0

let HTR_BUILDER_EXIT_PUBKEY

let HTR_BUILDER_EXIT_PUBKEY : int(20) = 20

let SSZ_ZERO_HASH_01

let SSZ_ZERO_HASH_01 : hash = hash_from_bits(0xf5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4b)

let SSZ_ZERO_HASH_02

let SSZ_ZERO_HASH_02 : hash = hash_from_bits(0xdb56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71)

let SSZ_ZERO_HASH_03

let SSZ_ZERO_HASH_03 : hash = hash_from_bits(0xc78009fdf07fc56a11f122370658a353aaa542ed63e44c4bc15ff4cd105ab33c)

let SSZ_ZERO_HASH_04

let SSZ_ZERO_HASH_04 : hash = hash_from_bits(0x536d98837f2dd165a55d5eeae91485954472d56f246df256bf3cae19352a123c)

let SSZ_ZERO_HASH_05

let SSZ_ZERO_HASH_05 : hash = hash_from_bits(0x9efde052aa15429fae05bad4d0b1d7c64da64d03d7a1854a588c2cb8430c0d30)

let SSZ_ZERO_HASH_06

let SSZ_ZERO_HASH_06 : hash = hash_from_bits(0xd88ddfeed400a8755596b21942c1497e114c302e6118290f91e6772976041fa1)

let SSZ_ZERO_HASH_07

let SSZ_ZERO_HASH_07 : hash = hash_from_bits(0x87eb0ddba57e35f6d286673802a4af5975e22506c7cf4c64bb6be5ee11527f2c)

let SSZ_ZERO_HASH_08

let SSZ_ZERO_HASH_08 : hash = hash_from_bits(0x26846476fd5fc54a5d43385167c95144f2643f533cc85bb9d16b782f8d7db193)

let SSZ_ZERO_HASH_09

let SSZ_ZERO_HASH_09 : hash = hash_from_bits(0x506d86582d252405b840018792cad2bf1259f1ef5aa5f887e13cb2f0094f51e1)

let SSZ_ZERO_HASH_10

let SSZ_ZERO_HASH_10 : hash = hash_from_bits(0xffff0ad7e659772f9534c195c815efc4014ef1e1daed4404c06385d11192e92b)

let SSZ_ZERO_HASH_11

let SSZ_ZERO_HASH_11 : hash = hash_from_bits(0x6cf04127db05441cd833107a52be852868890e4317e6a02ab47683aa75964220)

let SSZ_ZERO_HASH_12

let SSZ_ZERO_HASH_12 : hash = hash_from_bits(0xb7d05f875f140027ef5118a2247bbb84ce8f2f0f1123623085daf7960c329f5f)

let SSZ_ZERO_HASH_13

let SSZ_ZERO_HASH_13 : hash = hash_from_bits(0xdf6af5f5bbdb6be9ef8aa618e4bf8073960867171e29676f8b284dea6a08a85e)

let SSZ_ZERO_HASH_14

let SSZ_ZERO_HASH_14 : hash = hash_from_bits(0xb58d900f5e182e3c50ef74969ea16c7726c549757cc23523c369587da7293784)

let SSZ_ZERO_HASH_15

let SSZ_ZERO_HASH_15 : hash = hash_from_bits(0xd49a7502ffcfb0340b1d7885688500ca308161a7f96b62df9d083b71fcc8f2bb)

let SSZ_ZERO_HASH_16

let SSZ_ZERO_HASH_16 : hash = hash_from_bits(0x8fe6b1689256c0d385f42f5bbe2027a22c1996e110ba97c171d3e5948de92beb)

let SSZ_ZERO_HASH_17

let SSZ_ZERO_HASH_17 : hash = hash_from_bits(0x8d0d63c39ebade8509e0ae3c9c3876fb5fa112be18f905ecacfecb92057603ab)

let SSZ_ZERO_HASH_18

let SSZ_ZERO_HASH_18 : hash = hash_from_bits(0x95eec8b2e541cad4e91de38385f2e046619f54496c2382cb6cacd5b98c26f5a4)

let SSZ_ZERO_HASH_19

let SSZ_ZERO_HASH_19 : hash = hash_from_bits(0xf893e908917775b62bff23294dbbe3a1cd8e6cc1c35b4801887b646a6f81f17f)

let SSZ_ZERO_HASH_20

let SSZ_ZERO_HASH_20 : hash = hash_from_bits(0xcddba7b592e3133393c16194fac7431abf2f5485ed711db282183c819e08ebaa)

let SSZ_ZERO_HASH_21

let SSZ_ZERO_HASH_21 : hash = hash_from_bits(0x8a8d7fe3af8caa085a7639a832001457dfb9128a8061142ad0335629ff23ff9c)

let SSZ_ZERO_HASH_22

let SSZ_ZERO_HASH_22 : hash = hash_from_bits(0xfeb3c337d7a51a6fbf00b9e34c52e1c9195c969bd4e7a0bfd51d5c5bed9c1167)

let SSZ_ZERO_HASH_23

let SSZ_ZERO_HASH_23 : hash = hash_from_bits(0xe71f0aa83cc32edfbefa9f4d3e0174ca85182eec9f3a09f6a6c0df6377a510d7)

let SSZ_ZERO_HASH_24

let SSZ_ZERO_HASH_24 : hash = hash_from_bits(0x31206fa80a50bb6abe29085058f16212212a60eec8f049fecb92d8c8e0a84bc0)

let SSZ_ZERO_HASH_25

let SSZ_ZERO_HASH_25 : hash = hash_from_bits(0x21352bfecbeddde993839f614c3dac0a3ee37543f9b412b16199dc158e23b544)

function ssz_zero_hash

The root of an all-zero subtree at level.

function htr_leaf_capacity

Number of leaves in a complete supported SSZ tree at depth.

function htr_leaf_capacity(depth : htr_depth) -> htr_leaf_count = match depth {
    0 => 1,
    1 => 2,
    2 => 4,
    3 => 8,
    4 => 16,
    5 => 32,
    6 => 64,
    7 => 128,
    8 => 256,
    9 => 512,
    10 => 1024,
    11 => 2048,
    12 => 4096,
    13 => 8192,
    14 => 16384,
    15 => 32768,
    16 => 65536,
    17 => 131072,
    18 => 262144,
    19 => 524288,
    20 => 1048576,
    21 => 2097152,
    22 => 4194304,
    23 => 8388608,
    24 => 16777216,
    25 => 33554432,
}

function merkleize_vector_subtree

function merkleize_vector_subtree(leaves, start, count, depth) =
    if count == 0 then {
        ssz_zero_hash(depth)
    } else if depth == 0 then {
        let leaves_length = length(leaves);
        if (count == 1) & (start < leaves_length) then {
            leaves[leaves_length - 1 - start]
        } else {
            fatal_error(WitnessDeficient)
        }
    } else {
        let child_depth : htr_depth = depth - 1;
        let half = htr_leaf_capacity(child_depth);
        let left_count : htr_leaf_count =
            if count < half then count else half;
        let right_count : htr_leaf_count =
            if count < half then 0 else count - half;
        let left_root = merkleize_vector_subtree(leaves, start, left_count, child_depth);
        let right_root = merkleize_vector_subtree(leaves, start + half, right_count, child_depth);
        sha256_pair(left_root, right_root)
    }

function merkleize

function merkleize(leaves, depth) = {
    let count : htr_leaf_count = length(leaves);
    let capacity = htr_leaf_capacity(depth);
    if count <= capacity then {
        merkleize_vector_subtree(leaves, 0, count, depth)
    } else {
        fatal_error(WitnessDeficient)
    }
}

function htr_uint

Encodes a protocol quantity as an SSZ little-endian basic-value chunk.

function htr_uint(v : ssz_uint) -> hash = {
    var bytes = ZERO_HASH;
    bytes[0] = get_slice_int(8, v, 0);
    bytes[1] = get_slice_int(8, v, 8);
    bytes[2] = get_slice_int(8, v, 16);
    bytes[3] = get_slice_int(8, v, 24);
    bytes[4] = get_slice_int(8, v, 32);
    bytes[5] = get_slice_int(8, v, 40);
    bytes[6] = get_slice_int(8, v, 48);
    bytes[7] = get_slice_int(8, v, 56);
    B256(bytes)
}

function htr_u256

uint256 as a 32-byte little-endian chunk.

function htr_u256(value : word) -> hash = {
    var bytes = ZERO_HASH;
    bytes[0] = get_slice_int(8, value, 0);
    bytes[1] = get_slice_int(8, value, 8);
    bytes[2] = get_slice_int(8, value, 16);
    bytes[3] = get_slice_int(8, value, 24);
    bytes[4] = get_slice_int(8, value, 32);
    bytes[5] = get_slice_int(8, value, 40);
    bytes[6] = get_slice_int(8, value, 48);
    bytes[7] = get_slice_int(8, value, 56);
    bytes[8] = get_slice_int(8, value, 64);
    bytes[9] = get_slice_int(8, value, 72);
    bytes[10] = get_slice_int(8, value, 80);
    bytes[11] = get_slice_int(8, value, 88);
    bytes[12] = get_slice_int(8, value, 96);
    bytes[13] = get_slice_int(8, value, 104);
    bytes[14] = get_slice_int(8, value, 112);
    bytes[15] = get_slice_int(8, value, 120);
    bytes[16] = get_slice_int(8, value, 128);
    bytes[17] = get_slice_int(8, value, 136);
    bytes[18] = get_slice_int(8, value, 144);
    bytes[19] = get_slice_int(8, value, 152);
    bytes[20] = get_slice_int(8, value, 160);
    bytes[21] = get_slice_int(8, value, 168);
    bytes[22] = get_slice_int(8, value, 176);
    bytes[23] = get_slice_int(8, value, 184);
    bytes[24] = get_slice_int(8, value, 192);
    bytes[25] = get_slice_int(8, value, 200);
    bytes[26] = get_slice_int(8, value, 208);
    bytes[27] = get_slice_int(8, value, 216);
    bytes[28] = get_slice_int(8, value, 224);
    bytes[29] = get_slice_int(8, value, 232);
    bytes[30] = get_slice_int(8, value, 240);
    bytes[31] = get_slice_int(8, value, 248);
    B256(bytes)
}

function htr_bytes32

Bytes32 chunks are themselves.

function htr_bytes32(b : hash) -> hash = b

function htr_addr

A 20-byte address, left-aligned in its chunk.

function htr_addr(address_bytes : address) -> hash = {
    var bytes = ZERO_HASH;
    bytes[0] = address_bytes[0];
    bytes[1] = address_bytes[1];
    bytes[2] = address_bytes[2];
    bytes[3] = address_bytes[3];
    bytes[4] = address_bytes[4];
    bytes[5] = address_bytes[5];
    bytes[6] = address_bytes[6];
    bytes[7] = address_bytes[7];
    bytes[8] = address_bytes[8];
    bytes[9] = address_bytes[9];
    bytes[10] = address_bytes[10];
    bytes[11] = address_bytes[11];
    bytes[12] = address_bytes[12];
    bytes[13] = address_bytes[13];
    bytes[14] = address_bytes[14];
    bytes[15] = address_bytes[15];
    bytes[16] = address_bytes[16];
    bytes[17] = address_bytes[17];
    bytes[18] = address_bytes[18];
    bytes[19] = address_bytes[19];
    B256(bytes)
}

function htr_length_chunk

function htr_length_chunk(len) =
    let length_word = word_of_byte_count(len) in
    htr_u256(length_word)

function mix_in_length

function mix_in_length(root, len) =
    let length_chunk = htr_length_chunk(len) in
    sha256_pair(root, length_chunk)

function clog2

The ceiling of log2 — the Merkle depth of an n-chunk capacity.

function clog2(n : htr_leaf_count) -> htr_depth = {
    var remaining : htr_leaf_count =
        if n == 0 then 0 else n - 1;
    var depth : htr_depth = 0;
    foreach (_step from 0 to 24) {
        if remaining != 0 then {
            let current_depth = depth;
            depth =
                if current_depth < 25 then {
                    current_depth + 1
                } else {
                    fatal_error(WitnessDeficient)
                };
            remaining = tdiv_nat(remaining, 2)
        }
    };
    depth
}

function htr_chunk

function htr_chunk(bytes, chunk_index) =
    if chunk_index <= 33554432 then {
        let offset : source_pointer = chunk_index * WORD_BYTE_LENGTH;
        let chunk_word = slice_load(bytes, offset);
        word_to_hash(chunk_word)
    } else {
        fatal_error(WitnessDeficient)
    }

function htr_chunk_count

function htr_chunk_count(byte_len) =
    if byte_len <= HTR_BYTE_LIST_LIMIT then {
        (byte_len + 31) / 32
    } else {
        fatal_error(WitnessDeficient)
    }

function htr_bytes_subtree

function htr_bytes_subtree(bytes, start, count, depth) =
    if count == 0 then {
        ssz_zero_hash(depth)
    } else if depth == 0 then {
        if count == 1 then {
            htr_chunk(bytes, start)
        } else {
            fatal_error(WitnessDeficient)
        }
    } else {
        let child_depth : htr_depth = depth - 1;
        let half = htr_leaf_capacity(child_depth);
        let left_count : htr_leaf_count =
            if count < half then count else half;
        let right_count : htr_leaf_count =
            if count < half then 0 else count - half;
        let left_root = htr_bytes_subtree(bytes, start, left_count, child_depth);
        let right_root = htr_bytes_subtree(bytes, start + half, right_count, child_depth);
        sha256_pair(left_root, right_root)
    }

function htr_bytes_root

The Merkle root of a byte span's chunks at depth.

function htr_bytes_root(bytes : StatelessInputSlice, depth : htr_depth) -> hash = {
    let count = htr_chunk_count(bytes.len);
    let capacity = htr_leaf_capacity(depth);
    if count <= capacity then {
        htr_bytes_subtree(bytes, 0, count, depth)
    } else {
        fatal_error(WitnessDeficient)
    }
}

Helpers

function htr_bytevector

hash_tree_root of a fixed-size ByteVector.

function htr_bytevector(bytes : StatelessInputSlice) -> hash = {
    let chunks = htr_chunk_count(bytes.len);
    let depth = clog2(chunks);
    htr_bytes_root(bytes, depth)
}

function htr_ssz_uint

function htr_ssz_uint(bytes, offset) =
    let value = decode_ssz_uint(bytes, offset) in
    htr_uint(value)

function htr_ssz_u256

function htr_ssz_u256(bytes, offset) =
    let value = ssz_u256(bytes, offset) in
    htr_u256(value)

function htr_ssz_bytes32

function htr_ssz_bytes32(bytes, offset) =
    let value = ssz_bytes32(bytes, offset) in
    htr_bytes32(value)

function htr_ssz_addr

function htr_ssz_addr(bytes, offset) =
    let value = ssz_addr(bytes, offset) in
    htr_addr(value)

function htr_ssz_bytevector

function htr_ssz_bytevector(bytes, offset, len) = {
    if offset + len <= bytes.len then {
        let field = sub_slice(bytes, offset, len);
        htr_bytevector(field)
    } else {
        fatal_error(InvalidConfig)
    }
}

function htr_bytelist

function htr_bytelist(bytes, limit_bytes) = {
    let capacity = htr_chunk_count(limit_bytes);
    let depth = clog2(capacity);
    let root = htr_bytes_root(bytes, depth);
    mix_in_length(root, bytes.len)
}

The execution payload

function htr_withdrawal

hash_tree_root of one SSZ withdrawal (4 fields, depth 2).

function htr_withdrawal(withdrawal : StatelessInputSliceLength(44)) -> hash = {
    let index_root = htr_ssz_uint(withdrawal, WD_INDEX);
    let validator_root = htr_ssz_uint(withdrawal, WD_VALIDATOR_INDEX);
    let address_root = htr_ssz_addr(withdrawal, WD_ADDRESS);
    let amount_root = htr_ssz_uint(withdrawal, WD_AMOUNT);
    merkleize([index_root, validator_root, address_root, amount_root], 2)
}

function htr_transactions_subtree

function htr_transactions_subtree(transactions, start, count, depth) =
    if count == 0 then {
        ssz_zero_hash(depth)
    } else if depth == 0 then {
        if count == 1 then {
            let transaction = ssz_list_at(transactions, start);
            htr_bytelist(transaction, HTR_BYTE_LIST_LIMIT)
        } else {
            fatal_error(WitnessDeficient)
        }
    } else {
        let child_depth : htr_depth = depth - 1;
        let half = htr_leaf_capacity(child_depth);
        let left_count : htr_leaf_count =
            if count < half then count else half;
        let right_count : htr_leaf_count =
            if count < half then 0 else count - half;
        let left_root = htr_transactions_subtree(transactions, start, left_count, child_depth);
        let right_root = htr_transactions_subtree(transactions, start + half, right_count, child_depth);
        sha256_pair(left_root, right_root)
    }

function htr_transactions

hash_tree_root of the transactions list (List[ByteList[2^30], 2^20]).

function htr_transactions(transactions : TransactionListRef) -> hash = {
    let count : htr_leaf_count = transactions.count;
    let root = htr_transactions_subtree(transactions, 0, count, 20);
    mix_in_length(root, transactions.count)
}

function htr_withdrawals_subtree

function htr_withdrawals_subtree(withdrawals, start, count, depth) =
    if count == 0 then {
        ssz_zero_hash(depth)
    } else if depth == 0 then {
        if count == 1 then {
            let withdrawal = ssz_fixed_list_at(withdrawals, start, WD_SIZE);
            htr_withdrawal(withdrawal)
        } else {
            fatal_error(WitnessDeficient)
        }
    } else {
        let child_depth : htr_depth = depth - 1;
        let half = htr_leaf_capacity(child_depth);
        let left_count : htr_leaf_count =
            if count < half then count else half;
        let right_count : htr_leaf_count =
            if count < half then 0 else count - half;
        let left_root = htr_withdrawals_subtree(withdrawals, start, left_count, child_depth);
        let right_root = htr_withdrawals_subtree(withdrawals, start + half, right_count, child_depth);
        sha256_pair(left_root, right_root)
    }

function htr_withdrawals

hash_tree_root of the withdrawals list (List[SszWithdrawal, 16]).

function htr_withdrawals(withdrawals : WithdrawalListRef) -> hash = {
    let count : htr_leaf_count = withdrawals.count;
    let root = htr_withdrawals_subtree(withdrawals, 0, count, 4);
    mix_in_length(root, withdrawals.count)
}

function htr_execution_payload

hash_tree_root of the SszExecutionPayload (19 fields, depth 5).

function htr_execution_payload(input_ref : StatelessInputRef) -> hash = {
    let payload = input_ref.execution_payload;
    let parent_hash_root = htr_ssz_bytes32(payload, 0);
    let fee_recipient_root = htr_ssz_addr(payload, PL_FEE_RECIPIENT);
    let state_root = htr_ssz_bytes32(payload, PL_STATE_ROOT);
    let receipts_root = htr_ssz_bytes32(payload, PL_RECEIPTS_ROOT);
    let logs_bloom_root = htr_ssz_bytevector(payload, PL_LOGS_BLOOM, LOGS_BLOOM_BYTE_LENGTH);
    let prev_randao_root = htr_ssz_bytes32(payload, PL_PREV_RANDAO);
    let block_number_root = htr_ssz_uint(payload, PL_BLOCK_NUMBER);
    let gas_limit_root = htr_ssz_uint(payload, PL_GAS_LIMIT);
    let gas_used_root = htr_ssz_uint(payload, PL_GAS_USED);
    let timestamp_root = htr_ssz_uint(payload, PL_TIMESTAMP);
    let extra_data_root = htr_bytelist(input_ref.extra_data, WORD_BYTE_LENGTH);
    let base_fee_root = htr_ssz_u256(payload, PL_BASE_FEE);
    let block_hash_root = htr_ssz_bytes32(payload, PL_BLOCK_HASH);
    let transactions_root = htr_transactions(input_ref.transactions);
    let withdrawals_root = htr_withdrawals(input_ref.withdrawals);
    let blob_gas_used_root = htr_ssz_uint(payload, PL_BLOB_GAS_USED);
    let excess_blob_gas_root = htr_ssz_uint(payload, PL_EXCESS_BLOB_GAS);
    let block_access_list_root = htr_bytelist(input_ref.block_access_list, HTR_BYTE_LIST_LIMIT);
    let slot_number_root = htr_ssz_uint(payload, PL_SLOT_NUMBER);
    merkleize(
        [
            parent_hash_root,
            fee_recipient_root,
            state_root,
            receipts_root,
            logs_bloom_root,
            prev_randao_root,
            block_number_root,
            gas_limit_root,
            gas_used_root,
            timestamp_root,
            extra_data_root,
            base_fee_root,
            block_hash_root,
            transactions_root,
            withdrawals_root,
            blob_gas_used_root,
            excess_blob_gas_root,
            block_access_list_root,
            slot_number_root,
        ],
        5,
    )
}

function htr_versioned_hashes_subtree

function htr_versioned_hashes_subtree(versioned_hashes, start, count, depth) =
    if count == 0 then {
        ssz_zero_hash(depth)
    } else if depth == 0 then {
        if count == 1 then {
            htr_chunk(versioned_hashes, start)
        } else {
            fatal_error(WitnessDeficient)
        }
    } else {
        let child_depth : htr_depth = depth - 1;
        let half = htr_leaf_capacity(child_depth);
        let left_count : htr_leaf_count =
            if count < half then count else half;
        let right_count : htr_leaf_count =
            if count < half then 0 else count - half;
        let left_root = htr_versioned_hashes_subtree(versioned_hashes, start, left_count, child_depth);
        let right_root = htr_versioned_hashes_subtree(versioned_hashes, start + half, right_count, child_depth);
        sha256_pair(left_root, right_root)
    }

function htr_versioned_hashes

hash_tree_root of the versioned hashes (List[Bytes32, 4096]).

function htr_versioned_hashes(versioned_hashes : StatelessInputSlice) -> hash = {
    let items = ssz_bounded_fixed_list_ref(versioned_hashes, WORD_BYTE_LENGTH, sizeof(blob_commitment_count_bound));
    let count : htr_leaf_count = items.count;
    let root = htr_versioned_hashes_subtree(versioned_hashes, 0, count, 12);
    mix_in_length(root, items.count)
}

The execution requests

The EIP-7685 execution-request commitments.

function htr_deposit

hash_tree_root of one deposit request (5 fields, depth 3).

function htr_deposit(deposit : StatelessInputSliceLength(192)) -> hash = {
    let pubkey = htr_ssz_bytevector(deposit, HTR_DEPOSIT_PUBKEY, HTR_DEPOSIT_PUBKEY_LENGTH);
    let withdrawal_credentials = htr_ssz_bytes32(deposit, HTR_DEPOSIT_WITHDRAWAL_CREDENTIALS);
    let amount = htr_ssz_uint(deposit, HTR_DEPOSIT_AMOUNT);
    let signature = htr_ssz_bytevector(deposit, HTR_DEPOSIT_SIGNATURE, HTR_DEPOSIT_SIGNATURE_LENGTH);
    let index = htr_ssz_uint(deposit, HTR_DEPOSIT_INDEX);
    merkleize([pubkey, withdrawal_credentials, amount, signature, index], 3)
}

function htr_withdrawal_request

hash_tree_root of one withdrawal request (3 fields, depth 2).

function htr_withdrawal_request(request : StatelessInputSliceLength(76)) -> hash = {
    let source_address = htr_ssz_addr(request, HTR_REQUEST_SOURCE_ADDRESS);
    let source_pubkey = htr_ssz_bytevector(request, HTR_REQUEST_SOURCE_PUBKEY, HTR_REQUEST_PUBKEY_LENGTH);
    let amount = htr_ssz_uint(request, HTR_WITHDRAWAL_REQUEST_AMOUNT);
    merkleize([source_address, source_pubkey, amount], 2)
}

function htr_consolidation_request

hash_tree_root of one consolidation request (3 fields, depth 2).

function htr_consolidation_request(request : StatelessInputSliceLength(116)) -> hash = {
    let source_address = htr_ssz_addr(request, HTR_REQUEST_SOURCE_ADDRESS);
    let source_pubkey = htr_ssz_bytevector(request, HTR_REQUEST_SOURCE_PUBKEY, HTR_REQUEST_PUBKEY_LENGTH);
    let target_pubkey = htr_ssz_bytevector(request, HTR_CONSOLIDATION_REQUEST_TARGET_PUBKEY, HTR_REQUEST_PUBKEY_LENGTH);
    merkleize([source_address, source_pubkey, target_pubkey], 2)
}

function htr_builder_deposit_request

hash_tree_root of one builder deposit request (4 fields, depth 2).

function htr_builder_deposit_request(request : StatelessInputSliceLength(184)) -> hash = {
    let pubkey = htr_ssz_bytevector(request, HTR_BUILDER_DEPOSIT_PUBKEY, HTR_REQUEST_PUBKEY_LENGTH);
    let withdrawal_credentials = htr_ssz_bytes32(request, HTR_BUILDER_DEPOSIT_WITHDRAWAL_CREDENTIALS);
    let amount = htr_ssz_uint(request, HTR_BUILDER_DEPOSIT_AMOUNT);
    let signature = htr_ssz_bytevector(request, HTR_BUILDER_DEPOSIT_SIGNATURE, HTR_DEPOSIT_SIGNATURE_LENGTH);
    merkleize([pubkey, withdrawal_credentials, amount, signature], 2)
}

function htr_builder_exit_request

hash_tree_root of one builder exit request (2 fields, depth 1).

function htr_builder_exit_request(request : StatelessInputSliceLength(68)) -> hash = {
    let source_address = htr_ssz_addr(request, HTR_BUILDER_EXIT_SOURCE_ADDRESS);
    let pubkey = htr_ssz_bytevector(request, HTR_BUILDER_EXIT_PUBKEY, HTR_REQUEST_PUBKEY_LENGTH);
    merkleize([source_address, pubkey], 1)
}

type HtrRequestKind

The closed fixed-width leaf-operation algebra carried through the generic request-list recursion. htr_request_leaf interprets it at depth zero, replacing a higher-order leaf function without duplicating merkleization.

enum HtrRequestKind = {
    /* a validator deposit request (EIP-6110) */
    HtrDeposit,
    /* a validator withdrawal request (EIP-7002) */
    HtrWithdrawalRequest,
    /* a validator consolidation request (EIP-7251) */
    HtrConsolidationRequest,
    /* a builder deposit request */
    HtrBuilderDepositRequest,
    /* a builder exit request */
    HtrBuilderExitRequest,
}

function htr_request_leaf

function htr_request_leaf(requests, index, kind) =
    match kind {
        HtrDeposit => {
            let offset = index * HTR_DEPOSIT_LENGTH;
            if offset + HTR_DEPOSIT_LENGTH <= requests.len then {
                let deposit = sub_slice(requests, offset, HTR_DEPOSIT_LENGTH);
                htr_deposit(deposit)
            } else {
                fatal_error(InvalidConfig)
            }
        },
        HtrWithdrawalRequest => {
            let offset = index * HTR_WITHDRAWAL_REQUEST_LENGTH;
            if offset + HTR_WITHDRAWAL_REQUEST_LENGTH <= requests.len then {
                let request = sub_slice(requests, offset, HTR_WITHDRAWAL_REQUEST_LENGTH);
                htr_withdrawal_request(request)
            } else {
                fatal_error(InvalidConfig)
            }
        },
        HtrConsolidationRequest => {
            let offset = index * HTR_CONSOLIDATION_REQUEST_LENGTH;
            if offset + HTR_CONSOLIDATION_REQUEST_LENGTH <= requests.len then {
                let request = sub_slice(requests, offset, HTR_CONSOLIDATION_REQUEST_LENGTH);
                htr_consolidation_request(request)
            } else {
                fatal_error(InvalidConfig)
            }
        },
        HtrBuilderDepositRequest => {
            let offset = index * HTR_BUILDER_DEPOSIT_LENGTH;
            if offset + HTR_BUILDER_DEPOSIT_LENGTH <= requests.len then {
                let request = sub_slice(requests, offset, HTR_BUILDER_DEPOSIT_LENGTH);
                htr_builder_deposit_request(request)
            } else {
                fatal_error(InvalidConfig)
            }
        },
        HtrBuilderExitRequest => {
            let offset = index * HTR_BUILDER_EXIT_LENGTH;
            if offset + HTR_BUILDER_EXIT_LENGTH <= requests.len then {
                let request = sub_slice(requests, offset, HTR_BUILDER_EXIT_LENGTH);
                htr_builder_exit_request(request)
            } else {
                fatal_error(InvalidConfig)
            }
        },
    }

function htr_requests_subtree

function htr_requests_subtree(requests, kind, start, count, depth) =
    if count == 0 then {
        ssz_zero_hash(depth)
    } else if depth == 0 then {
        if count == 1 then {
            htr_request_leaf(requests, start, kind)
        } else {
            fatal_error(WitnessDeficient)
        }
    } else {
        let child_depth : htr_depth = depth - 1;
        let half = htr_leaf_capacity(child_depth);
        let left_count : htr_leaf_count =
            if count < half then count else half;
        let right_count : htr_leaf_count =
            if count < half then 0 else count - half;
        let left_root = htr_requests_subtree(requests, kind, start, left_count, child_depth);
        let right_root = htr_requests_subtree(requests, kind, start + half, right_count, child_depth);
        sha256_pair(left_root, right_root)
    }

function htr_deposits

hash_tree_root of the deposit-request list (depth 13).

function htr_deposits(deposits : StatelessInputSlice) -> hash = {
    let items = ssz_bounded_fixed_list_ref(deposits, HTR_DEPOSIT_LENGTH, sizeof(deposit_request_count_bound));
    let count : htr_leaf_count = items.count;
    let root = htr_requests_subtree(deposits, HtrDeposit, 0, count, 13);
    mix_in_length(root, items.count)
}

function htr_withdrawal_requests

hash_tree_root of the withdrawal-request list (depth 4).

function htr_withdrawal_requests(requests : StatelessInputSlice) -> hash = {
    let items = ssz_bounded_fixed_list_ref(
        requests,
        HTR_WITHDRAWAL_REQUEST_LENGTH,
        sizeof(withdrawal_request_count_bound),
    );
    let count : htr_leaf_count = items.count;
    let root = htr_requests_subtree(requests, HtrWithdrawalRequest, 0, count, 4);
    mix_in_length(root, items.count)
}

function htr_consolidation_requests

hash_tree_root of the consolidation-request list (depth 1).

function htr_consolidation_requests(requests : StatelessInputSlice) -> hash = {
    let items = ssz_bounded_fixed_list_ref(
        requests,
        HTR_CONSOLIDATION_REQUEST_LENGTH,
        sizeof(consolidation_request_count_bound),
    );
    let count : htr_leaf_count = items.count;
    let root = htr_requests_subtree(requests, HtrConsolidationRequest, 0, count, 1);
    mix_in_length(root, items.count)
}

function htr_builder_deposit_requests

hash_tree_root of the builder-deposit-request list (depth 6).

function htr_builder_deposit_requests(requests : StatelessInputSlice) -> hash = {
    let items = ssz_bounded_fixed_list_ref(
        requests,
        HTR_BUILDER_DEPOSIT_LENGTH,
        sizeof(builder_deposit_request_count_bound),
    );
    let count : htr_leaf_count = items.count;
    let root = htr_requests_subtree(requests, HtrBuilderDepositRequest, 0, count, 6);
    mix_in_length(root, items.count)
}

function htr_builder_exit_requests

hash_tree_root of the builder-exit-request list (depth 4).

function htr_builder_exit_requests(requests : StatelessInputSlice) -> hash = {
    let items = ssz_bounded_fixed_list_ref(requests, HTR_BUILDER_EXIT_LENGTH, sizeof(builder_exit_request_count_bound));
    let count : htr_leaf_count = items.count;
    let root = htr_requests_subtree(requests, HtrBuilderExitRequest, 0, count, 4);
    mix_in_length(root, items.count)
}

function htr_execution_requests

hash_tree_root of the SszExecutionRequests container.

function htr_execution_requests(input_ref : StatelessInputRef) -> hash = {
    let deposits = htr_deposits(input_ref.deposits);
    let withdrawal_requests = htr_withdrawal_requests(input_ref.withdrawal_requests);
    let consolidation_requests = htr_consolidation_requests(input_ref.consolidation_requests);
    let builder_deposit_requests = htr_builder_deposit_requests(input_ref.builder_deposit_requests);
    let builder_exit_requests = htr_builder_exit_requests(input_ref.builder_exit_requests);
    merkleize(
        [deposits, withdrawal_requests, consolidation_requests, builder_deposit_requests, builder_exit_requests],
        3,
    )
}

function htr_new_payload_request

hash_tree_root of the SszNewPayloadRequest (4 fields, depth 2) — the commitment the guest proves its input against.

function htr_new_payload_request(input_ref : StatelessInputRef) -> hash = {
    let payload_root = htr_execution_payload(input_ref);
    let versioned_hashes_root = htr_versioned_hashes(input_ref.versioned_hashes);
    let beacon_root = htr_ssz_bytes32(input_ref.new_payload_request, NPR_BEACON_ROOT);
    let requests_root = htr_execution_requests(input_ref);
    merkleize([payload_root, versioned_hashes_root, beacon_root, requests_root], 2)
}