V12 found and developed a working PoC for a bug in how deposits are handled on Spark, the Bitcoin L2 built by @lightspark. This was done fully autonomously.
We responsibly disclosed it, and the Lightspark team confirmed it as valid. The bug has been fixed.
Here's what we found.
Background
Spark is a Bitcoin L2. Instead of moving coins on-chain for every payment, you deposit BTC into an output jointly controlled by you and the Spark Operators. This output is a 2-of-2 where you have one key and the other key is a threshold key held across the operator set. Transfers then happen off-chain by handing over key material and re-tweaking the operator share rather than broadcasting Bitcoin transactions, which is what makes them instant and feeless. The security model is 1-of-n: as long as a single operator is honest, and the users hold onto a pre-signed transaction that lets them leave, no one can take your funds. That withdrawal path ("unilateral exit") is the property these two bugs affect.
A single deposit doesn't map to a single spendable balance. To let one on-chain UTXO be split and merged into arbitrary off-chain amounts, Spark keeps a tree for each deposit. The tree subdivides the on-chain UTXO into smaller outputs. The root node corresponds to the on-chain deposit. Internal nodes correspond to unbroadcasted transactions that further subdivide it into smaller outputs. Leaf nodes are the actual things you hold and transfer. Every node has a set of pre-signed transactions. In the schema these are fields like raw_tx, raw_refund_tx, and the CPFP/direct refund variants. Unilateral exit is means walking from the root to your leaf and broadcasting the pre-signed transactions along the way. Thus the integrity of these trees is critical as they hold the transaction data used to force a withdrawal from the L2.
Bug: Cross-tree root overwrite in deposit tree creation
The legacy create handler checks for an already-existing root node so it can update it instead of inserting a duplicate (for idempotency). But it identifies "the existing root" by only four fields:
godb.TreeNode.Query().
Where(treenode.OwnerIdentityPubkey(d.OwnerIdentityPubkey)).
Where(treenode.OwnerSigningPubkey(d.OwnerSigningPubkey)).
Where(treenode.Value(uint64(out.Value))).
Where(treenode.Vout(int16(req.OnChainUtxo.Vout))).
ForUpdate().Only(ctx)
The problem is none of those four fields is unique per deposit. An attacker can collide two UTXOs to have the same root node. owner_signing_pubkey and value are user-supplied vout is ~always 0. So one identity can make two deposits whose tuples collide. Processing the second matches the first deposit's root and overwrites its raw_tx / refund, but the tree_node_tree edge is immutable. Now tree A is broken. Its root's presigned transactions were overwritten to spend B's outpoint, but the root still belongs to tree A. And tree B has no root.
Exploitation is a bit wacky as it involves an attacker bricking their own deposit and then spending a poisoned A on the L2 to an unsuspecting victim. So it's like being able to pay someone monopoly money to grief them (though you have to burn your own money in the process).
Conclusion
We thank the Lightspark team for their speedy response to our report.
V12 finds bugs in complex codebases like entire Layer 2s, autonomously, end to end. If you want to find bugs like this in your code, use V12.





![[*]
groom
[+] groom
staging 1024
reclaim key armed
reclaim values
[*] roles
[+] roles
[*]
prime
[+]
prime
[*] blocker
[*1 prime
[*] trigger
[+] blocker
[*] trigger
[+] trigger
wait](/bugs/media/baab0ce9a7ba1ea731ca47f6.png)





![1root@localhost:~/blah#./update_poc_offsets.sh
[*] finding cmd_logs_get_log...
[+] cmd_logs_get_log: 0x000000000047e735
[*] finding memmove@plt...
[+] memmove@plt: 0x0000000000341b](/bugs/media/bc28e8faa3c8052317c4a0f5.png)
![mint [Running] - Oracle VM VirtualBox
X
File
Machine
View Input
windows_victim (Snapshot 1) [Running] - Oracle VM VirtualBox
Devices
Help
V ^ x
AnyDesk
AnyDesk
X 1389376322
X 13893](/bugs/media/d083e95408e7b1442c48ecbc.jpg)




























