bitcoin Β· validation Β· accumulators

The kilobyte UTXO set

Every Bitcoin node drags a 12 GB database through its whole life just to answer one question: does this coin exist? Cryptographic accumulators β€” Utreexo above all β€” compress that answer into about thirty hashes. Here's why that matters, how it works, and what it costs.

01 Β· the problem

Validation is cheap. State is not.

To validate a transaction, a node must check each input against a record of every coin that exists and hasn't been spent β€” the UTXO set (unspent transaction outputs). Each check needs a handful of facts: the coin exists, its amount, its spending condition (script), plus its creation height and coinbase flag (for coinbase maturity and BIP 68 relative timelocks). Then the spent coins are deleted and the new outputs inserted.

~170M
unspent coins tracked by every node
~12 GB
UTXO database on disk (chainstate)
~3B
random lookups during one full sync
~0.9 kB
the same state, as a Utreexo accumulator

Signatures can be skipped below a reviewable checkpoint (assumevalid), bandwidth can be bought, but the UTXO set is different: it's random-access state touched by every single input. If it fits in RAM, lookups are ~100 ns; if it doesn't, they become disk seeks and a full sync degrades from hours to days. State is the term that decides whether a Raspberry Pi and a workstation live in the same universe.

How Bitcoin Core actually stores it β€” LevelDB, dbcache, and the flush spiral

Core keeps the UTXO set in a LevelDB database (chainstate/, ~48 bytes per coin plus index overhead) fronted by an in-memory cache (-dbcache, default 450 MiB). While the working set fits in the cache, coin lookups and updates are memory-speed. When the cache fills during sync, Core writes it out and empties it β€” so the next few million lookups all miss and go to disk.

With ~170M coins needing north of 10 GB of cache, a default-sized cache overflows constantly. Every miss is a random read; every flush rewrites overlapping LevelDB files (compaction). On a machine with 2–4 GB of RAM this β€” not hashing, not signatures β€” is where initial sync goes to die. The standard advice "raise your dbcache" is a workaround for exactly the cost accumulators remove.

Why snapshots don't solve this β€” assumeutxo still ships the state

assumeutxo lets a node start from a UTXO snapshot and validate history in the background β€” a big usability win. But the snapshot is the 12 GB state: you still have to download it, hash it, store it, and random-access it forever after. It moves the state problem around the timeline; it doesn't shrink the state. An accumulator shrinks the thing itself, which is why the two compose nicely: an accumulator checkpoint is a kilobyte you can read in a code review.

02 Β· the idea

Flip who carries the data

An accumulator is a compact commitment to a set. It supports four operations: add an element, delete one, prove membership, and verify a proof. The trick is a change of responsibility: the verifier keeps only the tiny commitment, and whoever wants to spend a coin must present a proof that it's in the set.

Today β€” node carries the set

transaction input β€œspend coin X”
↓
look up X in a 12 GB database random read, per input, forever
↓
exists? get amount + script β†’ validate

Accumulator β€” spender carries a proof

input + ~1 kB proof β€œX is in the set, here's the path”
↓
check proof against ~28 hashes pure hashing, no database
↓
amount + script are inside the proven leaf β†’ validate

The database becomes a receipt check. Nothing about Bitcoin's consensus rules changes β€” only how a node represents what it already verifies.

03 Β· the mechanism

Utreexo: a forest that counts in binary

Utreexo (Dryja, 2019) arranges the UTXO set as a forest of perfect Merkle trees β€” every leaf is the hash of one coin (its outpoint, amount, script, creation height, and coinbase flag). The forest's shape is forced by one rule: tree sizes are the binary digits of the coin count. 6 coins = 110β‚‚ = one 4-tree and one 2-tree. The node stores only the tree roots and the count. That's the entire database.

Try it β€” add coins and watch trees merge like carry bits; click a coin, see its proof, spend it:

accumulator playground

node state = count + roots: 
root (stored by the node) derived node (not stored) selected coin its proof (supplied by the spender)
Click any coin to see the Merkle proof a spender would have to provide.

Three things to notice while playing:

Adding is a carry. A new coin starts as a lone 1-tree. If a 1-tree already exists, the two combine under a new parent; if that collides with a 2-tree, they combine again β€” exactly like incrementing a binary counter. Adds need no data at all beyond the roots.

A proof is a path. To spend a coin, you present the coin plus its sibling hashes up to a root β€” about logβ‚‚(n) hashes, ~28 for the real UTXO set. The amount and script are inside the leaf hash, so a proof can't lie about them: the hashes wouldn't land on the node's stored root.

