Incident Summary
On 6 September 2026, the Liquid Network was exploited through a vulnerability in the Elements codebase used to validate Confidential Transactions. The issue stemmed from an ambiguous cache-key encoding in the rangeproof verification cache, allowing two different validation inputs to produce the same cached entry. The attacker first crafted setup transactions and primed the cache, then submitted a malicious inflation transaction that reused the cached validation result and created unauthorized L-BTC. Approximately 3,998.5 L-BTC, valued at $318.7 million at the time of the exploit, was affected, with the attacker subsequently pegging out the assets into native Bitcoin.

Background
Liquid is a Bitcoin sidechain built on Elements. BTC locked in the Liquid Federation’s Bitcoin wallet backs L-BTC issued on the sidechain. Under the intended peg model, one L-BTC represents one BTC held by the federation.
Elements supports “Confidential Transactions” and “Confidential Assets”. Instead of publishing an amount directly, an output can contain a Pedersen value commitment. An asset may also be represented by a commitment. The transaction’s commitment equation establishes that inputs and outputs balance algebraically, while a ‘rangeproof’ demonstrates that each hidden output value lies in an allowed nonnegative range.
In particular, rangeproof validation is part of ensuring that a confidential amount is both legitimate and cannot be abused to create value outside the permitted range. Actual validity also depends on: rangeproof + value commitment + asset commitment + scriptPubKey.
https://github.com/ElementsProject/elements/blob/elements-23.4.0rc3/src/script/sigcache.cpp?utm_source=chatgpt.com
The script is supplied to secp256k1_rangeproof_verify as additional committed data. A proof valid for one (C, X, S) tuple is not automatically valid for a different tuple.
This distinction is essential. Pedersen commitments operate algebraically and can balance a very large positive output against a negative value. The rangeproof is what prevents that negative value from being accepted as a legitimate output. Bypassing the rangeproof check therefore turns an otherwise balanced commitment equation into an inflation mechanism.
Key Transactions (Times in UTC)
Key Addresses
| Address | Label |
|---|---|
| ex1q7kgx4ptje7px48tn0nsmc6se5pngdp3smpqa2w | Attacker on Liquid |
| bc1qgslsydz56d0ed6827hdemfmk5w2f6ldyc6wt7p | Recipient of the 3,996.01834922 BTC federation payout |
| bc1ql4mfu6aundtkksxklfajs2h3t9nzcd6gyqjlte | Recipient of the 2.65138358 BTC federation payout |
| bc1ql4mfu6aundtkksxklfajs2h3t9nzcd6gyqjlte | Attacker consolidation and on-chain messaging address |
| bc1qdlld6antmv4xug242ed83q7k4rqw50cwfns38szx4qu2f4jwaxxsuhwxxr | Liquid Federation Bitcoin peg wallet |
Attack Flow
1. Forge the proof Cache
We fetched and parsed the raw Elements serialization rather than relying only on the explorer’s rendered fields. The :0 outputs of the two setup transactions are identical across their asset, value commitment, nonce, script, surjection proof, and rangeproof.
Using zero-based end-exclusive offsets in the 8,882-byte raw transaction to decode output_0 of transaction 271147100a94f6337b6c3db39b30c92d5b97ed91597307b6f721f73a15187ec5 returned the following:

