DEV Community

qanzhi111
qanzhi111

Posted on

Liquid Network Range-Proof Collision: How a Cache-Key Bug Inflated ~4,000 L-BTC

The Bug Was Not a Broken Signature

On September 6, 2026, Blockstream’s Liquid Network suffered a confidential-asset accounting failure. A range-proof cache keyed two different proofs to the same lookup string because the implementation concatenated fields without a separator. The colliding cache entry let the network accept an inflation transaction that created about 3,998.5 L-BTC — roughly $318.7 million at the time.

This was not a stolen hot-wallet key, and it was not a typical DeFi reentrancy. It was a cryptographic-cache invariant failure: two distinct proofs hashed to one cache identity, so the second proof inherited the first proof’s “already verified” status.

CertiK’s incident analysis is the cleanest public reconstruction. The inflation transaction is on Liquid as f24a4b179b5cc7e88b25a763911f7cbdf2bf45d1d1b5ab611e94461cef0a183f. After a peg-out, Blockstream later returned 3,400 BTC in a6d697a25266ce3c78774fd1d75f896b7af522ada209b0f6228ea497bc49a46d, leaving about 598.5 BTC still outstanding from the inflated stock.


What a Range Proof Is Doing Here

Liquid uses Elements confidential transactions. Amounts are hidden, so the network cannot simply read “this output is 1.0 L-BTC.” Instead, each output carries a range proof that the committed amount is in a legal range and that no coins were created from nothing.

That proof is expensive. Implementations therefore cache verification results. Caching is fine if and only if the cache key uniquely identifies the proof. If two different proofs share a key, the cache becomes a confused-deputy: the second proof is treated as already valid.

CertiK’s write-up describes the Liquid Elements cache key as a concatenation of proof fields with no delimiter. When field boundaries can slide, two different encodings collide. The verifier then short-circuits.

The economic result is brutal and simple:

Colliding cache key → skipped range check → illegal confidential output accepted → peg-out of unbacked L-BTC.

Once L-BTC can be pegged out to mainchain BTC, the bug is no longer a sidechain accounting curiosity. It is real Bitcoin leaving a federation that thought the confidential math was sound.


Timeline, in One Pass

Public reporting and CertiK’s analysis converge on this sequence:

  1. A range-proof cache-key collision in Elements allowed an inflation transaction of about 3,998.5 L-BTC.
  2. The inflated confidential stock was pegged out toward Bitcoin.
  3. Liquid / Blockstream responded by patching Elements to v23.3.4 and containing further issuance.
  4. A recovery transfer returned 3,400 BTC to the federation.
  5. About 598.5 BTC of the inflated amount remained unrecovered at the time of the public analysis.

The headline number (~$319 million) is the gross inflation, not the final loss. The operational number that still matters is the unreturned residual. Federation chains live or die on whether the peg remains 1:1. Even a partial hole is a solvency event.


Why Sidechain Cache Bugs Are Worse Than “Just a Node Crash”

Most node bugs halt a network. This one minted purchasing power.

Confidential-asset systems hide the very quantities that a public chain would check in plaintext. Every cache, every batch verifier, every “we already saw this proof” shortcut is therefore consensus-critical. A false positive in the cache is equivalent to a false positive in CheckTransaction.

Three properties made Liquid a high-value target for this class of bug:

  • Hidden amounts. Reviewers cannot eyeball an output and see “this is 4,000 BTC.”
  • Peg-out to mainchain BTC. Illegal sidechain units can be converted into the most liquid asset in crypto.
  • Federation trust plus cryptography. Users assume the math closes the gap that a federation cannot see. If the math cache lies, the federation signs a peg-out of coins that never existed.

This is the same family of failure as cross-chain accounting bugs, just one layer lower: not a pool share, but a proof cache.


What Protocol Teams Should Copy From the Patch

1. Domain-separate every cache key

Never concatenate variable-length fields without a delimiter or a length prefix. Use a tagged hash:

key = H("elements/rangeproof-v1" || len(a) || a || len(b) || b || ...)

If two different tuples can serialize to the same string, the cache is an inflation gadget.

2. Treat verifier caches as consensus state

A cache hit must be as conservative as a full verify. When in doubt, miss the cache. Throughput optimizations that can mint coins are not optimizations.

3. Add an inflation invariant next to the peg

A federation should continuously check:

sum(peg-in) + known issuance == sum(peg-out) + confidential stock

Confidential stock is hard to audit in the open, which is exactly why the invariant needs a second channel: watchdog nodes, audit proofs, or a circuit breaker on abnormal peg-out size.

4. Circuit-break large peg-outs

A ~4,000 L-BTC peg-out is not a retail withdrawal. Size, velocity, and first-seen proof IDs should all be able to pause the peg.

5. Publish the residual, not only the patch

Returning 3,400 BTC was the right operational move. Leaving ~598.5 BTC unexplained is still a peg-integrity problem. Users need a public residual schedule, not only a version bump to Elements v23.3.4.


What This Means for Investigators

For on-chain investigators, Liquid is a reminder that the interesting transaction may be the inflation tx, not the mixer’s first hop.

Useful artifacts from this case:

  • Inflation tx: f24a4b179b5cc7e88b25a763911f7cbdf2bf45d1d1b5ab611e94461cef0a183f
  • Return tx: a6d697a25266ce3c78774fd1d75f896b7af522ada209b0f6228ea497bc49a46d
  • Patch train: Elements v23.3.4
  • Primary analysis: CertiK, Liquid Network incident analysis

The investigative question is not “who signed.” It is “which verifier believed a proof it never fully checked.” Once you frame it that way, cache-key collisions sit next to signature-malleability and replay bugs: small encoding mistakes with mint-level consequences.


Final Takeaway

Liquid did not lose ~4,000 BTC because a bridge contract had a typo in transferFrom. It lost the 1:1 peg, briefly and then partially, because a range-proof cache could not tell two proofs apart.

For builders: cache keys are consensus. For users of federated confidential chains: a peg is only as strong as the verifier’s ability to reject a colliding proof.

The residual ~598.5 BTC is the number to keep on the watchlist. Patches close the bug class. They do not automatically refill the peg.


Sources: CertiK Liquid Network incident analysis; Liquid / Elements v23.3.4 release notes; public Liquid transaction records for the inflation and return hashes above.

Top comments (0)