The proof is also the update. This is Utreexo's quiet elegance: when a coin is deleted, the new roots of the leftover pieces are literally the hashes from the proof you just verified. Verification and the state update consume the same bytes β€” the node never needs data it doesn't already hold in its hand.

The delete, step by step β€” worked example with 4 coins

Say the forest holds one 4-tree over coins a b c d: root R = H(H(a,b), H(c,d)). A transaction spends c, attaching the proof {d, H(a,b)}.

Verify: the node computes H(c,d) from the claimed coin and its sibling, then H(H(a,b), H(c,d)), and checks the result equals its stored root R. It does β€” the coin exists, and its amount/script (inside c's leaf hash) are authentic.

Delete: remove the 4-tree's root from state. Three coins remain, and 3 = 11β‚‚, so the forest must become a 2-tree plus a 1-tree. Their roots are H(a,b) and d β€” both are items from the proof. No lookup, no recomputation from leaves the node doesn't have.

In general, deleting a leaf makes its sibling "move up" one level, and the proof always contains exactly the hashes needed to rebuild every affected root.

A footnote for the precise: this tree-splitting is how the 2019 paper presents deletion. The implementations that shipped (utreexod, Floresta's rustreexo) use a newer swapless scheme β€” the leaf counter never decreases, the deleted slot simply goes empty, and d moves up into its parent's position, so the example would end as the single root H(H(a,b), d) rather than two roots. The property that matters is identical in both designs: the verified proof contains exactly the bytes needed to write the new roots.

What the playground simplifies β€” positions, batching, and real deletes

Production Utreexo tracks every node's position in the forest and deletes in batches (all of a block's spends at once) β€” necessary so that proofs from different peers refer to the same structure, and so multiple deletes in one tree don't trample each other. The 2019 paper keeps the forest canonical with swap operations; the shipped implementations (utreexod, Floresta's rustreexo) instead use a swapless scheme where deleted slots go empty and siblings move up in place. The playground deletes in place and then re-merges equal-sized trees one narrated step at a time, which keeps the binary-counter picture exact but arranges trees by history rather than canonically. The root arithmetic you see β€” proof hashes becoming new roots β€” is faithful.

Real leaf hashes also commit to the block height and coinbase flag, and the forest is hashed with tagged SHA-512/256. See the paper for the batched transformation.

What does the node trust? β€” nothing but its own hashes

Where do the roots come from? The node computes them itself, from genesis β€” it starts with an empty forest and applies every add and delete with its own hands as it validates each block. The accumulator state at any height is a pure function of the chain, exactly like today's UTXO set, only smaller. Nobody hands you roots.

Given that, proofs are self-authenticating: a proof either hashes up to your own stored roots or it doesn't, and forging one β€” wrong amount, wrong script, a coin that never existed β€” requires a SHA-2 collision (implementations hash with SHA-512/256). Whoever supplies proofs (a peer, a bridge) is exactly as untrusted as a peer supplying blocks today: garbage fails validation and the block is rejected.

Bridges are therefore a liveness dependency, never a safety one β€” with no proof you can't validate a spend, so you can be starved, but you cannot be fooled. The one optional trust-flavored piece is a hardcoded checkpoint (starting from baked-in roots instead of genesis), and that sits in the same trust class as assumevalid: a reviewable constant in the source, auditable by anyone who syncs without it.

Where do proofs come from? β€” bridge nodes and stale proofs

Somebody has to generate proofs for spenders, and today's wallets don't track them. Bridge nodes fill the gap: they maintain the entire forest (every internal hash β€” larger than a plain UTXO database) and attach proofs to transactions and blocks on behalf of the legacy ecosystem.

There's a subtler cost: proofs go stale. Every block adds and deletes leaves, which restructures the forest, so a proof valid at height H may be wrong at H+1. Wallets that hold their own proofs must refresh them as blocks arrive (cheap per block, but a standing obligation), or lean on a bridge to prove on demand. This dynamic-proof property is the main engineering difference from textbook Merkle trees, and the main reason adoption needs infrastructure, not just code.

04 Β· the price

What Utreexo trades away

Accumulators don't create efficiency from nothing β€” they move costs to where they're cheaper to pay. The honest ledger:

deleted

State & random I/O

Node state: 12 GB β†’ ~1 kB. No database, no cache tuning, no flush stalls. Validation speed becomes independent of RAM β€” a Pi validates like a workstation.

