# A Valid Proof of a False Thing: How Zero-Knowledge Proofs Actually Break

> Zero-knowledge proofs rarely break at the math. They break in the circuit, the trusted setup, and the Fiat-Shamir transcript -- here is exactly how, and why.

*Published: 2026-07-20*
*Canonical: https://paragmali.com/blog/a-valid-proof-of-a-false-thing-how-zero-knowledge-proofs-act*
*© Parag Mali. All rights reserved.*

---
<TLDR>
Production zero-knowledge proofs almost never break at the cryptographic hardness. Across the entire documented incident record, the discrete-log, pairing, and hash assumptions held. The breaks live one layer up, in the artifact humans compile: **under-constrained circuits** (a missing constraint lets a prover forge a valid proof of a false statement -- the single most prevalent bug class), **trusted-setup subversion** (whoever keeps the setup trapdoor can forge any proof), and the **Frozen Heart** class (a weak Fiat-Shamir transcript that hashes too little). Name the layer -- circuit, setup string, or transcript -- and you have named the break. Even a STARK's transparent, hash-based construction only sheds one seam while inheriting the others, and "post-quantum secure" remains a conjecture, not a proof.
</TLDR>

## 1. A Valid Proof of a False Thing

A verifier checks a zero-knowledge proof and returns a single bit: `true`. Every pairing balances. Every hash matches. The discrete-logarithm assumption the whole scheme rests on was never in doubt. And yet the statement the proof certifies is false, the money it authorized has already left the vault, and no one broke any cryptography to make it happen.

That sentence should read like a contradiction. A proof is supposed to be a guarantee, and "verified" is supposed to mean "true." Dissolving the paradox is the entire project of this article, and the resolution is deceptively small: a proof is valid relative to *the constraints someone wrote down*, not relative to the truth they meant to capture.

<Definition term="Zero-knowledge argument">
A protocol that convinces a verifier a statement is true while revealing nothing beyond its truth. The word "argument" (as opposed to "proof") signals *computational* soundness: the guarantee holds against a prover with bounded computing power, which is the setting every deployed SNARK actually lives in.
</Definition>

If the constraints never force the prover to be honest about some value, then a proof that verifies is not a lie about the mathematics. It is an honest proof of the wrong statement. The verifier did its job perfectly. The specification it was checking simply was not the specification the designer had in mind.

> **Key idea:** A verifying proof means "the prover found some witness the circuit accepts," not "the statement is true." Those two readings coincide only when the circuit was written to force them together. Every break in this article is a place where they came apart.

To see how that gap opens, picture a zero-knowledge system as two stacked layers.

<Mermaid caption="The two-layer zero-knowledge stack: a hardness floor that never broke in the documented record, and the human-built artifact above it where every break actually lives.">
flowchart TD
    subgraph Artifact["Human-built artifact -- where every break lives"]
      C[Arithmetized circuit]
      S[Trusted-setup string]
      T[Fiat-Shamir transcript]
    end
    Artifact --> Floor
    subgraph Floor["Hardness floor -- unbroken so far"]
      D[Discrete logarithm]
      P[Elliptic-curve pairings]
      H[Collision-resistant hash]
    end
</Mermaid>

At the bottom sits a **hardness floor**: a small set of problems believed intractable, such as computing discrete logarithms, solving pairing relations, or finding hash collisions. At the top sits a **human-built artifact**: an arithmetic circuit that encodes the statement, a setup string that parameterizes the system, and a Fiat-Shamir transcript that turns an interactive exchange into a single non-interactive proof. Cryptographers design the floor over decades and attack it relentlessly. Engineers assemble the artifact per project, often under deadline.

Here is the thesis this article defends, drawn from the documented vulnerability record rather than from any theorem: **production zero-knowledge proofs almost never fail at the cryptographic hardness. They fail at soundness in the layer humans build** [@sok24]. The floor has held in every deployed break on record. The three seams above it -- the circuit, the setup string, and the transcript -- are where the leaks happen, and this article anatomizes each one.

One boundary before we start. This is a structural teardown: we are after the ways the artifact itself is mis-designed. The separate world of implementation faults -- timing side channels, faulty randomness, nonce leakage -- is real and dangerous, but it belongs to a sibling article, "How Zero-Knowledge Broke in Real Life." Here, every bug we examine would still be a bug if the code ran on perfect hardware with a perfect random-number generator.

To understand why the floor and the seams are even *separable* -- why you can lose soundness while discrete logarithm stands untouched -- we have to go back to 1985, to the paper that first pulled a proof apart into three independent pieces.

## 2. Where Soundness Was Born

Before 1985, a proof was a static object. You held a witness -- a satisfying assignment, a factorization, a certificate -- and you handed it over. The verifier re-checked it and was convinced. Soundness in that world was absolute and *fused* to the statement: a false claim simply has no accepting witness, so cheating is impossible by definition.

But that same fusion meant there was no vocabulary for a cheating prover. You could not ask *how much* a liar might get away with, or measure it apart from the underlying hard problem. There was no "soundness error" to reason about, because soundness was all-or-nothing and welded to truth.

Shafi Goldwasser, Silvio Micali, and Charles Rackoff broke that object into pieces. Their 1985 paper on the knowledge complexity of interactive proof systems replaced the static witness with an interactive protocol: a prover and a *randomized* verifier trade messages, the verifier issues fresh unpredictable challenges, and it accepts only probabilistically [@gmr85].

In doing so they separated a proof into three properties that had never been named apart before -- **completeness**, **soundness**, and **zero-knowledge** -- and, decisively, they made soundness a *quantity*. A cheating prover now succeeds with some probability, the soundness error, that you can drive toward zero but analyze on its own terms [@gmr85].

<Sidenote>A "proof" in the strict sense has soundness against a computationally *unbounded* cheating prover; an "argument" only against a bounded one. Almost every deployed SNARK is technically an argument. The distinction matters because argument soundness can rest on a hardness assumption, which is exactly the floor this article shows never cracks.</Sidenote>

That separation is the hinge of everything that follows. Because soundness is measured against *the statement as encoded*, not against discrete logarithm, a proof can be cryptographically valid and still certify a falsehood -- the encoded statement was wrong, even though the mathematics behind it was pristine. GMR gave the field the language to say that sentence.

Then came two conveniences, each of which made proofs deployable and each of which opened a seam. In 1986, Amos Fiat and Adi Shamir showed you could remove the live verifier entirely [@fs86].

<Definition term="Fiat-Shamir transform">
A method for making an interactive proof non-interactive by computing the verifier's random challenge as a hash of the conversation so far, rather than waiting for a human or a live party to supply it. The prover hashes its own commitment (and, done correctly, the statement) to derive the challenge, so anyone can recompute and check it offline.
</Definition>

The Fiat-Shamir transform is the universal glue beneath essentially every non-interactive proof and [Schnorr-style signature](/blog/the-discrete-log-held-the-proofs-leaked-a-field-guide-to-com/) in production -- and, as we will see, the exact step whose mis-implementation becomes the Frozen Heart class thirty-six years later. Two years after Fiat-Shamir, Manuel Blum, Paul Feldman, and Silvio Micali added the other convenience: a shared **common reference string** that lets a single message be a full zero-knowledge proof inside a security model rather than merely a heuristic [@bfm88]. Every later structured setup -- Groth16's reference string, the "powers of tau" ceremonies -- is a concrete descendant of that idea.

