Why
The state-bloat problem this catalogue keeps running into from the application side, looked at from the protocol side. Every card here that writes a storage slot — an enforcer's spent counter, a token balance — adds to state that every node keeps live forever. Verkle trees do not delete any of it; they change what a node must carry to prove a piece of it, which is the difference between "state is too big" and "state is too big to sync".
How it works
Reading note, not a demo: how a Merkle proof's size grows with tree width (you must supply every sibling at every level), why vector commitments collapse that to a constant-size proof regardless of width, and what Ethereum's Verge roadmap intends to buy with the swap — stateless clients that validate without holding the state. Also what it costs: heavier cryptography, and a migration of the entire state trie.
Review clarification
Proof size, not proof time
This is the distinction the review kept returning to. A Verkle tree shrinks the proof's size (bytes to transmit), not the time to prove or verify. Its cryptography is heavier per operation — vector commitments over elliptic curves instead of plain SHA-256 hashing — so end-to-end compute goes up, not down. That is the card's subtitle made literal: hashing was never the bottleneck, bandwidth was.
One opening per level, regardless of width
Merkle: at each level you must supply every other child in the group — branching − 1 siblings, which grows with width. Verkle: a vector commitment collapses each level to one constant-size opening, whatever the width. So total proof ≈ (constant per level) × (number of levels). Numbers from the Related code, proving leaf #5 of 64:
| branching | depth | Merkle openings | Verkle openings |
|---|---|---|---|
| 2 | 6 | 6 | 6 |
| 16 | 2 | 30 | 2 |
| 256 | 1 | 255 | 1 |
Why Verkle deliberately goes wide
Because width is free for proof size, Verkle designers make nodes wide — Ethereum's design is 256-ary — which makes the tree shallow, so the proof is tiny on two counts at once: constant per level and fewer levels. Merkle cannot do this: every extra child costs one more sibling in every proof, which is why real Merkle trees stay binary.
The cost moved; it did not vanish
Verkle trades many cheap hash siblings for fewer, bigger, cryptographically heavier openings. Size down, crypto compute up. That trade is what buys stateless clients — a node validates a block without holding the whole state, because the witness it ships each block is finally small enough to move around the network.
What the Related code does — and does not
The Merkle half is real (SHA-256, counts siblings). The Verkle half mirrors it line-for-line but models proof size only: its commitment is still a hash, so the file runs with no libraries and is not cryptographically sound. A production Verkle needs an IPA/KZG vector commitment (elliptic-curve math) to make one O(1) opening actually prove a child at any position.
Related code
"""Merkle vs Verkle PoC -- proof size grows with tree width, not with hashing speed.
Illustrates the core mechanism: a Merkle proof needs one sibling hash per level, so
wider trees (more children per node) need more siblings per level to prove membership.
The Verkle half mirrors the Merkle half line-for-line -- same tree, same leaf. The ONLY
thing that changes is how many openings a proof carries per level:
Merkle: (children in the group - 1) siblings per level -> grows with width
Verkle: exactly ONE opening per level -> constant, any width
NOTE: a production Verkle uses an IPA/KZG *vector commitment* (elliptic-curve math) so
that one O(1) opening proves a child at any position. The Verkle commitment below is
still a hash, so the file runs with no libraries -- it only MODELS the proof-*size*
property, and is NOT cryptographically sound. The point is size, not crypto.
"""
import hashlib
def h(*parts: str) -> str:
return hashlib.sha256("|".join(parts).encode()).hexdigest()[:12]
# ── Merkle ────────────────────────────────────────────────────────────────────
def build_tree(leaves: list[str], branching: int) -> list[list[str]]:
"""Builds a Merkle tree with `branching` children per node; returns levels bottom-up."""
levels = [leaves]
while len(levels[-1]) > 1:
cur = levels[-1]
nxt = []
for i in range(0, len(cur), branching):
group = cur[i:i + branching]
nxt.append(h(*group))
levels.append(nxt)
return levels
def proof_size(levels: list[list[str]], leaf_index: int, branching: int) -> int:
"""Count sibling hashes needed to prove one leaf's membership -- (branching - 1) per level."""
siblings = 0
idx = leaf_index
for level in levels[:-1]:
siblings += branching - 1 # every level, you must supply all other children in the group
idx //= branching
return siblings
# ── Verkle (same tree shape; only the proof model differs) ──────────────────────
def verkle_build_tree(leaves: list[str], branching: int) -> list[list[str]]:
"""Same shape as build_tree; commit stands in for a vector commitment over the children."""
levels = [leaves]
while len(levels[-1]) > 1:
cur = levels[-1]
nxt = []
for i in range(0, len(cur), branching):
group = cur[i:i + branching]
nxt.append(h("vc", *group)) # a real Verkle uses an IPA/KZG commitment here
levels.append(nxt)
return levels
def verkle_proof_size(levels: list[list[str]], leaf_index: int, branching: int) -> int:
"""ONE constant-size opening per level, regardless of width -- what a vector commitment buys."""
return len(levels) - 1
if __name__ == "__main__":
leaves = [f"leaf{i}" for i in range(64)]
# bytes per opening: a 32B sibling hash (Merkle) vs one 48B EC opening (Verkle, BLS12-381 G1)
MB, VB = 32, 48
print(f"{'branch':>6}{'depth':>7}{'merkle_open':>13}{'verkle_open':>13}{'merkle_B':>10}{'verkle_B':>10}")
for branching in (2, 4, 8, 16):
mt = build_tree(leaves, branching)
vt = verkle_build_tree(leaves, branching)
mo = proof_size(mt, leaf_index=5, branching=branching)
vo = verkle_proof_size(vt, leaf_index=5, branching=branching)
print(f"{branching:>6}{len(mt) - 1:>7}{mo:>13}{vo:>13}{mo * MB:>10}{vo * VB:>10}")
# why Verkle deliberately picks a WIDE node (Ethereum's design is 256-ary):
# width is free for proof SIZE, so go wide -> shallow tree -> tiny proof.
print("\n--- wide node: free for Verkle, ruinous for Merkle ---")
for branching in (2, 16, 256):
mt = build_tree(leaves, branching)
vt = verkle_build_tree(leaves, branching)
print(f"branch={branching:>3} depth={len(mt) - 1} "
f"merkle openings={proof_size(mt, 5, branching):>3} "
f"verkle openings={verkle_proof_size(vt, 5, branching)}")
print("\nMerkle: wider = bigger proof (siblings pile up), so real trees stay binary.")
print("Verkle: a vector commitment collapses each level's siblings to a constant-size")
print("opening regardless of width -- so go wide, shallow, tiny. The cost did not vanish;")
print("it moved into heavier cryptography (per opening), not into more bytes. That")
print("constant-size property is what makes stateless clients (validate without holding")
print("the whole state) practical.")