The One-Line Bug That Could Have Broken Solana's Privacy Layer
In June 2025, security researcher suneal_eth from zkSecurity reported a vulnerability to Solana's Anza team that reads like a cryptographer's nightmare: a single missing input to a hash function that would let an attacker forge zero-knowledge proofs, mint unlimited tokens, and drain any confidential balance on the network.
The bug lived in Solana's ZK ElGamal Proof program — the native on-chain verifier powering Token-2022's confidential transfer feature. It's the second critical ZK ElGamal bug reported on Solana, and it offers a masterclass in why getting the Fiat-Shamir transformation right is existentially important for any protocol using non-interactive zero-knowledge proofs.
Let's dissect exactly what went wrong.
Background: How Confidential Transfers Work on Solana
Solana's Token-2022 standard introduced confidential transfers — the ability to move tokens while keeping balances and amounts encrypted. Under the hood, this relies on two programs working together:
- Token-2022 program — handles mints, accounts, and application logic (upgradable)
- ZK ElGamal Proof program — a native program that verifies zero-knowledge proofs certifying that encrypted balances are valid
The encryption scheme uses ElGamal encryption, a well-studied public-key cryptosystem. When you transfer tokens confidentially, you produce ZK proofs that say: "I have enough balance, and the encrypted amounts are consistent" — without revealing the actual numbers.
The proofs are generated using sigma protocols (interactive ZK proof systems) converted to non-interactive proofs via the Fiat-Shamir transformation.
The Fiat-Shamir Transformation: A Quick Primer
Interactive zero-knowledge proofs work like a conversation:
- Prover sends a commitment
- Verifier sends a random challenge
- Prover responds, proving knowledge without revealing the secret
The Fiat-Shamir heuristic eliminates the verifier by replacing the random challenge with a hash of the transcript so far:
challenge = Hash(commitment || public_inputs || ...)
The critical invariant: every algebraic component that affects the proof's validity must be included in the hash. Miss one, and an attacker can manipulate the missing component freely — choosing values that make forged proofs verify.
This is exactly what happened.
The Bug: A Phantom Challenge in Sigma OR Proofs
The ZK ElGamal Proof program implements sigma OR proofs — a construction where the prover demonstrates knowledge of at least one of several secrets. In these proofs, the prover generates intermediate "challenge" values as part of the protocol.
Here's the critical error: one of these prover-generated challenge values was not absorbed into the Fiat-Shamir transcript hash.
In a correct implementation:
transcript.absorb(commitment_1)
transcript.absorb(commitment_2)
transcript.absorb(challenge_prover) // ← THIS WAS MISSING
fiat_shamir_challenge = transcript.squeeze()
Without including challenge_prover in the hash, an attacker could:
- Choose an arbitrary
challenge_provervalue - Work backward to construct commitments and responses that satisfy the verification equations
- Produce a proof that passes on-chain verification — for any statement, true or false
This is the "Phantom Challenge" — a value that exists in the proof but is invisible to the Fiat-Shamir hash, giving the attacker a free variable to exploit.
Impact: What an Attacker Could Have Done
With the ability to forge arbitrary ZK proofs, an attacker could have:
| Attack Vector | Description |
|---|---|
| Unlimited minting | Create forged proofs to mint arbitrary quantities of any Token-2022 confidential token |
| Balance draining | Forge proofs to withdraw from any confidential-enabled account |
| Fee manipulation | Manipulate encrypted fee amounts during confidential transfers |
The attack required no special permissions — just the ability to submit transactions with crafted proof data.
The Fix: One Line of Code
The remediation was surgically simple: add the missing challenge value to the Fiat-Shamir transcript.
// Before (vulnerable):
transcript.absorb(commitment_1);
transcript.absorb(commitment_2);
let challenge = transcript.squeeze();
// After (fixed):
transcript.absorb(commitment_1);
transcript.absorb(commitment_2);
transcript.absorb(challenge_prover); // ← Added
let challenge = transcript.squeeze();
But the response went far beyond a one-line patch:
- June 10, 2025: Vulnerability reported with proof of concept
- June 11: Token-2022 confidential transfers disabled via multisig upgrade
- June 13: Urgent validator upgrade request (Agave v2.2.16, Jito-Solana v2.2.16, Firedancer v0.505.20216)
- June 19: ZK ElGamal Proof program disabled entirely via feature activation at epoch 805
The team chose to disable the entire ZK ElGamal program rather than just patch the specific bug — because this was already the second ZK ElGamal vulnerability, and they wanted comprehensive re-auditing before re-enabling.
Why This Class of Bug Keeps Happening
The Phantom Challenge bug isn't unique to Solana. Fiat-Shamir implementation errors are one of the most common vulnerability classes in ZK systems:
1. Frozen Heart (2022)
Trail of Bits discovered that multiple ZK proof libraries (Plonky2, Spartan, Halo2) had "Frozen Heart" vulnerabilities — missing public inputs in Fiat-Shamir transcripts that allowed proof forgery.
2. Zcash Sapling Spend Vulnerability (2019)
A similar Fiat-Shamir binding issue could have allowed double-spending in Zcash's Sapling shielded transactions.
3. Bulletproofs Range Proof Forgery
Multiple Bulletproofs implementations were found vulnerable to forgery due to incomplete transcript binding.
The pattern is always the same: a value that should be bound to the transcript is left out, creating a degree of freedom the attacker exploits.
Lessons for Auditors and Developers
For ZK Protocol Developers
1. Enumerate every algebraic component in your proof system. Before writing code, create a formal specification listing every value that must enter the Fiat-Shamir transcript. Missing even one is catastrophic.
2. Use structured transcript APIs. Libraries like Merlin (Rust) or similar transcript abstractions enforce a discipline where you explicitly label and absorb each component. Don't roll your own hash-chaining.
3. Test with adversarial proof generation. Write tests that attempt to forge proofs by manipulating unhashed components. If your test suite only checks honest proofs, it won't catch soundness bugs.
4. Treat the second bug as a signal, not an anomaly. Solana's ZK ElGamal program had two critical bugs. When you find one Fiat-Shamir issue, systematically audit every sigma protocol and OR proof in the system.
For Auditors
1. Map the Fiat-Shamir transcript end-to-end. For every squeeze() or challenge derivation, verify that all preceding algebraic values have been absorb()ed. This is a mechanical check that catches the entire vulnerability class.
2. Check sigma OR proofs specifically. OR proofs are more complex than standard sigma protocols because the prover generates simulated challenges. These prover-chosen values are the most common omission.
3. Cross-reference the spec and implementation. Many ZK bugs arise from correct math in the paper but incorrect translation to code. Verify line-by-line.
The Broader Question: Is Solana's Confidential Transfer Ready?
Fortunately, actual usage of Token-2022 confidential transfers was minimal when the bug was found. Major stablecoins (PYUSD, AUSD, USDG) had the feature initialized but not activated for end users. No funds were lost.
But the incident raises questions:
- The ZK ElGamal program remains disabled on mainnet pending further audits
- This was the second critical ZK soundness bug in the same program
- The re-enablement timeline is measured in months, not weeks
For protocols considering building on Solana's confidential transfer feature: wait for the re-audit results. And when it does re-launch, expect it to have received significantly more scrutiny — which is ultimately a good thing.
Key Takeaways
- Fiat-Shamir transcript completeness is non-negotiable. Every algebraic value in the proof must be hashed. No exceptions.
- Sigma OR proofs are a known footgun. The prover-generated challenge values in OR compositions are repeatedly the source of soundness bugs across different projects.
- One-line fixes can address catastrophic vulnerabilities. But the response should be proportional to the risk — Solana's decision to disable the entire program for comprehensive re-auditing was the right call.
- ZK systems need dedicated cryptographic review. Standard smart contract auditing techniques don't catch these bugs. You need auditors who understand the mathematics of proof systems.
This analysis is part of our ongoing DeFi Security Research series. Follow for weekly deep dives into smart contract vulnerabilities, audit techniques, and security best practices across Solana and EVM ecosystems.
Top comments (0)