<Mermaid caption="The break lineage, 1985 to 2025. The same three seams -- transform, setup, arithmetization -- recur as the systems industrialize, while the hardness floor never appears in the record.">
flowchart LR
    A[1985 GMR: three properties] --> B[1986 Fiat-Shamir transform]
    B --> C[1988 BFM setup string]
    C --> D[2012 Helios weak Fiat-Shamir]
    D --> E[2016 Groth16 toxic waste]
    E --> F[2019 Zcash redundant CRS]
    F --> G[2022 Frozen Heart]
    G --> H[2025 practical Fiat-Shamir break]
</Mermaid>

Could you simply delete the setup seam by using no setup at all? In 1994, Oded Goldreich and Yair Oren proved you cannot: non-trivial non-interactive zero-knowledge in the plain model, with no setup whatsoever, collapses to the class BPP -- meaning it buys you nothing beyond ordinary randomized computation [@go94]. A setup of *some* kind is mandatory. This theorem is why a careful engineer says a transparent system has "no *trusted* setup" and never "no setup." The distinction is load-bearing, not pedantic, and it will decide how we read STARKs later.

<Sidenote>"Transparent" setup means the setup uses only public randomness that anyone can verify -- a public-coin setup -- so there is no secret trapdoor to leak. Goldreich and Oren [@go94] show you can downgrade a *trusted* setup to a public-coin one, but you can never reach *no* setup for a non-trivial statement.</Sidenote>

Three properties, three ways to fail. Before we watch each one break in production, we need the grid that tells us which failure we are looking at -- and, more to the point, which one steals money.

## 3. Three Ways to Fail, One That Pays

A zero-knowledge system can fail along exactly three axes, because GMR gave it exactly three properties [@gmr85]. Only one of those failures makes an attacker rich. Getting clear on which is which is the analytical spine of everything that follows, so it is worth building the grid deliberately.

<Definition term="Soundness">
The guarantee that a cheating prover cannot convince an honest verifier of a false statement, except with negligibly small probability -- the soundness error. Soundness is the property an attacker attacks: break it and you can prove things that are not true.
</Definition>

<Definition term="Completeness">
The guarantee that an honest prover holding a valid witness always convinces an honest verifier. A completeness failure rejects legitimate proofs. It denies service; it does not manufacture forgeries.
</Definition>

<Definition term="Zero-knowledge (the property)">
The guarantee that a proof reveals nothing beyond the truth of the statement, formalized by a simulator that produces a transcript indistinguishable from a real one without ever seeing the witness. A zero-knowledge failure leaks the secret. It does not let anyone prove a falsehood.
</Definition>

Line those three up against their consequences and the asymmetry is stark.

| Failure axis | What goes wrong | Consequence | Who attacks it |
|--------------|-----------------|-------------|----------------|
| Completeness | Honest prover gets rejected | Liveness / denial of service | Usually no one -- it is a self-inflicted bug |
| **Soundness** | **False statement accepted** | **Forgery, minting, theft** | **A malicious prover** |
| Zero-knowledge | Witness is exposed | Privacy loss, de-anonymization | A malicious verifier or observer |

Read the table as a threat model. A **completeness** failure is annoying but not lucrative: your honest users cannot get their valid proofs accepted, so the system grinds rather than bleeds. A **zero-knowledge** failure is a privacy catastrophe -- the secret the proof was meant to hide leaks out -- but it forges nothing; the attacker learns a witness, they do not mint one.

A **soundness** failure is the money axis. When a malicious prover convinces an honest verifier of something false, and that verifier is a smart contract that releases funds or mints tokens, the false statement becomes a withdrawal. This is why soundness is the axis attackers actually target, and the axis this article spends its time on.

The empirical record confirms the asymmetry rather than merely asserting it. The 2024 systematization of SNARK vulnerabilities examined 141 disclosed bugs and found that the overwhelming majority compromise soundness -- on the order of 95 of them -- while only a handful, around four, affect completeness [@sok24]. The disclosed corpus is, in effect, a ledger of soundness breaks. And these are not abstract failures.

<PullQuote>
"Several million dollars worth of cryptocurrency have been stolen due to underconstrained arithmetic circuits." -- the Picus/QED2 authors and the IACR announcement of their work [@picus23][@iacr20490]
</PullQuote>

Notice what that sentence does *not* say. It does not say the [discrete-logarithm assumption](/blog/the-log-was-never-the-weak-part-how-discrete-log-cryptograph/) fell, or that someone found a hash collision, or that a pairing was inverted. The theft came from circuits that were *under-constrained* -- a soundness failure in the arithmetization layer, the top of the stack, with the hardness floor entirely intact [@picus23]. That is the pattern in miniature, and we are about to see it three times over.

Every real break in the rest of this article lands on the soundness axis, and each one lives in a *different layer* of the artifact.

## 4. The Break Surface, Layer by Layer

The artifact humans build has three parts, and each one has failed in production. We will walk them from the top down: the **arithmetization** layer where a computation becomes equations, the **setup** layer where the system is parameterized, and the **transform** layer where an interactive proof is frozen into a non-interactive one. This is not a smooth ascent toward safety. It is the same small set of bugs reappearing as the systems industrialize, generation after generation.

> **Note:** The next three subsections each take one layer, explain the mechanism precisely, and end at the failure that motivates the next. Keep one structural fact in view throughout: the seams are *orthogonal*. Fixing the circuit does nothing for the setup string; removing the trapdoor does nothing for the transcript. That independence is the observation the whole article turns on, and we will name it explicitly once all three are on the table.

We start with the arithmetization layer, the one that breaks most often and most expensively [@sok24].

### 4a. The Circuit -- Under-Constrained Arithmetization

Return to the forged proof from the opening. We can now say exactly what happened to it. Before a statement can be proved, it has to be compiled into a form a proof system understands, and that compilation is done by hand.

<Definition term="Arithmetization (R1CS constraint system)">
The compilation of a computation into a system of equations over a finite field. The most common target is a rank-1 constraint system, or R1CS: a long list of quadratic constraints, each of the form "one linear combination of the signals, times another, equals a third." The signals are the inputs, outputs, and intermediate values; "proving the computation" means exhibiting a signal assignment that satisfies every constraint at once.
</Definition>

A circuit, then, is a hand-written compiler artifact: the programmer's mental model of a computation, flattened into equations. That compilation has a lineage -- the quadratic-span-program and Pinocchio constructions turned a constraint system into a form a pairing can check in constant time, which is what made general-purpose SNARKs practical [@ggpr13][@pinocchio]. And like any hand-written artifact, it can be missing a line. What happens when the human forgets one of the equations?

<Definition term="Under-constrained circuit">
A constraint system that admits more than one satisfying assignment for a signal the designer meant to pin down. Because several witnesses satisfy the same constraints, a prover can pick one the specification never intended and still produce a proof the verifier accepts.
</Definition>

That is the whole mechanism. A missing constraint means the system of equations has too much slack: the value the developer assumed was forced is actually free. A malicious prover fills that freedom with an out-of-spec value, satisfies every constraint that *is* present, and hands the verifier a proof that checks perfectly.

