The Lie in the Sponge: Breaking Triton VM's STARKs
Jan 22, 2026·6 min read·2 visits
Executive Summary (TL;DR)
Triton VM, a Rust-based Zero-Knowledge Virtual Machine, failed to hash the `FriPolynomial` and `Log2PaddedHeight` into its Fiat-Shamir transcript. This broke the causality of the proof system, allowing an attacker to choose proof parameters *after* seeing the verifier's challenges, effectively allowing them to forge proofs for false statements. Additionally, a missing bounds check allowed for a trivial Denial of Service.
A critical soundness vulnerability in Triton VM's STARK proof system allowed malicious provers to forge proofs by exploiting a flaw in the Fiat-Shamir heuristic implementation. By failing to commit specific protocol elements to the transcript, the verifier could be tricked into accepting invalid state transitions.
The Hook: When Magic Loses Its Rules
Zero-Knowledge Proofs (ZKPs) and STARKs (Scalable Transparent Arguments of Knowledge) are often touted as the "moon math" that will save blockchain scaling. They rely on a delicate dance between a Prover (who claims to know something) and a Verifier (who checks the claim without learning the secret). In the interactive version of this dance, the Verifier throws random challenges at the Prover to catch them in a lie.
But we don't like interactive protocols on blockchains; they are slow. So we use the Fiat-Shamir heuristic. Instead of a human Verifier shouting random numbers, we use a cryptographic hash function (a "sponge") to generate challenges based on the conversation history (the transcript). It's like a cosmic commitment: "Based on everything I've said so far, the hash of my speech is X, so my challenge is X."
Here is the catch: For this to work, everything the Prover says must go into the sponge before the challenge is squeezed out. If you leave something out, you break causality. In triton-vm, the developers accidentally left a few crucial pages out of the history books. Specifically, they forgot to hash the FriPolynomial and the domain height before generating the next set of challenges. This is the cryptographic equivalent of letting a magician pick their card after you've already guessed it.
The Flaw: A Sponge With Holes
The vulnerability lies deep within the FRI (Fast Reed-Solomon Interactive Oracle Proofs of Proximity) sub-protocol. FRI is the heavy lifting in a STARK that proves a polynomial has a low degree. The security of this entire house of cards rests on the assumption that the Prover commits to a polynomial before the Verifier issues a challenge point.
In triton-vm, the verifier maintains a sponge state—a rolling hash of the transcript. When the Prover submits the FriPolynomial (the final polynomial in the FRI layers) and the Log2PaddedHeight (which dictates the Merkle tree height), these values should have been absorbed into the sponge. They weren't.
Because these values were ignored by the sponge, they didn't influence the subsequent randomness generation. A malicious prover could compute the challenges, observe the verifier's expected behavior, and then craft a FriPolynomial that perfectly satisfies the check for those specific challenges. It allows the prover to retroactively fit the data to the test, bypassing the proximity checks entirely. If you can bypass FRI, you can prove anything—1 equals 2, the sky is green, or that you own a million Bitcoin.
The Code: The Boolean That Killed Truth
The root cause wasn't a complex mathematical error; it was a configuration oversight in a Rust macro. In triton-vm/src/proof_item.rs, the proof_items! macro defines how different parts of the proof are handled. It takes a boolean flag indicating whether an item should be included in the Fiat-Shamir transcript.
Let's look at the vulnerable code. Notice the false flags next to the critical components:
// The Smoking Gun (Vulnerable Code)
proof_items! {
// ... other items ...
Log2PaddedHeight(u32) => false, try_into_log2_padded_height,
FriPolynomial(Polynomial<'static, XFieldElement>) => false, try_into_fri_polynomial,
}That false means "don't hash this." It effectively rendered these fields invisible to the challenge generator. The fix, applied in commit 3a045d636e97bb2eb628671db0001aa665c19dd8, was embarrassingly simple but critical:
// The Fix (Patched Code)
proof_items! {
// ... other items ...
Log2PaddedHeight(u32) => true, try_into_log2_padded_height,
FriPolynomial(Polynomial<'static, XFieldElement>) => true, try_into_fri_polynomial,
}By flipping that bit to true, the verifier now forces these items into the sponge state, ensuring that any change to the polynomial changes the subsequent challenges, restoring the security of the Fiat-Shamir transformation.
The Exploit: Forging Reality
To exploit this, we don't need to break SHA-256 or solve discrete logs. We just need to play games with order of operations. An attacker acting as a malicious Prover would start the STARK generation normally. However, when reaching the final FRI layer, they would pause.
Instead of committing to a valid low-degree polynomial, the attacker calculates what the Verifier's next challenge $\alpha$ will be without including the final polynomial in the hash (since the verifier code skips it). Knowing $\alpha$ in advance, the attacker constructs a polynomial $P(x)$ that satisfies the verifier's query at $\alpha$, even if $P(x)$ is actually high-degree or total garbage elsewhere.
Because the Verifier checks the proof by re-running the transcript and generating the same challenges, it generates the exact same $\alpha$ (unaffected by our malicious polynomial). It then checks our malicious polynomial against $\alpha$, sees that it matches, and stamps the proof as VALID. We have successfully forged a STARK for a computation that never happened.
Bonus DoS: While we are at it, the Log2PaddedHeight was also unchecked. We could send a Log2PaddedHeight of 64. The verifier, trying to be helpful, would attempt to allocate a domain size of $2^{64}$, instantly crashing the machine via memory exhaustion. Simple, crude, effective.
The Impact: Soundness Zero
The CVSS score for this is listed as 1.7 (Low) in the GitHub Advisory Database. This is a perfect example of why standard scoring systems fail for cryptography. In the context of a ZK-VM, a soundness break is Catastrophic. It's not "Low"; it's "Game Over."
If Triton VM were running a rollup or a bridge, this bug would allow an attacker to steal all funds, rewrite history, or censor transactions. The definition of a Proof System is that you cannot prove false statements. This bug negated that property entirely. The only saving grace is that this is a relatively niche library, but for anyone using it, the trust assumptions were completely voided.
Furthermore, the DoS vector means that even if you couldn't forge a proof (perhaps due to other constraints), you could trivially take down any public-facing verifier node with a single malformed packet.
The Fix: Upgrade and Version Bump
The remediation is straightforward: Update triton-vm to version 2.0.0. The developers didn't just patch the macro; they also incremented the protocol version constant CURRENT_VERSION from 0 to 1. This serves a dual purpose: it fixes the code and simultaneously invalidates all old proofs.
If you try to replay a proof generated by a vulnerable version against a patched verifier, it will fail immediately due to the version mismatch. This prevents replay attacks and ensures that the ecosystem migrates cleanly to the sound implementation.
For developers implementing their own STARK verifiers or experimenting with ZK: Let this be a lesson. The Fiat-Shamir heuristic is not magic dust you sprinkle on a protocol. It requires rigorous auditing of the transcript. If a variable affects the control flow, it must go into the sponge.
Official Patches
Fix Analysis (1)
Technical Appendix
CVSS:4.0/AV:N/AC:H/AT:N/PR:N/UI:N/VC:L/VI:L/VA:N/SC:N/SI:N/SA:N/E:UAffected Systems
Affected Versions Detail
| Product | Affected Versions | Fixed Version |
|---|---|---|
triton-vm TritonVM | < 2.0.0 | 2.0.0 |
| Attribute | Detail |
|---|---|
| CWE-358 | Improperly Implemented Security Check (Fiat-Shamir) |
| CWE-400 | Uncontrolled Resource Consumption (DoS) |
| CVSS v4.0 | AV:N/AC:H/AT:N/PR:N/UI:N/VC:L/VI:L/VA:N/SC:N/SI:N/SA:N/E:U |
| Language | Rust |
| Component | FRI Protocol / Fiat-Shamir Sponge |
| Impact | Soundness Break (Proof Forgery) |
MITRE ATT&CK Mapping
Improperly Implemented Security Check for Standard
Known Exploits & Detection
Vulnerability Timeline
Subscribe to updates
Get the latest CVE analysis reports delivered to your inbox.