The Free Variable
Four proof fields. No opening proof. Full circuit bypass.
Hey, it’s Arsen.
In today’s menu:
• How a newly churned node reconstructed a vault key and drained $10.7M
• The end of Code4rena
• Four proof fields no one verified — and why that breaks a $60M blockchain
• And more…
🏴☠️ Hacks
THORChain: $10.7M drained via TSS key reconstruction
You run a cross-chain DEX. Six vaults. Distributed key material across multiple nodes. No single node holds the full private key.
The design is sound. The implementation had a gap.
A new node churns in. Bonds RUNE. Joins a vault. Nothing unusual on paper.
How many signing rounds does it take to reconstruct a full private key from leaked fragments?
With a vulnerable GG20 implementation: however many it takes to stay patient. GG20 distributes the vault’s key material across participating nodes. But the vulnerability let partial material leak from each signing round. Over several days, the attacker accumulated enough fragments to reconstruct the full private key for one of the six Asgard vaults — then executed unauthorized outbound transactions.
The network caught it automatically. An anomaly detector flagged the activity and halted signing. Multiple node operators executed make pause. Individual user swaps were not affected — only protocol-owned liquidity was at risk.
The ETH addresses used to bond RUNE for the malicious node link directly to the addresses that received the stolen funds. Current evidence points to a single operator acting deliberately, not a random exploit.
Recovery options are still in discussion: slash affected node bonds, use Protocol-Owned Liquidity to absorb the loss, or a community-driven combination. No decision yet. Trading remains halted.
The attack surface wasn’t a missing access check or a reentrancy path. It was the cryptographic primitive securing every vault — and a node operator who knew exactly what to do with what it leaked.
🗞️ News
Code4rena is shutting down
Code4rena announced last week that it’s winding down. Short statement. No explanation. No acquirer named.
All active contests and bounties will run to completion. Projects mid-engagement won’t be dropped. Each competition wraps up on its own timeline, coordinated with sponsors.
Where do you build a public track record when the leaderboard you were climbing disappears?
C4 was the platform that made competitive auditing mainstream. Hundreds of contests. Millions in payouts. A leaderboard that gave researchers a way to go public — and a way for protocols to find them.
A lot of careers in Web3 security started with a first C4 finding. Five years of wardens, judges, and sponsors building something real.
Sherlock, Cantina, and Codehawks are still running. The competitive audit format survives. But the community that grew up specifically inside C4 is gone.
If you were mid-contest, finish it. If you were building toward C4, pick the next platform and keep going. The skill compounds the same either way.
The arena closed. The work doesn’t stop.
📚 Education
Unverified PLONK evaluations — OSEC’s dusk-plonk disclosure
OSEC disclosed a critical soundness bug in dusk-plonk — the PLONK implementation behind Dusk Network’s Phoenix privacy layer. At disclosure: ~$60M market cap. The entire shielded transaction layer was at risk.
Here’s the invariant every PLONK verifier must maintain: every scalar entering the final verification equation either comes with a KZG opening proof, or is computed locally from public data. The opening proof binds an evaluation to a polynomial the prover committed to before seeing the challenges. Without that binding, the scalar is a free variable — the prover sets it to whatever makes the equation balance.
What happens when four of those scalars have no opening proof?
Dusk’s proof struct included evaluations for four selector polynomials: q_arith, q_c, q_l, q_r. The verifier consumed all four in the arithmetic identity check. None were in the KZG opening batch. The commitments existed in the verifier key — but the verifier never checked the evaluations against them.
Four free variables.
The exploit reduces to a single field division. You commit to arbitrary witness and quotient polynomials. You follow the honest protocol through all commitment rounds. After seeing the challenge z, you compute what the equation needs. You solve for q_arith_eval. You set the other three to zero.
Valid proof. Arbitrary false statement.
For Dusk, this broke every Phoenix constraint simultaneously — Merkle membership, note ownership, balance integrity, nullifier correctness. OSEC built a local testnet PoC: wallet balance 0, forge a proof, mint 2000 DUSK, transfer 1337 DUSK to an honest wallet. The honest node accepted both transactions.
OSEC found the same class of bug in Espresso’s Jellyfish: nine of fifteen Plookup evaluations missing from the Fiat-Shamir transcript before the batching challenge was derived. Different implementation. Same missing invariant.
The audit check is mechanical. For every field in a proof’s evaluation struct: does it appear in the KZG opening batch, or does the verifier compute it locally? Neither case is exotic. Both are checkable from a diff.
Every field. Bound or locally derived. No exceptions.
That’s it for this week.
Reply with the Solana bug, tool, or pattern you want me to cover next — I read every one.
If a working Solana auditor in your circle would find this useful, forward it their way.
— Arsen, working Solana auditor