<Mermaid caption="The under-constrained mechanism: a single omitted constraint lets more than one witness satisfy the system, and the extra witness is a verifying proof of a false statement.">
flowchart TD
    A[Computation compiled to a constraint system] --> B[One constraint omitted]
    B --> C&#123;"More than one witness fits?"&#125;
    C -->|Yes| D[Prover supplies an out-of-spec value]
    C -->|No| E[Output uniquely determined and safe]
    D --> F[Verifier accepts a valid proof of a false statement]
</Mermaid>

The canonical toy version is a square. Suppose you want to prove "I know the secret value the designer had in mind, and its square is `x`." The obvious constraint is that the square of the witness equals `x`. But that single equation does not pin the witness: in a field, both a root and its negation square to the same value, so two different witnesses satisfy one constraint. Run it and watch both slip through.

<RunnableCode lang="js" title="One relation, two witnesses: an under-constrained square">{`
// A toy field-arithmetic "circuit" over the small prime p = 23.
// Intended statement: "I know the specific value y the designer meant,
// and its square is x." The circuit writes only ONE constraint:
//        x === (y * y) mod p
// It never pins WHICH square root y is -- so the value is under-determined.
const p = 23;
const mod = (a) => ((a % p) + p) % p;

function circuitAccepts(x, y) {
  return mod(y * y) === mod(x);   // the single constraint actually enforced
}

const x = 4;            // the public output the verifier sees
const intended = 2;     // the witness the designer assumed
const forged = p - 2;   // 21: a DIFFERENT witness, same constraint

console.log("intended y =", intended, "accepted:", circuitAccepts(x, intended));
console.log("forged   y =", forged,   "accepted:", circuitAccepts(x, forged));
// Both print true. A proof that "verifies" has not pinned the value the
// specification intended -- it only certifies that SOME accepted witness exists.
`}</RunnableCode>

In a toy square the extra witness is harmless. In a withdrawal circuit that was supposed to enforce "this note has not already been spent" or "this balance is non-negative," the extra witness is a second spend or a negative amount that verifies as valid -- a mint or a theft, produced without touching the cryptography underneath.

> **Note:** A verifying proof certifies that the prover satisfied the *circuit*, not that they satisfied your *specification*. If a constraint you assumed is missing, the verifier will accept out-of-spec witnesses all day. "Valid" is a claim about the equations on the page, never about your intentions.

This is the payoff of the first aha moment. The naive reading -- "a verifying proof means the statement is true" -- fails because a verifying proof only means the prover found *some* witness the circuit accepts. When the circuit is under-constrained, that witness can be an out-of-spec lie, and the discrete-logarithm assumption is fully intact the entire time.

The archetypal way this bug enters real code is a one-character slip.

<Sidenote>In Circom, the most widely used circuit language, `<--` assigns a value to a signal but adds no constraint, while `<==` assigns *and* constrains. Writing `<--` where you meant `<==` leaves the signal free -- the exact shape of an under-constrained bug. Trail of Bits built the Circomspect linter specifically to flag that slip and related unconstrained-signal patterns [@circomspect_gh][@circomspect_blog].</Sidenote>

The field arithmetic hides further traps: values that alias modulo the field prime, and "hint" signals computed outside the circuit that are trusted without an in-circuit check. Each is a different way to leave a signal under-determined, and cataloguing them all is a project of its own. The structural point is singular: every one of these is a missing constraint, and a missing constraint is a soundness bug in the layer you wrote.

How prevalent is it? The 2024 systematization is blunt: across 141 disclosed SNARK vulnerabilities, under-constrained circuits are the single most prevalent class [@sok24]. This is not an exotic failure that shows up once a decade. It is the modal bug of the entire field, which is why it gets the most tooling -- and why we can catch it if we ask the right question.

<Spoiler kind="hint" label="How would you catch this before an attacker does?">
The property you actually want is *determinism*: every output signal should be uniquely determined by the inputs. You can test it mechanically -- ask a solver whether two different witnesses can share the same inputs yet differ on an output. If the answer is yes, the circuit is under-constrained. That is exactly what the Picus/QED2 tool automates [@picus23], and Section 10 turns it into a concrete review step.
</Spoiler>

A missing constraint is a bug in a layer *you* wrote and can, in principle, read. But what if the flaw is baked into a string you were *handed* -- one you cannot even inspect, generated in a ritual you had to trust?

### 4b. The Reference String -- Trusted-Setup Subversion and Toxic Waste

Some of the most efficient proof systems need a one-time setup that samples secret numbers, uses them to build a public parameter string, and then -- supposedly -- destroys them. The whole security argument rests on that destruction actually happening. What if someone keeps a copy?

<Definition term="Structured reference string (and toxic waste)">
A structured reference string (SRS) is a setup string whose elements carry algebraic structure: they are group encodings of secret scalars sampled once at setup time. Only the encodings are published. The secret scalars themselves are the "toxic waste." Anyone who retains them holds a trapdoor that can forge a proof of *any* statement, because the trapdoor lets them satisfy the verification equation without a real witness.
</Definition>

This is the setup layer's first and cleanest failure mode, and Groth16 is its reference example. Jens Groth's 2016 construction produces the smallest proof among deployed pairing-based SNARKs -- three group elements checked by a single pairing-product equation [@groth16] -- and that compactness is exactly what makes it attractive for gas-sensitive on-chain verification. But its soundness depends on an *operational* assumption, not a cryptographic one: it assumes the toxic waste was destroyed [@groth16]. If it was not, the holder forges at will, and the pairing assumption underneath never budges.

<Mermaid caption="The toxic-waste failure mode of a trusted setup. Soundness hinges on whether the secret scalars were actually destroyed -- an operational assumption, not a cryptographic one.">
flowchart TD
    A[Setup samples secret scalars] --> B[Publish only their group encodings as the SRS]
    B --> C&#123;"Trapdoor destroyed?"&#125;
    C -->|Yes, via ceremony| D[No one can forge, system is sound]
    C -->|No, someone kept it| E[Holder forges any statement at will]
</Mermaid>

This is *why the ceremony matters*. To avoid trusting a single party to destroy the trapdoor, the setup is run as a multi-party computation: each participant folds in fresh secret randomness and destroys their own share, and the forgery-enabling trapdoor is the product of all shares. It is unrecoverable unless *every* participant colludes, which flips the requirement into a reassuring form -- the setup is secure if *at least one* of the participants was honest [@eth_kzg_blog].

<Sidenote>These are the "powers of tau" ceremonies, named for the secret scalar tau whose successive powers structure the string. The security property is the "1-of-N honest" assumption: among N contributors, a single honest one who truly destroyed their randomness makes the trapdoor unrecoverable [@eth_kzg_blog].</Sidenote>

The modern scale of this is remarkable. Ethereum's KZG Summoning Ceremony ran for 208 days and collected 141,416 contributions, and its security rests on needing only a single honest participant among them [@eth_kzg_blog]. That turns an unavoidable trusted setup into a broadly auditable social process. The residual cost is real, though: the guarantee is social, never provable, and for Groth16 the ceremony is *per-circuit* -- every new program needs its own [@eth_kzg_blog].

Now the accuracy-critical part. There is a *second*, entirely different setup-layer failure that is routinely conflated with the first, and conflating them gets the physics wrong.

<Aside label="Two different setup failures">
**Mode 1 -- toxic-waste retention.** Someone keeps the secret scalars a correct setup was supposed to destroy, and uses that trapdoor to forge. The construction is fine; the secret leaked. The ceremony is the defense.

