The hash-tree-root of all SSZ types merkleizes the contents as a binary tree.
In such a binary tree, the path to any node from the root can be described by a bitfield
This bitfield can also be expressed as an integer, called a generalized index in SSZ.
The generalized index value for a node in a binary tree is 2**depth + index, starting with a 1 for the root.
Visually, this looks as follows:
1
2 3
4 5 6 7
...
TODO: VISUALIZE GENERALIZED MERKLE TREE INDEX
Like the bitfield form that is extended with a 0 or 1 for each child,
the generalized index has the convenient property that the two children of node k are 2k and 2k+1.
To navigate from A to B to C, where B is in the subtree of A and C in the subtree of B, the generalized indices can be composed and sliced:
A generalized index is composed of a leading bit 1 for the root, and the remainder navigates the path in binary tree.
These navigation parts AB and BC can be concatenated to get the navigation part AC: AB ++ BC <-> AC.
And then a 1 is prepended again to delimit the exact length of the path and represent the root.
For implementation purposes, the generalized index matches the position of a node in the linear representation of the Merkle tree, as computed by this function:
def merkle_tree(leaves: Sequence[Bytes32]) -> Sequence[Bytes32]:
padded_length = get_next_power_of_two(len(leaves))
o = [Bytes32()] * padded_length + list(leaves) + [Bytes32()] * (padded_length - len(leaves))
for i in range(padded_length - 1, 0, -1):
o[i] = hash(o[i * 2] + o[i * 2 + 1])
return o
In the SSZ spec, a generalized index is represented as a custom integer type: GeneralizedIndex (of arbitrary bitlength).
It can be also be represented as a Bitvector/Bitlist object.
Note that for bitfields, the root bit is not encoded in SSZ:
bitlists: SSZ already naturally appends 1 to the serialized bits, to make a difference in bitlengths.bitvectors: vectors are of fixed length, and do not need the delimiting bit.