a. C0 = 09d6c6150e95e576d288dcc890bf48518119c6f227ea1be6f9a932792dbee683f5 b.S0=6a|43|086f5d67160fc4b477954fb09ef321e5b589d7a07740a1a6df494ed2335b1d01d8|0a0a488de4899d0ae757f6cf8368663184d164106111ed9eaecf510e35282ddc6d|6a.
i.6a means OP_RETURN
ii.43 means push the following 67 bytes
The 67 pushed bytes are deliberately structured as: 33-byte C1, 33-byte X, 1-byte 6a
Therefore Script S0 = 6a43 || C1 || X || 6a
where X = 0a0a488de4899d0ae757f6cf8368663184d164106111ed9eaecf510e35282ddc6d is the result of secp256k1_generator_generate(L-BTC asset ID).
2. Prime the live mempool cache
Block connection uses cache semantics that consume an existing entry and do not store a new one on a miss. As a result, connecting block 4,050,335 should remove the cache entry produced when the two visible setup transactions were first accepted. The inflation transaction in block 4,050,336 therefore requires a fresh valid transaction containing the same (P0, C0, X, S0) tuple to reach each accepting node after block 4,050,335 was connected and before the invalid output was checked.
That live primer txid and raw bytes remain unknown; identifying it may require a functionary debug.log, a contemporaneous mempool.dat, a peer-relay capture, or an archived mempool snapshot.
3. Liquid side inflation
Decoding output_1 of inflation transaction f24a4b179b5cc7e88b25a763911f7cbdf2bf45d1d1b5ab611e94461cef0a183f returned the data below:
a. C1 = 086f5d67160fc4b477954fb09ef321e5b589d7a07740a1a6df494ed2335b1d01d8
This is exactly the first 33 bytes of data pushed by the setup output’s OP_RETURN script.
*Note that the rangeproof P1 is 68 bytes longer than P0, specifically at the end
09d6c6150e95e576d288dcc890bf48518119c6f227ea1be6f9a932792dbee683f5
b. 0a0a488de4899d0ae757f6cf8368663184d164106111ed9eaecf510e35282ddc6d6a43 which gives C0 from setup: 09d6c6150e95e576d288dcc890bf48518119c6f227ea1be6f9a932792dbee683f5 and X = 0a0a488de4899d0ae757f6cf8368663184d164106111ed9eaecf510e35282ddc6d is the result of secp256k1_generator_generate(L-BTC asset ID). Embedding the precise L-BTC generator allowed the setup script’s bytes to realign with the attack output’s asset-generator field.
c. Overrall
Valid setup: P0 || C0 || X || (6a43 || C1 || X || 6a)
Invalid attack: (P0 || C0 || X || 6a43) || C1 || X || 6a
Both reduce to the same 4,301-byte stream: P0 || C0 || X || 6a43 || C1 || X || 6a
Independent verification with rust-elements and secp256k1-zkp produced the following results:
The cache lookup occurs before commitment parsing and secp256k1_rangeproof_verify. A hit returns true immediately, so the invalid P1 is never cryptographically checked on a primed node

4. Cash out
L-BTC is redeemable against real BTC controlled by Liquid's federation. The next transaction, 3875a6d6…f1146b, spent the incident transaction’s output f24…:0. Two later transactions created explicit sendtomainchain peg-out outputs:
- 46f117c9…eb3d2: 2.65138358 L-BTC
- ce4caece…988f2: 3,996.01834922 L-BTC
Bitcoin transaction 8db751a6…7b140, confirmed in block 965,783, paid the two exact incident peg-out amounts:

Vulnerability
Elements caches successful rangeproof verifications to avoid repeating an expensive cryptographic operation. Two key constructions are relevant.
Earlier code derived the cache entry from only:
That design omitted the asset generator and script even though both affect proof validity. Commit c26d719 attempted to bind the cache entry to the full verification context. It changed the input to:
However, the implementation fed the raw fields into one SHA-256 stream without encoding their lengths. Both P and S are variable-length fields. Consequently, distinct tuples can produce the same byte stream by moving bytes across field boundaries. Patched cache implementation.
Valid primer: (P0, C0, X, 6a43 || C1 || X || 6a)
Invalid attack: (P0 || C0 || X || 6a43, C1, X, 6a)
The observed setup and attack outputs do not collide under the earlier P || C key. Their old-key inputs differ in both length and content. The construction specifically matches the four-field, undelimited key introduced by the patch. Acceptance therefore points to a c26d719-like implementation combined with a primed cache, although the exact binaries deployed on individual functionaries have not been published.
Fund Flow
The self-proclaimed white hat returned 3,400 BTC (268 million dollars) and kept 598.5 BTC (47 million dollars), 15% of the total, as a bounty reward. bc1ql4mfu6aundtkksxklfajs2h3t9nzcd6gyqjlte.

To keep up to date on the latest incident alerts and statistics, follow @certikalert on X, or read our latest analysis on certik.com.