**Mode 2 -- a construction soundness bug.** The setup string itself is built wrong -- it contains elements that should not be there -- so a prover can forge *even though no secret was ever retained*. No ceremony helps, because nothing leaked. The construction is the defect.
</Aside>

Mode 2 is what actually happened to Zcash. The SNARK it deployed, BCTV14, carried redundant elements in its reference string that the earlier Pinocchio template had deliberately excluded [@gabizon19][@bctv14]. Ariel Gabizon showed those redundant elements let a prover convert a valid proof of one statement into a valid-looking proof of a *different* statement -- undetectable, unlimited counterfeiting, with the hardness assumption completely intact [@gabizon19].

<PullQuote>
The flaw let an attacker "transform the proof of one statement into an ostensibly valid proof of a different statement, thereby breaking ... soundness." -- the official record of CVE-2019-7167 [@cve19]
</PullQuote>

The timeline is the quietly alarming part, and it hides two windows that are easy to conflate. The Electric Coin Company's postmortem records that the flaw was discovered on 1 March 2018, silently remediated by the Sapling network upgrade that activated on 28 October 2018, and only disclosed on 5 February 2019 [@ecc19].

Those two windows are very different lengths. The *exploitable* window was the long one: the flaw was present from Zcash's 2016 launch until the Sapling fix, roughly two years in which the currency was silently, undetectably counterfeitable. The *secrecy* window was only the eleven months from private discovery to public disclosure. Across both, no observer could have told from the chain, because a forged proof under this bug looks exactly like an honest one [@ecc19]. Groth16's minimal reference string removed this entire class by construction, because it contains only the elements the proof actually needs [@groth16].

> **Note:** The Zcash counterfeiting flaw was *not* a leaked secret and *not* a failed ceremony. It was a soundness bug in how the reference string was built [@gabizon19][@cve19]. Filing it under "toxic waste" hides the lesson: even a setup whose secrets were perfectly destroyed can be forgeable if the string is structured wrong.

Both setup failures, though, share one escape hatch: they vanish entirely if you have no trusted setup at all. Draw every parameter from public randomness and there is no trapdoor to keep and no structured string to get wrong. Bulletproofs does exactly that -- and Bulletproofs was still forged. The trapdoor, it turns out, was never the only seam.

### 4c. The Transcript -- The Frozen Heart

The Fiat-Shamir transform is the glue that makes proofs non-interactive, and its soundness hangs on a single question: what goes into the hash? Get that wrong and the whole non-interactive proof becomes forgeable, no matter how strong the hash function is.

<Definition term="Strong versus weak Fiat-Shamir">
Weak Fiat-Shamir derives the challenge from the prover's commitment alone -- the challenge is a hash of the commitment. Strong Fiat-Shamir binds the challenge to everything that should matter: a domain separator, the statement, all public inputs, and the full transcript. The entire difference is *what enters the hash*, and it is the difference between a sound proof and a forgeable one.
</Definition>

Here is why the omission is fatal. In a three-move protocol, a simulator that is handed the challenge in advance can fabricate a convincing commitment-and-response pair with no witness at all. That capability is a feature -- it is precisely what makes the protocol zero-knowledge.

Weak Fiat-Shamir accidentally hands the *attacker* the same capability. If the challenge is a hash of the commitment only, a malicious prover can work backwards: choose values so the verification relation holds, for a statement that is false. Because the statement and its public inputs never entered the hash, the transcript no longer pins down *which* statement is being proved. What the attacker can reach is protocol-dependent: a random or attacker-derived statement in the general case, and in systems like the Bulletproofs range proof a *chosen* out-of-range value -- the case that turns the flaw into forged money.

<Mermaid caption="Weak versus strong Fiat-Shamir. When the challenge omits the statement, the attacker can fix the challenge first and back-fill the rest; binding the statement into the hash removes that freedom.">
flowchart TD
    subgraph Weak["Weak Fiat-Shamir"]
      W1[Challenge hashes the commitment only] --> W2[Attacker fixes the challenge first]
      W2 --> W3[Back-fills a bogus commitment and response]
      W3 --> W4[Forged proof verifies]
    end
    subgraph Strong["Strong Fiat-Shamir"]
      S1[Challenge hashes statement, inputs, transcript] --> S2[Challenge pinned to the statement]
      S2 --> S3[No freedom to back-fill]
      S3 --> S4[Forgery blocked]
    end
</Mermaid>

The tell is that the challenge is *identical* for two different statements under weak Fiat-Shamir, so a transcript built for one carries over to another. Watch it happen.

<RunnableCode lang="js" title="Weak Fiat-Shamir: the challenge that forgets the statement">{`
// A stand-in hash (FNV-1a). Not cryptographic -- illustrative only.
function H(s) {
  let h = 2166136261 >>> 0;
  for (const ch of String(s)) {
    h = Math.imul(h ^ ch.charCodeAt(0), 16777619) >>> 0;
  }
  return h >>> 0;
}

// Two DIFFERENT statements a prover might try to prove.
const statementA = "pubkeyA-owns-100";
const statementB = "pubkeyB-owns-100";
const commitment = "prover-first-message";

// WEAK Fiat-Shamir: the challenge ignores the statement entirely.
const weakA = H(commitment);
const weakB = H(commitment);
console.log("weak FS   -- same challenge for A and B?", weakA === weakB); // true

// STRONG Fiat-Shamir: the challenge binds statement + inputs + transcript.
const strongA = H(statementA + "|" + commitment);
const strongB = H(statementB + "|" + commitment);
console.log("strong FS -- same challenge for A and B?", strongA === strongB); // false
`}</RunnableCode>

Under weak Fiat-Shamir the challenge is the same for statement A and statement B, so any freedom the prover has to satisfy the relation for that one challenge value is not tied to a particular statement -- they can retarget it to a false claim. Strong Fiat-Shamir makes the challenge a commitment to the statement itself, so that freedom is pinned and the retargeting fails.

This is not a hypothetical, and it is not a one-off. It is the same bug across three eras. In 2012, David Bernhard, Olivier Pereira, and Bogdan Warinschi named the weak-versus-strong distinction and used it to break Helios, a real electronic-voting system that hashed the commitment but not the statement [@bpw12]. A decade later, in April 2022, Trail of Bits found the identical defect living in modern proof-system implementations of Girault's scheme, Bulletproofs, and PlonK, and gave the class a name [@tob_fh].

<PullQuote>
"We've dubbed this class of vulnerabilities Frozen Heart. The word frozen is an acronym for FoRging Of ZEro kNowledge proofs." -- Trail of Bits, coordinated disclosure, April 2022 [@tob_fh]
</PullQuote>

The disclosure carried the identifier CVE-2022-29566, describing a flawed Fiat-Shamir implementation that does not include all public values in the challenge [@cve22]. The next year, Quang Dao, Jim Miller, Opal Wright, and Paul Grubbs turned the disclosure into a measured corpus: their peer-reviewed study found 36 weak-Fiat-Shamir instances across 12 different proof systems, with concrete knowledge-soundness attacks on Bulletproofs, PlonK, Spartan, and a verifiable-delay function -- including a case that enabled unlimited currency creation in a vulnerable protocol [@dao23].

<Sidenote>The 2022 disclosure reached implementations across many libraries -- iden3's snarkjs, ConsenSys's gnark, Dusk Network's PLonK library, and ZenGo's zk-paillier among them -- all remediated before publication [@tob_fh].</Sidenote>