added

Bandwidth

Blocks travel with proofs: about Γ—1.7 the bytes as utreexod ships today (worst case Γ—4 for per-transaction relay). Caching cuts most of it β€” most coins are spent soon after creation, proofs for cached coins are omitted, and the paper's simulation with a 500 MB leaf cache lands near Γ—1.25.

added

Prover burden

Spenders (or bridge nodes on their behalf) must generate and refresh proofs. Bridges store more than a normal node β€” the asymmetry is the point, but someone must run them.

added

CPU, slightly

~log n hashes per input is more raw compute than a RAM hash-map hit. On a big-RAM machine Utreexo doesn't win time β€” it wins footprint. The speedup is real only where state was the bottleneck.

Why the bandwidth hit is smaller than it looks β€” coins die young

Empirically, a large share of outputs are spent within hours or days of creation (change outputs, exchange churn, batching flows). Utreexo exploits this: nodes keep recently added leaves cached, and peers skip sending proofs for anything the receiver is known to cache. Since most spends hit young coins, most proofs shrink to nearly nothing. The published numbers span exactly this effect: proofs roughly double a block uncached, utreexod ships at about Γ—1.7, and the paper's simulated 500 MB leaf cache brings the whole download down to about Γ—1.25. The long-tail β€” old coins waking up β€” pays full ~28-hash fare.

What it means for initial sync β€” which bottleneck it removes, in numbers

A full sync is bounded below by max(network, disk, cpu). On mid-2026 mainnet (~753 GB of blocks, ~3B inputs): a 1 Gbps line needs ~1h40m for the download no matter what; hashing and parsing cost tens of minutes of CPU; signatures below assumevalid are skipped. On big-RAM hardware the UTXO term is small β€” so Utreexo barely moves the bound there, and the extra proof bytes actually raise the network term.

The transformation is on constrained hardware: with 2 GB of RAM, a stock node's UTXO term explodes into billions of disk seeks (days), while a Utreexo node's stays exactly where the big-RAM one is. Accumulators don't lower the floor β€” they make the floor reachable on hardware that used to sit 10Γ— above it.

05 Β· the inversion

SwiftSync: never build the database at all

