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.")
docs/code/pocs/merkle-vs-verkle.py
Where it lands in Jayverse
- Devnet: track proof size, not hashing speed, for any future L2. When Devnet moves from an Anvil fork toward an OP-Stack L2, treat proof-size growth as the metric that decides whether a stateless or light client is feasible, not raw hash throughput.
- Auditor: flag storage growth in every contract that writes persistent state. Verex order state, Bridge mint counters and Rabbit's session-key mandates all add to state every node keeps forever; treat that growth as a first-class cost the way Ethereum's Verge roadmap does.
- gitboard: add a state-size metric per service. Track storage growth for Verex and Bridge so the "how big does this get" question is visible before it becomes a syncing problem.
Key expressions
| Expression | 뜻 · 쓰이는 자리 |
|---|---|
| keep running into | 계속 맞닥뜨리다 · 반복해서 같은 문제에 부딪힐 때. "the state-bloat problem this catalogue keeps running into" |
| looked at from ... side | ~쪽에서 바라본 · 같은 문제를 다른 관점에서 볼 때. "looked at from the protocol side" |
| collapse to | ~으로 축소되다, 하나로 합쳐지다 · 여러 항목이 하나의 값으로 줄어들 때. "collapses each level to one constant-size opening" |
| buy (achieve) | 대가를 치르고 얻어내다 · 비용을 들여 특정 이익을 확보할 때. "what Ethereum's Verge roadmap intends to buy" |
| trade X for Y | X를 내주고 Y를 얻다 · 하나를 포기하고 다른 것을 얻는 교환. "Verkle trades many cheap hash siblings for fewer" |
| line-for-line | 한 줄 한 줄 그대로 · 구조를 거의 똑같이 따라할 때. "mirrors it line-for-line but models proof size only" |
| made literal | 말 그대로 실현된, 문자 그대로가 된 · 비유적 표현이 실제 사실로 확인될 때. "the card's subtitle made literal" |
| go wide | 설계를 폭넓게 가다, 너비를 늘리다 · 트리 구조 등에서 분기 수를 늘리는 선택. "Why Verkle deliberately goes wide" |
| IPA/KZG | 내적 논증/케이트 공약(Inner Product Argument / Kate-Zaverucha-Goldberg commitment) · 실제 Verkle 트리에 쓰이는 벡터 공약 방식들. "A production Verkle needs an IPA/KZG vector commitment" |
| vector commitment | 벡터 공약(여러 값을 하나의 짧은 증거로 압축하는 암호 기법) · Verkle 증명 크기를 일정하게 만드는 핵심 도구. "vector commitments over elliptic curves instead of plain SHA-256" |
| witness | 블록 검증에 필요한 압축 증거 데이터 · 상태 전체 없이도 유효성을 증명하게 해주는 증거. "the witness it ships each block is finally small" |