Now the sentence that carries the thesis. Bulletproofs has *no trusted setup* -- it is transparent, drawing all its randomness from public coins [@stanford_bp]. And it was *still* a Frozen Heart victim: Trail of Bits traced the bug to an insecure Fiat-Shamir recommendation in the original Bulletproofs paper itself, which the authors then corrected [@tob_fh][@bulletproofs]. Removing the trapdoor did nothing to protect the transcript, because the two are different seams. The hash was perfectly collision-resistant the entire time. The bug was never in the hash -- it was in what entered it.

Three layers, three breaks, one striking regularity: not once did the discrete-logarithm, pairing, or hash assumption itself fail. That regularity is not a coincidence. It is a lens.

## 5. Name the Layer, Name the Break

Step back from the three incidents and look at the shape they make together. Each one attacked a different part of the artifact -- the circuit, the setup string, the transcript -- and each one lived on the soundness axis. In not one of them did the hardness floor carry the failure. That is not three unrelated stories. It is one lens.

> **Key idea:** Name the layer -- circuit, setup string, or transcript -- and you have named the break. Across the documented incident record, the hardness floor never carries the failure; the leak is always in the artifact humans compiled on top of it.

The lens works because the seams are *orthogonal*. They live in different parts of the artifact, so closing one does nothing to the others. This is the second aha moment, and it corrects a comfortable intuition. It is tempting to believe that transparent, no-trusted-setup systems are fundamentally safer -- remove the trapdoor and you remove the risk.

But Bulletproofs had no trusted setup and was still forgeable through weak Fiat-Shamir [@tob_fh][@stanford_bp]. Removing the setup seam left the transform seam exactly where it was, because the two are independent coordinates. "Name the layer" fully determines "name the break" only because the layers do not bleed into one another.

<Mermaid caption="The sorting lens as a decision tree. Given a verifying proof of a false statement, route it to a layer -- and note that the hardness floor branch has never been taken in the documented record.">
flowchart TD
    A[A verifying proof of a false statement] --> B&#123;"Which layer leaked?"&#125;
    B -->|Circuit| C[Under-constrained: a missing constraint]
    B -->|Setup string| D[Trapdoor retained, or a redundant CRS]
    B -->|Transcript| E[Weak Fiat-Shamir, the Frozen Heart]
    B -->|Hardness floor| F[Never observed in the record]
</Mermaid>

Laid out as a table, the whole incident record fits on a single grid.

| Seam | Layer | Mechanism | Canonical incident | Caught by |
|------|-------|-----------|--------------------|-----------|
| Under-constrained circuit | Arithmetization | Missing constraint admits multiple witnesses | Rollup and mixer withdrawal bugs [@sok24] | Picus, ZKAP, Circomspect |
| Toxic-waste retention | Setup string | Retained trapdoor forges any statement | Groth16 setup, mitigated by ceremony [@groth16] | MPC ceremony, transparency |
| Redundant reference string | Setup string | Malformed string lets a statement be swapped | Zcash BCTV14 [@cve19] | Minimal SRS, audit |
| Frozen Heart | Transform | Weak Fiat-Shamir omits the statement | Girault, Bulletproofs, PlonK [@cve22] | Strong-Fiat-Shamir transcripts |

The middle two rows are both the setup seam, split into its two distinct modes. Read down the "Caught by" column and you can already see the shape of the defense: one family of tools per layer, which is exactly how the state of the art is organized.

Complete the picture along the other axis. Nearly every entry in the disclosed corpus is a soundness break, and each maps to exactly one layer of the artifact [@sok24]. The diagonal that matters -- soundness, in the artifact, never the floor -- is the article's thesis rendered as a coordinate grid.

<PullQuote>
The seams are orthogonal: closing one does nothing to the others.
</PullQuote>

One honest caveat keeps this from becoming triumphalism. The claim is empirical, not a theorem: the hardness floor has held in every documented incident, which means unbroken *so far*, not *unbreakable*. There is exactly one place where the floor itself is on the clock -- a large quantum computer running Shor's algorithm against the discrete-logarithm and pairing assumptions -- and we will give that its due in Section 8. For now, the record is unambiguous: the seams leaked; the floor did not.

If every break has a home layer, then every *defense* has a target layer too. So how far has the field gotten at sewing each seam shut?

## 6. Sewing the Seams Shut

There is no single "secure zero-knowledge" method, and looking for one misreads the problem. What exists instead is a defense-in-depth stack with one family of tools per seam -- a structure that mirrors the failure taxonomy exactly, because the defenses were built in response to it [@sok24].

**The arithmetization seam** gets the most-developed tooling, because it is the most-exploited layer, and the tools sit on a spectrum that trades soundness against scale.

At the sound end is Picus, which implements the QED2 algorithm. It formalizes the exact property a missing constraint violates -- that every output signal must be uniquely determined by the inputs -- and then asks a solver the dual question directly: does a second satisfying assignment exist with the same inputs but different outputs? [@picus23] If the solver proves none can, the circuit is deterministic; if it finds one, it returns a concrete pair of witnesses that demonstrates the bug [@picus_gh].

The catch is that deciding uniqueness over nonlinear field constraints has no efficient general algorithm, so Picus is *sound but incomplete*: a clean verdict is trustworthy, but it can time out rather than decide. On a benchmark of 163 Circom circuits it resolved roughly 70 percent [@picus23].

At the scalable end is ZKAP, which builds a Circuit Dependence Graph and expresses nine vulnerability anti-patterns as queries over it [@zkap24]. It is a bug-finder, not a prover, so it can miss bug shapes nobody encoded -- but it scans large codebases cheaply, and in evaluation it found 81 real vulnerabilities across 258 Circom circuits, several previously unknown and later fixed by developers [@zkap24]. Fastest of all is Circomspect, the Trail of Bits linter that flags the `<--` versus `<==` slip and other unconstrained-signal patterns at authoring time [@circomspect_gh][@circomspect_blog].

| Tool | Approach | Guarantee | Scaling | Output |
|------|----------|-----------|---------|--------|
| Picus / QED2 | SMT proof of output uniqueness | **Sound**, incomplete | Weakest (solver cost) | Determinism proof or exploit witness pair |
| ZKAP | Anti-pattern queries over a dependence graph | Bug-finder; not sound | Strong | Ranked vulnerability findings |
| Circomspect | Multi-pass linter | Heuristic warnings | Strongest (near-linear) | Warnings (stdout or SARIF) |

> **Note:** Run Circomspect in CI to catch the `<--` and `<==` slips as they are written, ZKAP for corpus-scale triage, and reserve Picus to *prove* your critical circuits deterministic. No single tool is both sound and scalable, so the practical answer is to stack them.

**The setup seam** offers a three-way choice rather than a single fix: reduce the trust, amortize it, or remove it. Reduce it with an MPC ceremony, whose modern exemplar is Ethereum's KZG Summoning Ceremony -- 141,416 contributions over 208 days, secure if any one contributor was honest [@eth_kzg_blog][@eth_ceremony]. Amortize it with a universal, updatable reference string as in PlonK, where one ceremony serves every circuit up to a size bound and new contributors can inject entropy at any time [@plonk]. Or remove the trapdoor outright with a transparent construction.

<Definition term="Transparent setup (public-coin)">
A setup that draws only on public randomness -- public coins -- that anyone can reproduce and verify. There is no secret to retain, so there is no trapdoor to leak. By the Goldreich-Oren result it is still a setup; it is simply not a *trusted* one.
</Definition>