Utreexo shrinks the UTXO set by making spenders carry proofs. SwiftSync (Somsen, 2025) goes further for the special case of syncing: it needs no proofs, no forest, no lookups. You download a hint file β€” literally one bit per output ever created, saying whether that output will still be unspent when you reach the tip (~100 MB compressed for all of Bitcoin's history). Then validation becomes bookkeeping:

One pass β€” every output is sorted by its hint bit

output created Β· hint = 1 β€œsurvives to the tip”
↓
append to the final UTXO set write-once β€” never read during sync
output created Β· hint = 0 β€œwill be spent before the tip”
↓
aggregate += H(outpoint) never stored anywhere
input spends a coin
↓
aggregate βˆ’= H(outpoint) no existence check β€” just subtract

End of sync β€” a single equality

= 0every spend matched exactly one creation, and every doomed output was spent exactly once. The set is consistent β€” proven without ever asking β€œdoes this coin exist?”
β‰  0a hint was wrong, or a block spent a coin that never existed. Sync aborts. Bad hints can waste your time; they can never make you accept an invalid state β€” the scheme fails closed.

That fail-closed property is why the hint file needs no trust in the safety sense: anyone can publish hints, and lying is self-defeating.

The second consequence is the deeper one. Adding and subtracting from an aggregate commutes β€” order doesn't matter. So blocks no longer have to be validated one after another:

Today β€” inherently serial

b₁→bβ‚‚β†’b₃→bβ‚„

Each block reads and mutates the UTXO state left by the previous one. One thread connects blocks, no matter how many cores you own.

SwiftSync β€” commutative

b₃b₁bβ‚„bβ‚‚ β‡’Ξ£ Β± H(…)

Any order, every core at once. This attacks the serial bottleneck of sync β€” the one cost no amount of bandwidth or disk can buy back.

The trade: spent coins are never materialized, so their scripts can't be individually checked (nor coinbase maturity, which needs the same per-coin metadata) β€” SwiftSync inherits the same assumevalid stance a default node already takes for old signatures. It's a sync-time trick only: when it finishes you're a stock node with a stock database. A proof-of-concept measured a 5.28Γ— sync speedup before parallel validation is even exploited.

Why a sum can prove set-consistency β€” the accountant's argument

Think of the aggregate as a ledger that must balance. Every hint-0 output deposits H(outpoint) exactly once when it's created; every input withdraws H(outpoint) exactly once when it spends. If history is honest, deposits and withdrawals pair off perfectly and the balance is zero.

Now try to cheat. Spend a coin that never existed: a withdrawal with no matching deposit β€” non-zero. Spend the same coin twice: two withdrawals, one deposit β€” non-zero. Mark a doomed output as β€œunspent” in the hints: its deposit never happens but its withdrawal does β€” non-zero. Mark a surviving output as β€œdoomed”: a deposit nobody withdraws β€” non-zero. The hash makes collisions infeasible, so entries can't be forged to cancel accidentally. One equality at the end certifies billions of set operations that were never performed individually.

Fuller designs fold amounts into the aggregated data to tighten the money-supply check; the gist walks the exact accounting.

Should you trust the hint file? β€” no, and the node doesn't

The hints are not an authority β€” they're foreknowledge: claims about the future ("this output will die before the tip") that let the node skip building a searchable database. Foreknowledge is checkable after the fact, and every bit is checked β€” just not at the moment it's read. All the checks are batched into the single zero-equality at the end; the four ways a hint or a block could lie each leave the sum non-zero (see the drawer above).

Two hardening details close the remaining gaps. First, each node salts the hash with its own random secret, so an adversary who authors both hints and transactions can't precompute entries engineered to cancel (this blocks Wagner-style attacks on additive aggregates). Second, hints are deterministic given the chain and target height β€” anyone can regenerate the file and compare, so a published hint file is reproducible, not bespoke.

The result: wrong hints have exactly one power β€” making the final equation fail, at which point the node discards the attempt and fetches hints elsewhere. The failure mode is wasted hours, never a wrong UTXO set. That's the pattern all of these designs share (Utreexo proofs, SwiftSync hints, assumeutxo snapshots): convert trust into deferred verification, and make every failure fail closed. Untrusted helpers may make you slow; they may never make you wrong. The honest residue is the same as today's node: the reviewable constants in the source (assumevalid, snapshot hashes) and the code itself.

SwiftSync vs Utreexo, head to head β€” they solve different problems
UtreexoSwiftSync
Scopenode architecture, foreverinitial sync only
State while running~1 kB of rootshint bits + one aggregate
Network costΓ—1.2–1.7 blocks, ongoing~100 MB hints, once
Parallel validationno β€” forest updates are orderedyes β€” fully commutative
Trust addednonenone for safety; pairs with assumevalid
Needs ecosystembridges, proof relay, wallet supportnothing β€” one hint file
After syncstays a kilobyte nodebecomes a stock node

They compose, too: a Utreexo node can use SwiftSync-style hints to skip proof verification for coins that die during sync β€” the ideas attack orthogonal costs (state vs. seriality).

06 Β· the design space

Utreexo isn't the only answer

Several designs attack the same state problem from different angles. The columns that matter: what cost it deletes, what it charges, and whether trust assumptions move.

ApproachDeletes which costPays withTrust changeStatus
UtreexoUTXO state, forever (~1 kB node)Γ—1.2–1.7 block bandwidth; bridge infra; dynamic proofsnoneutreexod, Floresta
SwiftSync Β§05UTXO state during sync + makes blocks verifiable in parallel~100 MB hint file (fail-closed if wrong)pairs with assumevalidPoC, ~5Γ— sync
assumeutxowaiting: start at a snapshot, validate history behindobtain + verify a 12 GB snapshot; state unchangedreviewable snapshot hash in codeshipped in Core
UHS Fields~half of state: store coin hashes onlypeers attach coin data to relayed txsnoneproposal
TXO bitfield Cohenmost of state: one spentness bit per historical outputseparate index to locate coin datanoneproposal
UTXO commitmentstrust in any snapshot: miners commit the set's hasha consensus change β€” the hard partreduces trustnever activated
RSA / class-group accumulatorsproof staleness & log-size proofs (constant-size instead)trusted setup (RSA) or slow group mathsetup ceremony (RSA)research
ZeroSyncsync itself: verify a succinct proof of the whole chainenormous proving cost; young cryptographysoundness of the proof systemresearch
Why not RSA accumulators? β€” constant-size proofs exist, with a catch

RSA accumulators (Boneh–BΓΌnz–Fisch, 2019) commit a set into a single group element; membership proofs are constant-size and don't go stale the way Merkle paths do, and thousands of proofs aggregate into one. The catch: you need a modulus nobody can factor β€” a trusted setup ceremony, anathema to Bitcoin's assumptions. Class groups avoid the ceremony but make every operation orders of magnitude slower. Merkle forests won in practice because they're plain SHA-2 hashing (SHA-512/256 in the shipped code): no new assumptions, fast on any CPU, auditable by anyone who can read forty lines of code.

07 Β· who runs what

Applicability: pick by deployment, not by elegance

None of these designs dominates β€” they spend different budgets (RAM, bandwidth, latency, sync time), so the right one depends on what the machine is for. The honest matching:

DeploymentPain todayBest fitWhy
Desktop / workstation nodelittle β€” a few hours of syncassumeutxo now; SwiftSync when it landsthe UTXO set already fits in RAM, so Utreexo would only add bandwidth; what's left to optimize is sync time
Home server / Raspberry Picache thrashing: days of sync, dbcache tuning, SD-card wearUtreexo (utreexod, Floresta) + SwiftSync for the syncvalidation becomes RAM-independent β€” the death-spiral mode this class of hardware lives in simply disappears
Phone / mobile walletlight wallets verify PoW + inclusion but can't check rule-validity; Electrum-style ones also reveal addressesUtreexo from a checkpoint (embedded Floresta)~1 kB of state and forward-only validation are phone-sized; the first plausible path to a wallet that enforces the rules itself
Exchange / explorer / heavy infranone of the above β€” they need rich indexes anywaystock node, big dbcache; run bridges as a serviceaddress and history indexes dwarf the UTXO set; accumulators shrink exactly the state they must keep expanded
Miner / poolvalidation latency is moneystock node, everything in RAMproof bytes and extra hashing in the block-relay hot path are pure cost with zero benefit
Ephemeral nodes β€” CI, analytics, fleet spin-upevery fresh instance pays a full IBDSwiftSync (+ assumeutxo today)reproducible hints turn node provisioning into a mostly-parallel batch job
What a phone full node actually looks like β€” the Utreexo endgame

The pieces: an embeddable compact-state node (Floresta ships as a library with bindings for exactly this), a hardcoded accumulator checkpoint β€” a kilobyte of roots, reviewable in the source like assumevalid β€” and forward-only validation from that checkpoint: headers, then blocks with proofs, a few thousand blocks rather than history. Wallet discovery uses compact block filters (BIP 158) checked locally, so no server ever learns your addresses.

The honest costs: proof-carrying blocks are ~Γ—1.7 bytes on a possibly metered connection, and sustained hashing costs battery β€” so the realistic mode is "validate on Wi-Fi and charge, serve the wallet locally all day," not always-on validation. The trust model is the desktop one: your own hashes plus a reviewable constant.

To be precise about what this improves on: today's light wallets are not "no validation." An SPV client verifies the header chain (proof-of-work, difficulty) and Merkle inclusion of its transactions, so a server cannot forge a payment to it β€” and Neutrino (BIP 157/158) already fixed the privacy leak that Electrum-style and bloom-filter wallets have. What no light client can do is check rule-validity: scripts, signatures, inflation, double-spends elsewhere. It follows the most-work chain unconditionally, trusting the hashrate majority to enforce the rules, and it can be lied to by omission. That specific gap β€” enforcing consensus rules yourself β€” is the one a checkpointed Utreexo node closes; privacy it merely matches.

Further out on the same road: consensus-committed UTXO roots would remove even the checkpoint constant, and ZeroSync-style chain proofs would replace catching-up with downloading a proof β€” but those need a soft fork and maturing proof systems respectively. Utreexo-from-checkpoint needs neither.

Why the heavyweights keep fat nodes β€” and why that's the design working

Miners race the network: every millisecond spent validating a fresh block delays mining on top of it, which costs real revenue in stale and empty blocks. They keep the whole UTXO set pinned in RAM and want the block-relay hot path as thin as possible β€” attaching and verifying proofs there is a cost with no compensating benefit for them.

Exchanges, explorers, and API providers need address indexes, transaction history, and mempool analytics β€” orders of magnitude more state than the UTXO set β€” so shrinking the UTXO set saves them approximately nothing. But they own the beefy hardware anyway, which makes them the natural bridge operators: maintaining the full forest and serving proofs is a service role that fits infrastructure budgets.

This split isn't a failure of adoption β€” it's the intended shape: few fat provers, many thin verifiers. The asymmetry between a data-center bridge and a phone verifying against 28 hashes is precisely what the accumulator was designed to create.

08 Β· further reading

Go deeper