Liquid Network Post-Mortem: The Cache Bug Behind a ~4,000 BTC Drain
Summary
On September 6, 2026, exploiters created approximately 4,000 unbacked L-BTC and redeemed it through SideSwap for real bitcoin. Liquid and SideSwap reported that their private keys remained secure. The failure was in the software deciding whether the L-BTC was valid. Liquid's incident update
The exploiters returned 3,400 BTC the following day, leaving approximately 598.5 BTC under their control. Liquid remained paused at this article's evidence cutoff: September 9, 12:24 UTC. This is an independent preliminary analysis. Return transaction, service status
Root Cause
Liquid hides transaction amounts using confidential transactions. Range proofs let nodes verify that those hidden amounts fall within permitted bounds. Elements, Liquid's node software, caches successful checks so it can reuse the result.
The vulnerable cache key combined four fields:
H(proof || value_commitment || asset_generator || script)
The problem was concatenation without field boundaries. The proof and script can vary in length, allowing different inputs to produce the same combined bytes. Vulnerable source
The historical transaction pair shows this directly. The incident output's proof was 68 bytes longer, while its script was 68 bytes shorter. Both complete encodings were 4,301 bytes and matched byte for byte.
Let P0 and C0 be the setup proof and commitment, C1 the incident commitment, and X the asset generator. The bars below mark field boundaries:
Setup: P0 | C0 | X | (6a43 || C1 || X || 6a)
Attack: (P0 || C0 || X || 6a43) | C1 | X | 6a
Flatten either row and the result is identical. SHA-256 was not broken; the application supplied the same bytes for two different verification problems. Transaction reconstruction
That became exploitable because a cache hit returned success before full range-proof verification. With a matching valid result already cached, an invalid tuple could inherit its acceptance.
There is also an important distinction in the code history. An earlier key omitted the asset and script. The change that added them introduced this encoding ambiguity. The observed pair matches the expanded key, not the earlier two-field key. Context-binding change, mononaut's analysis
The byte equality was reproduced locally using the published generator. The precise live cache-priming sequence and deployed operator binaries remain unconfirmed; this was not a full-node exploit replay.
Exploit Flow
flowchart LR
A["Matching valid<br/>cache entry"] --> B["Crafted tuple<br/>hits same key"]
B --> C["Invalid L-BTC<br/>accepted"]
C --> D["Authorized<br/>SideSwap peg-out"]
D --> E["Real BTC paid<br/>to exploiter"]
SideSwap says its node accepted the L-BTC and processed the order normally. The federation then paid the authorized Bitcoin destination, and SideSwap forwarded the customer payment. Secure signing keys could not compensate for an incorrect validation result. SideSwap's statement
Impact and Drain Details
| Movement | BTC | Transaction |
|---|---|---|
| Federation to SideSwap payout address | 3,996.01834922 | 8db751a6… |
| SideSwap to exploiters | 3,995.99999857 | 85d2ca15… |
| Exploiters to federation | 3,400.00000000 | a6d697a2… |
These are successive transfers, not amounts to add together. The often-cited 4,019 BTC figure is the federation transaction's input total, including funds returned as change.
The impact extended beyond the reserve loss: pausing Liquid also interrupted transfers of other issued assets, although Liquid reported those assets were not directly affected by the exploit. Incident update
The Exploiter
A visible setup transaction appeared in Liquid block 4,050,335 at 13:52:10 UTC on September 6. The incident transaction followed one block later. SideSwap received the redemption order at 14:05, and Bitcoin confirmed the payout at 14:28:56. Setup, incident transaction, SideSwap
The main recipient and return sender was bc1ql4mfu6aundtkksxklfajs2h3t9nzcd6gyqjlte. Its controller's identity has not been established.
Remediation
Liquid disabled bridge nodes and reports that bridge-node patching completed on September 7. The public hardening, PR #1600, merged September 8. It length-prefixes verification inputs, strengthens related cache keys, and adds a range-proof-cache disable option. The historical tuples serialize differently under the fix. Patch
The elements-23.3.4 release followed on September 9 and was marked pre-release at the cutoff. A corrected ledger, resumed services, and reconciled BTC backing remained separate recovery steps. Release, recovery update
The engineering lesson is specific: a verification cache must preserve the complete verification context and its field boundaries. Its acceptance result must agree with full verification, regardless of cache history.
Bug Bounty Disclosure
The exploiters called themselves white hats. On September 9, an onchain message demanded a 10% bounty funded by Blockstream and threatened a 15% holder loss. No agreed bounty or final settlement was established at the cutoff; the retained BTC should not be described as an approved reward. Onchain demand