Bulletproofs and STARKs are the transparent options: no toxic waste, no ceremony, and no BCTV14-style reference string to get wrong [@bulletproofs][@stark18]. That closes the setup seam completely, at the cost of larger proofs or heavier verification, and -- as we keep seeing -- it does nothing at all for the transform seam.

**The transform seam** is closed by refusing to hand-roll transcripts. Strong-Fiat-Shamir libraries make weak Fiat-Shamir the non-default.

<Sidenote>Merlin, built on the STROBE framework, folds every value into a running transcript through a labeled `append_message` call and derives challenges from it, so forgetting to bind a public input becomes a visible omission in the code rather than a silent default [@merlin_gh][@merlin_site].</Sidenote>

Used correctly, a strong-transcript library eliminates the entire *avoidable* Frozen Heart class -- every instance that came from an engineer omitting a value from the hash [@merlin_gh]. The honesty this section owes the reader is in the word "avoidable." No tool here is both sound and scalable; a transcript library can still be misused; a hand-rolled challenge gets no protection at all; and, as Section 8 will show, there is a theoretical ceiling above even a perfectly bound transcript. The defenses shrink the attack surface layer by layer. They do not abolish it.

These defenses all assume you have already chosen a proof system. But that choice is upstream of everything here -- it decides which seams you inherit before you write a line of circuit. SNARK or STARK: what does each buy, and what does each still owe?

## 7. SNARK or STARK: Which Seams You Inherit

The usual comparison of proof systems asks which is fastest or smallest. The thesis reframes the question: **which seam does each construction avoid, and which does it inherit?** Read that way, the four most-deployed families tell a single clean story.

<MarginNote>Groth16's proof is small enough -- around 128 to 192 bytes -- to post on-chain cheaply [@groth16], which is why it stays attractive despite needing a fresh ceremony per circuit.</MarginNote>

Groth16 is the smallest and fastest to verify: three group elements, a constant-time pairing check [@groth16]. It inherits the setup seam in its purest form -- per-circuit toxic waste -- and it inherits the transform seam like everything else.

PlonK keeps the succinct proof but *relocates* the setup seam: one universal, updatable reference string for all circuits instead of one per circuit [@plonk]. It relocates the trapdoor; it does not remove it, and it was itself a Frozen Heart victim [@tob_fh]. Bulletproofs goes further and *avoids* the setup seam entirely by being transparent [@bulletproofs] -- and was *also* a Frozen Heart victim [@tob_fh]. STARKs avoid the setup seam too, and additionally trade the algebraic hardness assumption for a hash-based one.

<Definition term="STARK and FRI">
A STARK is a scalable, transparent proof system whose security rests only on collision-resistant hashes. It commits to data with Merkle trees and uses FRI -- the Fast Reed-Solomon Interactive Oracle Proof of Proximity -- to test that a committed function is close to a low-degree polynomial. With no algebraic trapdoor it needs no trusted setup, and by dropping discrete-log and pairing assumptions it sheds the structure that Shor's algorithm targets.
</Definition>

Put them on one grid and read the columns, not the rows.

| Dimension | Groth16 (2016) | PlonK (2019) | Bulletproofs (2018) | STARK / FRI (2018) |
|-----------|----------------|--------------|---------------------|--------------------|
| Proof size | 3 elements, ~128 to 192 B | ~constant, few hundred B | $2\log_2 n + 9$ elements | polylog, tens to hundreds of KB |
| Verify cost | ~3 pairings (constant) | ~constant | $O(n)$, batchable | polylog |
| Setup seam | **Inherited** (per-circuit toxic waste) | **Relocated** (universal trapdoor) | **Avoided** (transparent) | **Avoided** (transparent) |
| Transform seam | Inherited | Inherited (Frozen Heart) | Inherited (Frozen Heart) | Inherited |
| Hardness floor | Pairings (Shor-breakable) | Pairings + KZG (Shor-breakable) | Discrete log (Shor-breakable) | Hashes (plausibly PQ) |
| Post-quantum | No | No | No | **Conjectural** |

Move left to right along the setup row and the seam closes: inherited, relocated, avoided, avoided [@groth16][@plonk][@bulletproofs][@stark18]. Now read the transform row: inherited, inherited, inherited, inherited -- all the way across, transparent systems included. That one unbroken column is the entire argument in miniature. Closing the trapdoor does nothing for weak Fiat-Shamir, because they are different seams in the same artifact. And the hardness-floor row never once carries the break; the only future risk it names is the Shor entry, ticking for every algebraic system on the left.

Two disciplined caveats keep this table honest.

> **Note:** For STARKs, "post-quantum secure" is a *conjecture*. Security rests on the conjectured collision resistance of a hash, which no one has proved survives a quantum adversary, and Grover's algorithm forces larger hash parameters to hold the same margin [@stark18]. Write "plausibly post-quantum," never "post-quantum secure" as if it were settled.

The second caveat is about the word "no." Transparent cores are often wrapped for deployment.

<Sidenote>Production STARK pipelines frequently wrap a final Groth16 proof around the STARK so the on-chain verifier checks only a tiny constant-size proof, exactly as RISC Zero's zkVM does when it compresses its final succinct STARK receipt into an on-chain Groth16 receipt [@risc0recursion]. That last mile re-introduces a trusted setup at the recursion boundary. So "no trusted setup" is true of the STARK *core*, not necessarily of the whole *pipeline* -- a re-introduced surface by construction, not a documented exploited break.</Sidenote>

<Aside label="Same person, both sides">
Ariel Gabizon sits on both sides of this story. He analyzed the BCTV14 redundant-reference-string counterfeiting bug that threatened Zcash [@gabizon19], and he co-authored PlonK, one of the constructions that relocated the setup seam [@plonk]. The expert who found a break also built a defense -- this is a field that advances by adversarial pressure, not a morality tale with heroes and villains.
</Aside>

How each of these systems is actually built -- the QAP compilation, the FRI protocol, the polynomial commitments -- is the subject of the companion article "[Never Decrypted: Proving You Ran the Computation](/blog/never-decrypted-proving-you-ran-the-computation-without-reve/)." Here we only need enough to see where each one leaks.

Transparency, strong transcripts, sound circuits: with enough care, can we close every seam? The theorems say no -- and they say precisely how far "careful" is even allowed to go.

## 8. What Being Careful Cannot Fix

Some of the failures in this article are not sloppiness. They are pinned by theorems that tell you how careful you are even *allowed* to be. Four bounds define the ceiling.

**Soundness error is never exactly zero.** In the interactive-proof model, a cheating prover convinces the verifier of a false statement with some probability, and you can shrink that probability but never eliminate it for a non-trivial statement [@gmr85].

> **Note:** There is no non-trivial proof system with zero soundness error. You drive the error down toward $2^{-k}$ by enlarging the challenge space or repeating rounds, but never to exactly zero [@gmr85]. "Sound" is always shorthand for "sound except with negligible probability."

**Setup is necessary.** Goldreich and Oren proved that non-trivial non-interactive zero-knowledge in the plain model collapses to BPP [@go94]. Transparency can downgrade a *trusted* setup to a public-coin one, but "no setup at all" is unreachable. This is the theorem behind the whole "no *trusted* setup, never no setup" discipline.

**Fiat-Shamir is uninstantiable in the standard model.** In 2003, Shafi Goldwasser and Yael Tauman Kalai showed there exists a secure interactive protocol whose Fiat-Shamir version is insecure for *every* concrete hash function [@gk03]. Strong-transcript engineering removes the *avoidable* weak-Fiat-Shamir failures, but it cannot lift this ceiling: even a perfectly bound transcript rests on the random-oracle heuristic, not a proof.

For two decades that impossibility was a contrived, almost pathological construction. Then it got real.

<Aside label="The 2025 result, in scope">
In 2025, Dmitry Khovratovich, Ron Rothblum, and Lev Soukhanov moved the 2003 impossibility from a toy protocol to a *practical* soundness attack on a real, deployed GKR-based proof system, generating accepting proofs for false statements for every choice of Fiat-Shamir hash function [@khov25]. Quanta Magazine described it as shaking the bedrock assumption -- proving lies no matter which hash is used [@quanta25]. The guardrail matters: this break lives in the Fiat-Shamir *heuristic*, the transform layer, **not** in the hash's collision resistance. The hardness floor still stands; it is the transform that is now provably crackable in a real case.
</Aside>

That is the third aha moment. The intuition it corrects is the belief that disciplined engineering -- strong transcripts, sound circuits, a clean ceremony -- could in principle make a proof system perfectly and provably sound. The theorems say otherwise: soundness error is irreducible, setup is unavoidable, and Fiat-Shamir soundness has no standard-model proof and now a working counterexample on a real system. Yet even here, at the frontier where the news is worst, the break is in the artifact's heuristic layer, not the discrete-log or hash hardness underneath.

There is exactly one place the hardness floor itself is genuinely on the clock, and intellectual honesty requires naming it. A large quantum computer running [Shor's algorithm](/blog/how-q-day-breaks-everything-shors-algorithm-and-the-simultan/) breaks the discrete-logarithm and pairing assumptions that underpin Groth16, PlonK, and Bulletproofs [@stark18]. That is Q-Day: the one scenario in which the floor, not a seam, gives way. STARKs are the field's hedge against it -- they shed the algebraic target for a hash-based one [@stark18] -- but their post-quantum guarantee is a conjecture, not a theorem.

<Definition term="Knowledge soundness (extractability)">
A strengthening of plain soundness: it demands that any prover who produces a verifying proof must actually *know* a witness, formalized by an extractor that can recover that witness from the prover. Many modern SNARK attacks are knowledge-soundness attacks -- the prover convinces the verifier without knowing any valid witness at all.
</Definition>

<Sidenote>Grover's algorithm gives a quadratic speedup on brute-force search, which effectively halves a hash function's security margin. Post-quantum parameters must roughly double the hash output size to hold the same level of security -- one reason STARK proofs are large.</Sidenote>

Assemble the four bounds and the perfect proof system dissolves as a target. It would be constant-size, transparent, unconditionally post-quantum, and mechanically proven both sound and correctly constrained end to end. Goldreich-Oren forbids the "no setup" part; Goldwasser-Kalai forbids the standard-model soundness proof that non-interactivity would need; and no construction has a *theorem* of post-quantum security, only a conjecture with Grover eating into the margin.

The reachable ideal is not a single global optimum. It is a stack of local optima -- sound-verified circuits, transparent setup, strong transcripts, post-quantum-posture hashes -- each seam sewn as tight as its own theorem allows.

If the perfect system is provably out of reach, the honest question is not "is it solved?" but "where, exactly, are the seams still open?"

## 9. Where the Seams Are Still Open

Five frontiers are visibly moving, and each one is a seam not yet fully sewn.

**Sound and scalable circuit verification.** The dominant bug class deserves a tool that is both sound and fast, and none exists: Picus proves determinism but times out on large circuits, while ZKAP and Circomspect scale but are incomplete [@picus23][@zkap24][@sok24]. Production circuits run to millions or billions of constraints; the tool that could certify determinism at that scale would close the seam attackers use most.

**Closing the Fiat-Shamir theory-practice gap.** Strong transcripts eliminate the *avoidable* weak-Fiat-Shamir bugs, but deployed non-interactive soundness still rests on the random-oracle heuristic, and that gap is widening rather than closing -- the class recurred for a decade, then the 2025 result made the theoretical impossibility a working attack on a real system [@dao23][@gk03][@khov25]. The open goal is a standardized, machine-checkable transcript specification that provably rules out weak instances, or correlation-intractable hashes that instantiate Fiat-Shamir soundly for a practically useful class of protocols.

**Trust-minimized setup at Groth16's profile.** Transparent systems remove the trapdoor but pay in proof size, verification cost, or a merely conjectural post-quantum guarantee. No construction today matches Groth16's roughly 128-byte, constant-verify profile *and* transparency *and* proven post-quantum security at once [@plonk][@stark18][@eth_kzg_blog]. Goldreich-Oren forbids "no setup," so the reachable target is "no *trusted* setup" -- and the race is to close the size-and-speed penalty.

**Provably, not conjecturally, post-quantum arguments.** Every deployed pairing or discrete-log SNARK is on Shor's clock, and the hash-based hedge trades that for a conjecture with Grover eating the margin [@stark18]. A succinct argument with an *unconditional* post-quantum soundness theorem -- not an assumption about a hash -- does not yet exist.

**Audit tooling that keeps pace with new arithmetizations.** The deepest tooling targets Circom and R1CS, but constructive innovation is racing ahead into AIR, Plonkish custom gates, and full zkVM instruction traces -- re-opening the same arithmetization and transform seams in new syntaxes, including the GKR-inside-a-zkVM setting where the 2025 Fiat-Shamir break actually landed [@sok24][@khov25]. Porting sound analysis to those targets is early-stage work.

> **Note:** The gap between "we linted it" and "we proved it deterministic" is not a footnote -- it is the everyday condition of zero-knowledge auditing. Sound tools do not scale; scalable tools are not sound. Every production audit lives in that gap and closes it with human judgment.

Until these frontiers close, the most reliable defense is not a tool at all. It is a disciplined human review -- one that walks the same three layers, in the same order the bugs appear.

## 10. A Layer-by-Layer Design Review

Everything in this article compresses into a checklist a reviewer can apply cold: three layers, three questions, asked in the order the bugs actually appear.

**Layer 1, arithmetization.** Is *every* signal constrained, with no stray `<--` where a `<==` was intended? Are the outputs uniquely determined by the inputs on the critical path -- provably, not by inspection? Are there aliasing gaps or non-deterministic "hint" signals trusted without an in-circuit check? Run Circomspect in CI to catch the assignment-versus-constraint slips [@circomspect_gh], ZKAP for corpus-scale triage [@zkap24], and Picus to prove determinism on the circuits that guard funds [@picus23]. The property Picus checks is the one you can test yourself in miniature.

<RunnableCode lang="js" title="A toy determinism check: is the output uniquely determined?">{`
// Echoes the question Picus/QED2 asks: for fixed inputs, is the output unique?
const p = 13;
const mod = (a) => ((a % p) + p) % p;

// The author intends "out = 1 exactly when inp is nonzero", but writes only:
//        inp * out === inp
// which forgets to force out to be boolean. Watch input 0 go wrong.
function constraintHolds(inp, out) {
  return mod(inp * out) === mod(inp);
}

function acceptedOutputs(inp) {
  const outs = [];
  for (let out = 0; out < p; out++) {
    if (constraintHolds(inp, out)) outs.push(out);
  }
  return outs;
}

for (let inp = 0; inp <= 2; inp++) {
  console.log("input", inp, "-> accepted outputs", acceptedOutputs(inp));
}
// input 0 accepts EVERY output 0..12 -- under-determined and forgeable.
// inputs 1 and 2 accept exactly one output -- uniquely determined.
// A determinism checker flags input 0; a plain linter never would.
`}</RunnableCode>

**Layer 2, setup.** Is the system trusted or transparent? If trusted, was the ceremony run with at least one credibly honest party, and is your reference string byte-identical to the audited transcript you think you are using? [@eth_kzg_blog] Remember that a universal SRS is not the same as no trapdoor -- PlonK's string is reusable, not trustless [@plonk]. Prefer transparency where your proof-size and verification budget allows it; otherwise, verify the ceremony rather than assuming it.

**Layer 3, transform.** Does the challenge hash bind the domain separator, the statement, *all* public inputs, and the full transcript? Use an audited transcript library such as Merlin rather than a hand-rolled hash of the commitment [@merlin_gh]. This is the single question that would have prevented every Frozen Heart instance.

> **Note:** 1. **Circuit:** Is every signal constrained, and is every output uniquely determined by the inputs? 2. **Setup:** Trusted or transparent -- and if trusted, is your string the audited one and the ceremony credibly honest? 3. **Transcript:** Does the challenge hash bind the statement and every public input? Three questions, one per seam, in the order bugs appear.

There is a fourth question that sits above the other three: should this be a zero-knowledge system at all?

> **Key idea:** Zero-knowledge adds three failure layers -- circuit, setup, transcript -- above a hardness floor that, on its own, was never the problem. If you need neither privacy nor succinct verification of an untrusted computation, a signature or a re-execution has none of those seams to get wrong. Adopt zero-knowledge only when its guarantees are the ones you actually need.

That is not an argument against zero-knowledge proofs. They do something nothing else can -- verify a computation you never watched, while learning nothing about its secrets. It is an argument for adopting them with open eyes, knowing that each of their conveniences is also a seam.

The checklist works for one reason: every question maps to a layer, and every layer, we now know, is where the breaks actually live. That is the thesis restated as an operating procedure.

## 11. Frequently Asked Questions

<FAQ title="Frequently asked questions">
<FAQItem question="A valid proof means the statement is true, right?">
Not by itself. Soundness is defined relative to the *circuit's* constraints, not your intentions. If the circuit is under-constrained, it will certify witnesses the specification never meant to allow, and the verifier accepts them as valid [@sok24]. "Valid" means "some witness satisfied the constraints," which equals "true" only when the constraints were written to force it.
</FAQItem>
<FAQItem question="Isn't a trusted setup just a privacy backdoor?">
No. The setup trapdoor governs *soundness*, not privacy. Whoever retains it can forge proofs of false statements [@groth16], but it tells them nothing about what any honest proof was hiding. Confusing the two misplaces the risk: the trapdoor is a forgery risk, not a de-anonymization one.
</FAQItem>
<FAQItem question="Are STARKs post-quantum secure?">
Conjecturally, not provably. STARK security rests on the conjectured collision resistance of a hash, which no one has proved survives a quantum adversary, and Grover's algorithm forces larger hash parameters to keep the same margin [@stark18]. The correct phrase is "plausibly post-quantum," never "post-quantum secure" as if it were a theorem.
</FAQItem>
<FAQItem question="Does a zero-knowledge exploit mean the cryptography was broken?">
Almost never. In the documented record, the break is in the layer above the hardness assumption -- the circuit, the setup string, or the transcript -- while discrete logarithm, pairings, and hashes held [@sok24]. Name the layer and you have named the break; the floor rarely enters the story.
</FAQItem>
<FAQItem question="Isn't this all just side channels and bad randomness?">
No, and that distinction is the point. Timing attacks, faulty randomness, and nonce leakage are real, but they are implementation faults -- the subject of the sibling article "How Zero-Knowledge Broke in Real Life." Everything here is *structural*: it would still be a bug on perfect hardware with a perfect random-number generator.
</FAQItem>
<FAQItem question="Bulletproofs has no trusted setup, so why did it still break?">
Because the transform seam is orthogonal to the setup seam. Bulletproofs is transparent, yet it was still a Frozen Heart victim through weak Fiat-Shamir, traced to an insecure recommendation in the original paper [@tob_fh][@bulletproofs]. Removing the trapdoor does nothing about what enters the challenge hash.
</FAQItem>
<FAQItem question="Can automated tools catch every under-constrained bug?">
Not yet. No deployed tool is both sound and scalable: Picus proves determinism but times out on large circuits, while ZKAP and Circomspect scale but are incomplete [@picus23][@zkap24]. Production audits close that gap with human review, not tooling alone.
</FAQItem>
</FAQ>

## 12. Making "Verified" Mean What You Think

Return one last time to the verifier that returned `true`. Nothing about that bit was wrong. The pairing balanced, the hash matched, and the assumption underneath held -- as it has held in every documented break in this article. What failed was the artifact humans compiled on top of that floor: a circuit missing a constraint, a setup string built or handled wrong, a transcript that hashed too little.

That is the whole argument, and the evidence now sits behind it. Across the disclosed record, the hardness floor never carried the failure [@sok24]. The three seams above it did -- and each break announced its own layer. Under-constrained circuits forge in the arithmetization; retained or malformed setup strings forge in the setup; weak Fiat-Shamir forges in the transform.

Even a transparent, hash-based STARK only sheds the setup seam while inheriting the rest, and its post-quantum promise is a conjecture with Grover's clock running. The one place the floor itself is genuinely at risk is Q-Day, when Shor's algorithm reaches the algebraic assumptions -- the single scenario this article cannot file under "the layer humans built."

<PullQuote>
The cryptography was never the weak point. The discipline is making sure that when the verifier says "true," it means the thing you actually intended -- constraint by constraint, string by string, hash input by hash input.
</PullQuote>

Name the layer, and you have named the break. For how these systems are constructed in the first place, see "Never Decrypted: Proving You Ran the Computation." For the implementation and side-channel failures deliberately held out of scope here, see "How Zero-Knowledge Broke in Real Life." Both are the other half of learning to trust a proof you did not watch being made.

<StudyGuide slug="how-zero-knowledge-proofs-actually-break" keyTerms={[
  { term: "Soundness", definition: "The guarantee that a cheating prover cannot convince an honest verifier of a false statement except with negligible probability; the property attackers target." },
  { term: "Under-constrained circuit", definition: "A constraint system that admits more than one witness for a signal the designer meant to pin down, letting a prover certify an out-of-spec value." },
  { term: "Toxic waste", definition: "The secret scalars a trusted setup samples and must destroy; whoever retains them holds a trapdoor that forges any statement." },
  { term: "Fiat-Shamir transform", definition: "Replacing a verifier's random challenge with a hash of the transcript to make a proof non-interactive; sound only when the hash binds the statement and all public inputs." },
  { term: "Transparent setup", definition: "A setup using only public randomness, with no secret trapdoor to leak; still a setup, just not a trusted one." },
  { term: "Knowledge soundness", definition: "A strengthening of soundness requiring that any prover who convinces the verifier must actually know a witness, formalized by an extractor." }
]} />
