<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Parag Mali - tag: zk-snarks</title><description>Posts tagged zk-snarks.</description><link>https://paragmali.com/</link><language>en-US</language><lastBuildDate>Sat, 25 Jul 2026 08:57:44 GMT</lastBuildDate><atom:link href="https://paragmali.com/tags/zk-snarks/rss.xml" rel="self" type="application/rss+xml"/><item><title>How Zero-Knowledge Broke in Real Life: Under-Constrained Circuits and the Bugs That Minted Money</title><link>https://paragmali.com/blog/how-zero-knowledge-broke-in-real-life-under-constrained-circ/</link><guid isPermaLink="true">https://paragmali.com/blog/how-zero-knowledge-broke-in-real-life-under-constrained-circ/</guid><description>Every time production zero-knowledge has publicly broken, the cryptography held. The real failures are soundness bugs in circuits, verifiers, and transcripts.</description><pubDate>Fri, 24 Jul 2026 00:00:00 GMT</pubDate><content:encoded>
Zero-knowledge proofs now secure billions in rollups and bridges -- and every time one has *publicly* broken, the cryptography held. The failures were soundness bugs in the human-built artifact: a circuit missing a constraint, a verifier mishandling a zero, or a Fiat-Shamir transcript that hashed too little -- each letting a prover convince an honest verifier of something false. This is the documented record: Zcash&apos;s counterfeiting flaws, Dark Forest&apos;s range-check omission, the Frozen Heart disclosures, Solana&apos;s ZK ElGamal near-miss, the 2026 zkVM transcript breaks, the Veil Cash and FOOMCASH Groth16 exploits that actually moved money, and the adjacent BNB bridge theft. Why is this one bug family so easy to introduce, so hard to catch -- and what does the industry now build to hunt it? The through-line: the crypto held; the code didn&apos;t.
&lt;h2&gt;1. A Proof of Something That Was Not True&lt;/h2&gt;
&lt;p&gt;In June 2025, security researchers sent Solana&apos;s engineers a quiet, alarming message. The blockchain&apos;s on-chain ZK ElGamal Proof program -- the component that checks zero-knowledge proofs behind Token-2022 confidential transfers -- would accept a proof of something that was not true. The post-mortem stated the consequence plainly: a sophisticated attacker could forge a proof enabling &quot;minting unlimited tokens or draining a victim&apos;s confidential balance&quot; [@solana-june-2025].&lt;/p&gt;
&lt;p&gt;No elliptic curve had been solved. No hash had been reversed. The discrete logarithm was exactly as hard the morning after as the morning before. What went wrong was smaller and stranger than any of that. In the program&apos;s own words, &quot;a component was not included in a hash used to generate a transcript for the Fiat-Shamir Transformation&quot; [@solana-june-2025]. One algebraic value was left out of a single hash, and that omission was enough to forge a proof the verifier would wave straight through.&lt;/p&gt;

&quot;...minting unlimited tokens or draining a victim&apos;s confidential balance ... There is no known exploit.&quot; -- Solana ZK ElGamal Proof program post-mortem, June 2025
&lt;p&gt;Read the last clause of that quote carefully, because it governs everything that follows. &lt;em&gt;There is no known exploit&lt;/em&gt; [@solana-june-2025]. The bug was reported and disclosed responsibly, confidential transfers were disabled on June 11, and the proof program itself was disabled at the start of mainnet-beta epoch 805 on June 19 [@solana-june-2025]. The feature was still in testing, and no real funds moved. This was a near-miss, not a heist.&lt;/p&gt;

In a proof system, *soundness* is the guarantee that no one can produce a convincing proof of a false statement. A cheating prover cannot make an honest verifier accept a claim that is not true, except with negligible probability. Every failure in this article is a soundness failure: a valid-looking proof of something that was not so.
&lt;p&gt;That distinction -- between a proof of a true thing and a proof the verifier merely &lt;em&gt;accepts&lt;/em&gt; -- is the property under attack in every incident here. When it fails, we say the system has lost soundness.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Every incident below carries a disposition tag, and the differences are load-bearing. A &lt;strong&gt;near-miss&lt;/strong&gt; was disclosed and patched with no known exploitation -- present in shipping code, never turned into stolen funds. An &lt;strong&gt;exploited-then-recovered&lt;/strong&gt; bug did move value on-chain, but the funds were returned or white-hat-rescued, so the net loss was small or zero. A &lt;strong&gt;confirmed theft&lt;/strong&gt; moved value that stayed gone. And a &lt;strong&gt;reported&lt;/strong&gt; incident is one where money moved but the zero-knowledge root cause rests on a security firm&apos;s characterization, with no primary post-mortem yet. Almost every clean zero-knowledge &lt;em&gt;circuit&lt;/em&gt; failure on record is a near-miss; every confirmed theft, as we will see, lives in a verifier, a settlement boundary, or integration code -- and the largest was an adjacent kind of bug entirely.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Here is the thesis in miniature, and it inverts the headline you would expect. Solana&apos;s flaw is not an anomaly -- it is the &lt;em&gt;signature&lt;/em&gt; of how zero-knowledge breaks in real life. Picture the system as two layers. Underneath sits a cryptographic &lt;em&gt;hardness floor&lt;/em&gt;: the discrete-log-type and pairing-based hardness assumptions -- formally argued in the generic group model -- together with the collision-resistant hashes that proofs rest on.&lt;/p&gt;
&lt;p&gt;Stacked on top is a &lt;em&gt;human-built artifact&lt;/em&gt;: the construction, the circuit, the verifier, the transcript. In every publicly disclosed failure, the floor held. The artifact above it leaked. And where that artifact guarded a rollup or a bridge, the leak was denominated in money.&lt;/p&gt;
&lt;p&gt;This piece is the documented record of that pattern -- the empirical mirror of its structural sibling, &lt;a href=&quot;https://paragmali.com/blog/a-valid-proof-of-a-false-thing-how-zero-knowledge-proofs-act/&quot; rel=&quot;noopener&quot;&gt;&lt;em&gt;A Valid Proof of a False Thing&lt;/em&gt;&lt;/a&gt;, which derives &lt;em&gt;why&lt;/em&gt; these proofs can break, and a companion to &lt;a href=&quot;https://paragmali.com/blog/never-decrypted-proving-you-ran-the-computation-without-reve/&quot; rel=&quot;noopener&quot;&gt;&lt;em&gt;Never Decrypted: Proving You Ran the Computation&lt;/em&gt;&lt;/a&gt;, which shows how the proof systems are built in the first place. Here we do neither the derivation nor the construction. We read the ledger.&lt;/p&gt;
&lt;p&gt;No cryptography broke -- so what, exactly, did? To answer that, we have to go back to the first time real money sat behind a shipping zero-knowledge system.&lt;/p&gt;
&lt;h2&gt;2. The First Silently Forgeable Proof&lt;/h2&gt;
&lt;p&gt;On March 1, 2018, Ariel Gabizon -- a cryptographer then at the Electric Coin Company, which builds &lt;a href=&quot;https://paragmali.com/blog/the-anonymity-that-actually-holds-what-zcash-and-monero-prov/&quot; rel=&quot;noopener&quot;&gt;Zcash&lt;/a&gt; -- found something disquieting in the paper Zcash had launched on. The privacy coin&apos;s proving system was a variant, known as BCTV14, of an earlier protocol called Pinocchio [@bctv-2013-879, @pinocchio-2013-279]. Gabizon discovered &quot;a subtle cryptographic flaw&quot; in it [@ecc-zcash]. Not in the hardness of the pairings the proofs relied on, but in the construction wrapped around them.&lt;/p&gt;

A **common reference string (CRS)** is a set of public parameters, produced once in a trusted setup, that both prover and verifier consult. In a pairing-based SNARK it encodes the circuit&apos;s structure as elliptic-curve group elements. If the CRS exposes elements it was supposed to withhold, a prover can turn them against the system -- which is exactly what happened to Zcash.
&lt;p&gt;The flaw was specific. Pinocchio&apos;s template deliberately left certain elements out of its public parameters; the BCTV14 variant put some of them back. Gabizon&apos;s write-up names the mechanism exactly: the construction included &quot;redundant elements in the CRS ... which are vital not to expose,&quot; and their exposure let a prover create &quot;a proof of knowledge for &lt;em&gt;any&lt;/em&gt; public input given a valid proof for &lt;em&gt;some&lt;/em&gt; public input&quot; [@gabizon-2019-119]. In plain stakes, that is counterfeiting -- forging shielded Zcash out of nothing. The assigned CVE puts the consequence in a single sentence [@cve-2019-7167].&lt;/p&gt;

&quot;...transform the proof of one statement into an ostensibly valid proof of a different statement, thereby breaking the soundness of the proof system.&quot; -- CVE-2019-7167
&lt;p&gt;The company found no evidence anyone had used it. The bug was patched silently by the Sapling network upgrade, which activated on October 28, 2018, and disclosed publicly on February 5, 2019 [@ecc-zcash].The vulnerability ran on two clocks. It was &lt;em&gt;exploitable&lt;/em&gt; for roughly two years -- from the Sprout launch in October 2016 until Sapling closed it on October 28, 2018 -- but &lt;em&gt;secret&lt;/em&gt; for only about eleven months, from Gabizon&apos;s March 2018 discovery to the February 2019 disclosure [@ecc-zcash]. A long window of exposure, a short window of concealment. The first near-miss in a long line.&lt;/p&gt;
&lt;p&gt;Zcash teaches the shape of the entire record. Separate two things that are easy to blur. Underneath any real proof system sits a &lt;strong&gt;hardness floor&lt;/strong&gt; -- named, well-studied assumptions like the discrete logarithm and elliptic-curve pairings. A pairing here is the verifier&apos;s &lt;em&gt;tool&lt;/em&gt;, the operation that checks the proof equation, not itself a hardness assumption. A Groth16-style SNARK&apos;s soundness is argued in the &lt;em&gt;generic group model&lt;/em&gt;, an idealized setting in which the adversary may only combine group elements through the allowed operations [@groth16-2016-260].&lt;/p&gt;
&lt;p&gt;On top of that floor, humans build an &lt;strong&gt;artifact&lt;/strong&gt;: the construction, the circuit, the verifier, the transcript. Gabizon&apos;s flaw did not touch the floor. It lived in the construction -- the topmost, most human layer -- and his fix confirms the diagnosis: the corrected BCTV14 is provably sound &quot;in the generic group model, when these elements are excluded from the CRS&quot; [@gabizon-2019-119]. The math was fine; the wiring around it exposed something it should not have.&lt;/p&gt;

flowchart TD
    A1[&quot;Construction: CRS and proof-system wiring&quot;] --&amp;gt; A2[&quot;Circuit: the statement compiled to field constraints&quot;]
    A2 --&amp;gt; A3[&quot;Verifier: the on-chain proof-checker&quot;]
    A3 --&amp;gt; A4[&quot;Transcript: the Fiat-Shamir challenge hash&quot;]
    A4 --&amp;gt; F1
    subgraph FLOOR[&quot;Hardness floor (generic group model) -- unbroken in every disclosed case&quot;]
        F1[&quot;Discrete-log and pairing hardness, plus collision-resistant hashes&quot;]
    end

The 0xPARC ZK Bug Tracker files Zcash under &quot;Trusted Setup Leak&quot; [@zk-bug-tracker], which sounds like toxic-waste retention -- someone kept the setup&apos;s secret trapdoor. The primary sources tell a different story. Gabizon and the Electric Coin Company describe a *redundant-CRS construction bug*: the setup published elements it should have held back, and no leaked trapdoor was needed [@gabizon-2019-119, @ecc-zcash]. Both failures live in the setup, but they are not the same failure. This article follows the primaries and keeps the two distinct.
&lt;p&gt;The same shielded-Zcash counterfeiting signature returned almost exactly eight years later, one artifact layer down. In late May 2026, Taylor Hornby, working for Shielded Labs, found an under-constrained element in the newer Orchard circuit -- the shielded pool that succeeded Sapling -- that made it &quot;possible to put arbitrary false inputs into an elliptic curve multiplication and still have the multiplication check pass&quot; [@shieldedlabs-orchard]. A complete exploit generated &quot;unlimited, undetectable counterfeit ZEC&quot; in a local regtest [@forum-zcash-orchard].&lt;/p&gt;
&lt;p&gt;The gap had been latent from Orchard&apos;s activation in May 2022 until an emergency fix on June 1, 2026 -- roughly four years -- and because Zcash&apos;s privacy hides transaction values, there is &quot;no definitive way to determine using only cryptography whether such exploitation occurred&quot; on mainnet [@forum-zcash-orchard]. A near-miss that rhymes with 2018 down to the coin, and -- as Section 3 will show -- a textbook case of a bug you cannot test for, because the flaw is a constraint that was never written.The 2026 Orchard bug sits in a different layer from the 2018 flaw -- 2018 was a redundant-CRS &lt;em&gt;construction&lt;/em&gt; bug, 2026 an under-constrained &lt;em&gt;circuit&lt;/em&gt; gadget -- but the coin and the consequence are identical: counterfeit shielded ZEC from nothing. The discovery was AI-assisted, through a targeted review of the Orchard circuit [@shieldedlabs-orchard]. It stays a near-miss: proven in regtest, never confirmed on mainnet [@forum-zcash-orchard].&lt;/p&gt;
&lt;p&gt;Two catalogs make the rest of this record legible, and both recur below. The first is the &lt;strong&gt;0xPARC ZK Bug Tracker&lt;/strong&gt;, the canonical community catalog of zero-knowledge bugs found in the wild, with its own taxonomy of failure modes [@zk-bug-tracker]. The second is the academic corpus: a 2024 systematization by Chaliasos and colleagues that analyzed 141 real-world SNARK vulnerabilities and asked what patterns they share [@chaliasos-sok-arxiv, @chaliasos-sok-usenix]. Between them, they let us treat &quot;how zero-knowledge breaks&quot; as an empirical question with an answerable shape.&lt;/p&gt;

flowchart LR
    Z[&quot;2018-19: Zcash counterfeiting -- construction&quot;] --&amp;gt; D[&quot;2021: Dark Forest range check -- circuit&quot;]
    D --&amp;gt; AZ[&quot;2022: Aztec 0-bug -- verifier&quot;]
    AZ --&amp;gt; FH[&quot;2022: Frozen Heart -- transcript&quot;]
    FH --&amp;gt; B[&quot;Oct 2022: BNB Token Hub -- adjacent Merkle verifier, CONFIRMED THEFT&quot;]
    B --&amp;gt; S[&quot;2025: Solana ZK ElGamal -- transcript, near-miss&quot;]
    S --&amp;gt; VG[&quot;Feb 2026: Veil Cash / FOOMCASH -- Groth16 verifier key, EXPLOITED then RECOVERED&quot;]
    VG --&amp;gt; V[&quot;early 2026: six zkVMs -- transcript, near-miss&quot;]
    V --&amp;gt; OR[&quot;June 4 2026: Zcash Orchard -- circuit, near-miss&quot;]
    OR --&amp;gt; AC[&quot;June 14 2026: Aztec Connect -- circuit + settlement, CONFIRMED THEFT&quot;]
    AC --&amp;gt; HK[&quot;July 3 2026: Hinkal -- verifier integration, REPORTED&quot;]
&lt;p&gt;Zcash&apos;s flaw was exotic -- a redundant element buried in a trusted setup. It would have been comforting if that were the whole story. Instead, as zero-knowledge spread from one coin to thousands of applications, the same &lt;em&gt;kind&lt;/em&gt; of bug appeared everywhere -- and in a form so ordinary it hid in plain sight.&lt;/p&gt;
&lt;h2&gt;3. The Constraint That Was Never Written&lt;/h2&gt;
&lt;p&gt;Dark Forest is a fully on-chain, real-time strategy game -- a galaxy of planets settled on Ethereum, where players keep their coordinates secret and prove in zero knowledge that each move is legal. A move requires showing that a target coordinate lies inside the map&apos;s bounds without revealing where it is. Dark Forest&apos;s circuit did this with a &lt;code&gt;RangeProof&lt;/code&gt; built on a standard library comparator [@df-blog]. The comparator looked right. It was not, and the bug was flagged by Daira Hopwood [@zk-bug-tracker].&lt;/p&gt;

An **arithmetic circuit** expresses a computation as equations -- additions and multiplications -- over the integers modulo a large prime. Compiled to a rank-1 constraint system (R1CS), it becomes a list of constraints, each of the form (linear combination) times (linear combination) equals (linear combination). A zero-knowledge proof convinces a verifier that the prover knows a *witness* -- an assignment of a value to every wire -- satisfying every constraint, while revealing nothing about it.
&lt;p&gt;The subtlety is that a proof does not certify that a statement is true. It certifies that &lt;em&gt;some&lt;/em&gt; witness satisfies the constraints that were actually written. If the constraints permit a witness the designer never intended, the proof will happily certify it.&lt;/p&gt;

A circuit is **under-constrained** when its equations fail to pin down a unique, legitimate witness -- when some assignment the designer never intended still satisfies every written constraint. The missing constraint is an *absence*: the equation that would have excluded the bad witness was simply never written.
&lt;p&gt;The smallest illustration fits in one line. Suppose a circuit constrains $y \cdot y = x$ and treats $x$ as the output. Over a prime field, that equation has two solutions for most $x$: both $y$ and its negation satisfy it. The output does not determine $y$. That non-uniqueness -- one input, more than one satisfying witness -- is the algebraic signature of every under-constrained circuit.&lt;/p&gt;
&lt;p&gt;{`
// A tiny field: the integers modulo a small prime.
const p = 23;
const mod = (a) =&amp;gt; ((a % p) + p) % p;&lt;/p&gt;
&lt;p&gt;// A constraint that only says: y * y === x  (mod p).
// It pins down x from y, but never pins down which root y is.
const x = 9;&lt;/p&gt;
&lt;p&gt;// Search the whole field for values of y that satisfy the constraint.
const witnesses = [];
for (let y = 0; y &amp;lt; p; y++) {
  if (mod(y * y) === x) witnesses.push(y);
}&lt;/p&gt;
&lt;p&gt;console.log(&apos;Constraint: y*y === &apos; + x + &apos; (mod &apos; + p + &apos;)&apos;);
console.log(&apos;Witnesses that satisfy it: &apos; + witnesses.join(&apos;, &apos;));
console.log(witnesses.length + &apos; distinct witnesses, so the output does NOT determine y.&apos;);
// Both 3 and 20 (which is -3 mod 23) pass. The circuit is under-constrained.
`}&lt;/p&gt;
&lt;p&gt;Dark Forest&apos;s bug was this same shape, dressed in a range check.&lt;/p&gt;

A **range check** forces a value to lie in a bounded interval, usually by constraining it to fit in *n* bits. Field arithmetic wraps around modulo a large prime, so without a range check a value that looks small can secretly be enormous. A large share of under-constrained bugs are missing range checks.
&lt;p&gt;Dark Forest v0.3 built its &lt;code&gt;RangeProof&lt;/code&gt; on CircomLib&apos;s &lt;code&gt;LessThan(n)&lt;/code&gt; comparator [@df-blog]. That gadget is only correct when its inputs are already known to fit in &lt;em&gt;n&lt;/em&gt; bits -- it &lt;em&gt;assumes&lt;/em&gt; the bound but does not &lt;em&gt;enforce&lt;/em&gt; it.&lt;/p&gt;
&lt;p&gt;The circuit never constrained its inputs to &lt;em&gt;n&lt;/em&gt; bits, so an attacker could feed an oversized field element whose low bits looked in-range and satisfy a &lt;code&gt;RangeProof&lt;/code&gt; for an out-of-range coordinate. The fix was to add &lt;code&gt;Num2Bits&lt;/code&gt; bit-length checks on both inputs, forcing them into range before the comparison [@df-fix-commit].In Circom the assignment operators are not interchangeable. &lt;code&gt;&amp;lt;==&lt;/code&gt; both &lt;em&gt;assigns&lt;/em&gt; a value and &lt;em&gt;adds a constraint&lt;/em&gt; tying it to the equation, while &lt;code&gt;&amp;lt;--&lt;/code&gt; and &lt;code&gt;=&lt;/code&gt; only assign, producing a witness value with no constraint attached. A signal written with &lt;code&gt;&amp;lt;--&lt;/code&gt; and never separately constrained is invisible to the proof -- the classic way an intended constraint goes missing.&lt;/p&gt;
&lt;p&gt;{`
// CircomLib&apos;s LessThan(n) is only correct when BOTH inputs are &amp;lt; 2^n.
// It decides a &amp;lt; b from the low n bits. If a caller forgets to constrain
// the inputs to n bits (no Num2Bits), the gadget can be fooled.&lt;/p&gt;
&lt;p&gt;const n = 8;          // designed for values in 0..255
const bound = 100;    // we want to prove: secret &amp;lt; 100&lt;/p&gt;
&lt;p&gt;function lessThan_nbits(a, b) {
  const mask = (1 &amp;lt;&amp;lt; n) - 1;            // only the low n bits are meaningful
  return (a &amp;amp; mask) &amp;lt; (b &amp;amp; mask) ? 1 : 0;
}&lt;/p&gt;
&lt;p&gt;const honest = 42;            // a legitimate in-range coordinate
const oversized = 256 + 42;   // 298: same low 8 bits as 42, but out of range&lt;/p&gt;
&lt;p&gt;console.log(&apos;LessThan says 42 &amp;lt; 100  : &apos; + lessThan_nbits(honest, bound));    // 1, correct
console.log(&apos;LessThan says 298 &amp;lt; 100 : &apos; + lessThan_nbits(oversized, bound)); // 1, FALSE but accepted
console.log(&apos;298 is out of range, yet the range proof passes.&apos;);
console.log(&apos;The forgotten Num2Bits(8) on the input is the missing constraint.&apos;);
`}&lt;/p&gt;

flowchart TD
    IN[&quot;Attacker supplies an oversized field element as the coordinate&quot;] --&amp;gt; RP[&quot;RangeProof calls LessThan(n) on the input&quot;]
    RP --&amp;gt; ASSUME{&quot;Is the input constrained to n bits?&quot;}
    ASSUME --&amp;gt;|&quot;No Num2Bits check (the bug)&quot;| WRONG[&quot;LessThan reads only the low n bits, returns in-range&quot;]
    ASSUME --&amp;gt;|&quot;Num2Bits added (the fix)&quot;| RIGHT[&quot;Oversized input rejected before comparison&quot;]
    WRONG --&amp;gt; ACCEPT[&quot;Verifier accepts a proof for an out-of-range value&quot;]
&lt;p&gt;Now sit with why this is so hard to catch.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; You cannot unit-test the presence of a constraint you forgot to write. A test feeds the circuit an input, computes a witness, and checks that a valid proof results -- and it always does, because the illegitimate witnesses live in exactly the space the missing equation was supposed to forbid. Every positive test passes. The bug is an &lt;em&gt;absence&lt;/em&gt;, and absences do not appear in a test that only exercises what is already there.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This was no one-off. The same absence recurs across production libraries -- unbounded remainders, unchecked range gadgets, and more -- catalogued in the next section [@zk-bug-tracker].&lt;/p&gt;
&lt;p&gt;Different libraries, same shape: a value assigned but never pinned down. All of these were near-misses -- audit-caught or disclosed before any exploitation. But the pattern was now visible, and it was not going away.&lt;/p&gt;
&lt;p&gt;The comforting response is &quot;audit harder.&quot; Yet a missing constraint is an absence, and absences do not live in one place. To defend against them, you first have to see the whole artifact they can hide in.&lt;/p&gt;
&lt;h2&gt;4. Three Ways to Accept a Lie&lt;/h2&gt;
&lt;p&gt;If a circuit can forget a constraint, what else can? Walk up the artifact and each layer has its own way of certifying something false. There are three: the circuit, the verifier, and the transcript. Each has a signature failure, and each disclosed failure is the reason the next defense exists.&lt;/p&gt;

flowchart TD
    L1[&quot;Circuit layer: Dark Forest, Aztec nullifier, MACI -- a missing constraint or range check&quot;] --&amp;gt; L2[&quot;Verifier layer: Aztec 0-bug, BNB Merkle -- the proof-checker itself accepts a forgery&quot;]
    L2 --&amp;gt; L3[&quot;Transcript layer: Frozen Heart, Solana, zkVMs -- the Fiat-Shamir hash binds too little&quot;]
&lt;h3&gt;The circuit forgets a constraint&lt;/h3&gt;
&lt;p&gt;This is the dominant class -- the $y \cdot y = x$ intuition, generalized across production code. Once you know to look for an absence, you start to find it everywhere:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;BigInt arithmetic.&lt;/strong&gt; A &lt;code&gt;BigMod&lt;/code&gt; template left its output remainder unbounded, so the &quot;remainder&quot; could exceed the modulus [@zk-bug-tracker].&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Circom-Pairing.&lt;/strong&gt; The library never checked the outputs of its &lt;code&gt;BigLessThan&lt;/code&gt; range checks -- Veridise&apos;s &quot;million-dollar bug caught early,&quot; found during an audit before the code reached mainnet [@veridise-circom-pairing].&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MACI 1.0.&lt;/strong&gt; An under-constrained vote-validity check would have let a coordinator quietly censor targeted votes [@zk-bug-tracker].&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MiMC.&lt;/strong&gt; A hash value was &quot;assigned but not constrained&quot; -- &lt;code&gt;=&lt;/code&gt; where &lt;code&gt;&amp;lt;==&lt;/code&gt; was required [@zk-bug-tracker].&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ZK Email.&lt;/strong&gt; An under-constrained circuit enabled email-address spoofing [@zk-bug-tracker].&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PSE, Scroll, and Polygon zkEVMs.&lt;/strong&gt; The same family surfaced as missing-constraint and unbounded-remainder findings inside production rollup circuits [@zk-bug-tracker].&lt;/li&gt;
&lt;/ul&gt;

A **nullifier** is a unique tag a zero-knowledge system publishes when a secret note is spent, so the same note cannot be spent twice. Its security depends on being deterministic and unique. If the circuit fails to pin it down, one note can produce many nullifiers, and the double-spend guard evaporates.
&lt;p&gt;The most vivid of the set is Aztec&apos;s nullifier bug. In Aztec 2.0, &quot;the field element representing this position was not actually constrained to be 32 bits,&quot; a gap that would &quot;allow the attacker to spend the same input multiple times&quot; [@aztec-disclosure]. A double-spend, produced by a single missing bit-length constraint.The 0xPARC catalog does not date its individual entries, so several years in this under-constrained cluster are approximate -- Dark Forest around 2021, the rest grouped roughly 2021 to 2023 [@zk-bug-tracker]. The dates are soft; the pattern is not.&lt;/p&gt;
&lt;p&gt;Every one of these is an absence. Every one was a near-miss -- caught in an audit or disclosed before exploitation.Not every &quot;missing range check&quot; is a soundness bug, and the distinction sharpens the point. Semaphore and its Zk-Kit dependency once shipped without a check that a proof input was smaller than the SNARK scalar-field order [@semaphore-issue-90, @zk-kit-issue-23]. But that check was in the &lt;em&gt;smart contract&lt;/em&gt;, and its absence produced &quot;a group that will always fail verification&quot; -- valid proofs &lt;em&gt;rejected&lt;/em&gt;, a group rendered unusable [@zk-bug-tracker]. That is a completeness or liveness failure -- a denial of service -- not a soundness forgery, and it was fixed by adding the missing bound [@semaphore-fix-91]. It belongs one layer up from the circuit-soundness absences in this section, and naming it that way is what keeps &quot;every failure here is a soundness failure&quot; honest.&lt;/p&gt;
&lt;h3&gt;The verifier itself is wrong&lt;/h3&gt;

The **verifier** is the code that checks a proof -- on a blockchain, usually a smart contract or precompile. It is a *separate artifact* from the circuit that produced the proof. A flawless circuit can still be defeated by a buggy verifier, because the verifier can accept proofs the circuit&apos;s math would never have permitted. Two artifacts, two independent chances to be wrong.
&lt;p&gt;Aztec&apos;s PlonK verifier, written in C++, accepted forged proofs when two proof elements were set to zero. The code recognized &lt;code&gt;0&lt;/code&gt; as &quot;not on the curve&quot; yet did not reject it, then re-interpreted that same &lt;code&gt;0&lt;/code&gt; as the point at infinity, which satisfied the final pairing equation. A point-at-infinity and zero-encoding inconsistency, living in the checker rather than in any circuit [@aztec-0bug, @zk-bug-tracker].&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; It is tempting to reason: &quot;the circuit was proven correct, so the system is safe.&quot; The verifier is a different program with different bugs. Aztec&apos;s circuit could have been perfect and its C++ verifier would still have accepted a forged proof. When money depends on a proof, the audited artifact must be the on-chain verifier, not only the circuit.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;February 2026 then put that warning on-chain twice, in the first two known exploits against &lt;em&gt;live&lt;/em&gt; zero-knowledge verifiers. Neither touched the circuit, the pairing, or the curve. Both hit a trusted setup that was never finished.&lt;/p&gt;

Groth16, like many SNARKs, needs a **trusted setup**: a one-time ceremony that derives the proving and verification keys from secret randomness that must then be discarded. Its ceremony has two phases -- a universal &quot;powers of tau&quot; (Phase 1) and a per-circuit contribution (Phase 2) that randomizes the key parameter $\delta$. Skip Phase 2 and the tooling ships a placeholder key in which $\delta$ still equals $\gamma$ -- a valid-looking default that verifies honest proofs perfectly and accepts forged ones just as happily.
&lt;p&gt;Veil Cash, a Base privacy pool, deployed a Groth16 verifier at &lt;code&gt;0x1e65c075989189e607ddafa30fa1a0001c376cfd&lt;/code&gt; whose verification key had &lt;code&gt;delta2 == gamma2&lt;/code&gt;: both were the BN128 G2 generator point [@darknavy-veil, @veilcash-poc]. In Groth16, $\gamma$ and $\delta$ play different roles and must be independent. DARKNAVY states the consequence directly: with &lt;code&gt;delta == gamma&lt;/code&gt;, the proof element &lt;code&gt;C&lt;/code&gt; can cancel the public-input term, letting an attacker make the pairing equation pass &quot;for ANY public inputs without any witness&quot; [@darknavy-veil].&lt;/p&gt;
&lt;p&gt;Using fabricated nullifier hashes across 29 withdrawals of 0.1 ETH, an actor drained 2.9 ETH of the roughly 4.5 ETH sitting in the legacy pools [@darknavy-veil, @veilcash-poc]. The Groth16 mathematics held; the deployed verification key was wrong.&lt;/p&gt;

flowchart TD
    A[&quot;Developer runs the snarkjs setup but skips Phase 2, the delta contribution&quot;] --&amp;gt; B[&quot;Verification key ships with delta and gamma both the G2 generator placeholder&quot;]
    B --&amp;gt; C{&quot;Does the pairing check still depend on the public inputs?&quot;}
    C --&amp;gt;|&quot;delta equals gamma (the bug)&quot;| D[&quot;Pairing equation collapses into a tautology&quot;]
    D --&amp;gt; E[&quot;Attacker forges a proof for any public inputs, with no witness&quot;]
    E --&amp;gt; F[&quot;Mixer pays out: 29 forged withdrawals drain the pool&quot;]
    C --&amp;gt;|&quot;Phase 2 run, delta randomized (the fix)&quot;| G[&quot;Forged proof rejected&quot;]
&lt;p&gt;Where does an identical, on-chain-verifiable mistake come from twice in one week? zkSecurity traced the shared root cause to the tooling. Both projects used Circom with snarkjs, and in both &quot;something went extremely wrong in the setup phase ... They simply didn&apos;t have any contributions to the second phase of the setup ceremony&quot; [@zksecurity-groth16]. snarkjs initializes a new key with &lt;code&gt;gamma2&lt;/code&gt; and &lt;code&gt;delta2&lt;/code&gt; set to the same placeholder -- the G2 generator -- and expects the developer to run &lt;code&gt;snarkjs zkey contribute&lt;/code&gt;, the Phase-2 step that randomizes &lt;code&gt;delta&lt;/code&gt; while leaving &lt;code&gt;gamma&lt;/code&gt; alone.&lt;/p&gt;
&lt;p&gt;Skip that one step and the key ships with &lt;code&gt;delta = gamma = G2 generator&lt;/code&gt;, collapsing the pairing check into a tautology [@zksecurity-groth16].The bug is a missing ceremony step with a valid-looking default, which is exactly why it survives testing: honest proofs verify perfectly. zkSecurity shows the tell in one line -- run the snarkjs quick-start &lt;em&gt;without&lt;/em&gt; the contribution step and &lt;code&gt;jq &apos;.vk_gamma_2&apos;&lt;/code&gt; and &lt;code&gt;jq &apos;.vk_delta_2&apos;&lt;/code&gt; print the identical BN254 generator [@zksecurity-groth16]. A reviewer who compares those two points catches the collapse before the verifier ever holds value.&lt;/p&gt;
&lt;p&gt;Six days after Veil Cash, the identical bug was pointed at FOOMCASH, a larger &quot;ZKProof-powered&quot; lottery whose Groth16 verifier had shipped on both Ethereum and Base with &lt;code&gt;gamma&lt;/code&gt; and &lt;code&gt;delta&lt;/code&gt; set to the same point [@quillaudits-foom, @foomclub-poc].&lt;/p&gt;
&lt;p&gt;QuillAudits puts the exposure at about $1.3M in FOOM on Ethereum plus about $316K on Base -- roughly $1.6M by that accounting -- drained by algebraically forged proofs &quot;without possessing any legitimate deposits or private witness&quot;; other trackers report the gross across both chains as high as about $2.26M [@quillaudits-foom, @rekt-unfinished]. CertiK diagnosed the &lt;code&gt;delta2 == gamma2&lt;/code&gt; collapse within hours, and BlockSec flagged the event as a &quot;copycat&quot; imitation of Veil Cash -- a published post-mortem turned into an attack blueprint inside a week [@rekt-unfinished].&lt;/p&gt;
&lt;p&gt;Here the disposition tag matters as much as the mechanism, and it is the reason this cluster gets its own tier. Neither drain was a permanent theft. Veil Cash&apos;s funds were recovered in full: Decurity rescued the remaining pools, and the original actor returned the 2.9 ETH unprompted [@rekt-unfinished]. FOOMCASH was substantially white-hat-rescued -- about $1.84M, roughly 80 percent of the $2.26M gross -- and whether it was ever malicious is disputed across the primaries: zkSecurity states flatly that &quot;some websites reported the issue as an attack, which was not the case,&quot; while QuillAudits frames the same event as a drain [@zksecurity-groth16, @quillaudits-foom, @rekt-unfinished].&lt;/p&gt;
&lt;p&gt;What no source disputes is that a broken verifier moved real money on-chain. Tag both &lt;strong&gt;exploited-then-recovered&lt;/strong&gt; -- a distinct middle tier, neither a clean near-miss nor a permanent theft. And note what the cluster does to the thesis: it sharpens it. No curve broke and no Groth16 theorem failed; the deployed verification key violated the $\gamma \ne \delta$ separation the theorem assumes, so this is a verifier mismatch, not a pure in-circuit under-constraint.&lt;/p&gt;

&quot;The first two known exploits against live ZK circuits happened in the past week ... some websites reported the issue as an attack, which was not the case.&quot; -- zkSecurity, &quot;The Groth16 Trusted Setup Exploit,&quot; 2026
&lt;p&gt;On October 6, 2022, a verifier-layer failure produced the largest confirmed on-chain theft in this record -- and the largest ever traced to a proof-verification bug, one layer over from any zk-SNARK. The BNB Chain Token Hub bridge relied on a precompiled contract that verified IAVL Merkle inclusion proofs, a Cosmos-SDK proof format. As samczsun&apos;s analysis showed, the verification code validated the Merkle path against only one branch and skipped &lt;code&gt;lpath.Right&lt;/code&gt;, letting an attacker splice in an empty &quot;phantom node&quot; so that a crafted proof reproduced the expected root and certified a cross-chain deposit that had never happened [@samczsun-bnb, @emilianobonassi-bnb].&lt;/p&gt;
&lt;p&gt;The forgery minted roughly 2,000,000 BNB, nominally around $566 to $586 million; validators halted the chain before most of it escaped, limiting the funds bridged off BNB Chain to an estimated $100 million and up [@coinbase-bnb].The figure of about 2,000,000 BNB minted is consistent across write-ups; the nominal dollar value (~$566 to $586 million) and the amount successfully moved off-chain before the halt (reported between roughly $100 and $127 million) vary by source [@coinbase-bnb].&lt;/p&gt;

flowchart TD
    A[&quot;Attacker crafts a deposit that never happened&quot;] --&amp;gt; B[&quot;Builds an IAVL Merkle proof for it&quot;]
    B --&amp;gt; C[&quot;Splices in an empty phantom leaf node&quot;]
    C --&amp;gt; D{&quot;Does the verifier check both branches of the path?&quot;}
    D --&amp;gt;|&quot;Skips lpath.Right (the bug)&quot;| E[&quot;Crafted proof reproduces the expected root&quot;]
    E --&amp;gt; F[&quot;Bridge certifies the deposit, mints about 2,000,000 BNB&quot;]
    D --&amp;gt;|&quot;Checks both branches (the fix)&quot;| G[&quot;Forged proof rejected&quot;]
&lt;p&gt;That was, and remains, the &lt;em&gt;largest&lt;/em&gt; confirmed theft in this record. On June 14, 2026, a newer confirmed theft reached a zero-knowledge circuit. An attacker drained about $2.19 million from a deprecated, immutable Aztec Connect contract, &lt;code&gt;RollupProcessorV3&lt;/code&gt;, in one atomic transaction [@slowmist-aztec, @newsbtc-aztec]. SlowMist&apos;s forensic post-mortem names the mechanism a &lt;em&gt;ZK-rollup settlement-boundary bypass&lt;/em&gt; [@slowmist-aztec].&lt;/p&gt;
&lt;p&gt;The shape is worth tracing. The rollup&apos;s proof committed to 32 public-input slots, but the Layer-1 settlement contract processed only the first, indexed by an attacker-controlled &lt;code&gt;numRealTxs&lt;/code&gt; value read straight from calldata with no on-chain constraint [@slowmist-aztec, @cryptotimes-aztec]. Between the two counts sat a gap of unused slots, and SlowMist says the circuit constraint that gap slots&apos; &lt;code&gt;publicValue&lt;/code&gt; should be zero was &quot;either bypassed or not imposed&quot; [@slowmist-aztec].&lt;/p&gt;
&lt;p&gt;So the attacker packed the gap with forged deposits, minted unbacked balance across fourteen &lt;code&gt;processRollup&lt;/code&gt; calls -- seven mints, then seven withdrawals -- and cashed it out as real ETH, DAI, and wstETH [@slowmist-aztec, @cryptotimes-aztec]. SlowMist&apos;s summary is the thesis in a sentence: &quot;when the ZK circuit constraint is missing, the L1 contract layer also fails to detect the issue&quot; [@slowmist-aztec].&lt;/p&gt;

flowchart TD
    A[&quot;Attacker sets numRealTxs from calldata, no on-chain constraint&quot;] --&amp;gt; B[&quot;ZK proof commits all 32 public-input slots&quot;]
    A --&amp;gt; C[&quot;L1 settlement loop traverses only the first slot&quot;]
    B --&amp;gt; D{&quot;Circuit forces gap-slot publicValue to zero?&quot;}
    D --&amp;gt;|&quot;Either bypassed or not imposed&quot;| E[&quot;Gap slots 2..32 carry forged deposits&quot;]
    E --&amp;gt; F[&quot;Mint unbacked L2 balance across 14 processRollup calls&quot;]
    F --&amp;gt; G[&quot;Withdraw real ETH, DAI, wstETH from L1&quot;]
    D --&amp;gt;|&quot;Constraint present (the fix)&quot;| H[&quot;Gap slots forced empty, forgery rejected&quot;]

This record is non-exhaustive and still growing, so read it by category, not by count. The incident-by-incident accounting, with figures and disposition tags, is the ledger in the next section; what matters here is the shape of the conclusion: these are artifact and accounting failures, never a solved curve or a reversed hash.
&lt;p&gt;The verifier can be wrong on its own. But there is a third layer, subtler than either the circuit or the checker -- the one that turned Solana&apos;s proof into a forgery, and the one with the longest history of quietly re-shipping.&lt;/p&gt;
&lt;h3&gt;The transcript hashes too little&lt;/h3&gt;

The **Fiat-Shamir transform**, introduced by Amos Fiat and Adi Shamir in 1986, turns an interactive proof into a non-interactive one [@fiat-shamir-86]. It applies to *public-coin* protocols -- ones in which every message the verifier sends is nothing but fresh public randomness. Instead of waiting for that random challenge, the prover computes it as a hash of the conversation so far -- the *transcript*. Because the hash stands in for the verifier&apos;s coins, every public value must be fixed *before* the hash is taken; a hash is unpredictable, so the prover cannot steer it, as long as it covers everything it must.
&lt;p&gt;That last clause is the whole ballgame.&lt;/p&gt;

**Strong Fiat-Shamir** hashes the domain separator, the statement being proved, and *every* public value into the challenge. **Weak Fiat-Shamir** hashes only some of them, often just the prover&apos;s first message. The gap is the vulnerability: if a public value is left out of the hash, a malicious prover can vary that value freely, grind until the challenge is favorable, and forge a proof.

sequenceDiagram
    participant P as Malicious prover
    participant H as Fiat-Shamir hash
    Note over P,H: Weak FS hashes the commitment but omits a public value
    P-&amp;gt;&amp;gt;H: Send commitment only
    H-&amp;gt;&amp;gt;P: Challenge derived from too little
    P-&amp;gt;&amp;gt;P: Vary the omitted public value, recompute
    Note over P: Grind until the challenge suits a false statement
    P-&amp;gt;&amp;gt;H: Submit forged proof
    Note over P,H: Strong FS binds statement plus all public inputs, blocking the grind
&lt;p&gt;In April 2022, Trail of Bits published a coordinated disclosure of exactly this mistake across three different proof systems -- Girault&apos;s proof of knowledge, &lt;a href=&quot;https://paragmali.com/blog/the-discrete-log-held-the-proofs-leaked-a-field-guide-to-com/&quot; rel=&quot;noopener&quot;&gt;Bulletproofs&lt;/a&gt;, and PlonK [@tob-frozenheart-p1].&lt;/p&gt;

&quot;We&apos;ve dubbed this class of vulnerabilities Frozen Heart. The word frozen is an acronym for FoRging Of ZEro kNowledge proofs.&quot; -- Trail of Bits, April 2022
&lt;p&gt;The Bulletproofs instance was especially telling: it &quot;stem[med] from a mistake in the original academic paper,&quot; whose authors had recommended an insecure Fiat-Shamir implementation that omitted a Pedersen commitment from the hash [@tob-frozenheart-bp, @bulletproofs-2017-1066]. That is the instance the class&apos;s CVE was filed against: CVE-2022-29566 names the Bulletproofs 2017/1066 paper, which &quot;mishandles Fiat-Shamir generation because the hash computation fails to include all of the public values&quot; [@mitre-cve-2022-29566, @cve-2022-29566]. The PlonK instances shared the same shape, leaving public inputs out of the transcript [@tob-frozenheart-plonk].The pitfall was not new. Bernhard, Pereira, and Warinschi had drawn the weak-versus-strong distinction a decade earlier and shown that the weak form broke the Helios electronic-voting system [@bpw-2016-771]. The same one-line omission then recurs across four eras: Helios in 2012, Frozen Heart in 2022, Solana in 2025, and six zkVMs in 2026.&lt;/p&gt;
&lt;p&gt;This was a proactive disclosure: affected parties were notified and had remediated before publication. A near-miss, class-wide.&lt;/p&gt;
&lt;p&gt;How common is the class? A peer-reviewed systematization by Dao, Miller, Wright, and Grubbs surveyed open-source code and found 36 weak Fiat-Shamir implementations affecting 12 different proof systems [@dao-2023-691]. Not an anecdote -- a recurring omission with a name.&lt;/p&gt;
&lt;p&gt;And here the article&apos;s opening snaps shut. The Solana ZK ElGamal bug from June 2025 was this exact root cause: a component left out of the Fiat-Shamir transcript hash, following an earlier instance in the same program reported that May [@solana-june-2025, @solana-may-2025]. But the story did not stop at Solana. In early 2026 -- March, by the disclosure timeline that runs from the first report in September 2025 -- OtterSec published &quot;Unfaithful claims: breaking 6 zkVMs,&quot; reporting the &lt;em&gt;same&lt;/em&gt; omission one layer up, inside the verifiers of six production zero-knowledge virtual machines: Jolt, Nexus, Cairo-M, Ceno, Expander, and Binius64 [@osec-zkvms].&lt;/p&gt;

&quot;...public-claim data was not always bound into Fiat-Shamir transcripts before challenge generation ... We are breaking soundness in all six systems.&quot; -- OtterSec, &quot;Unfaithful claims: breaking 6 zkVMs,&quot; 2026
&lt;p&gt;The public claim the proof was &lt;em&gt;about&lt;/em&gt; -- the statement itself -- was not always hashed into the transcript before the challenge was drawn, exactly the ordering invariant the transform depends on. To make the break concrete, the researchers proved mathematically impossible statements -- a counterexample to Fermat&apos;s Last Theorem among them -- against reference implementations, noting that in a blockchain context such a forgery &quot;could translate to receiving $1M out of thin air&quot; [@osec-zkvms]. Every case was responsibly disclosed and fixed between September 2025 and March 2026, in reference and production code, with no realized on-chain theft -- a transcript-layer near-miss, six times over.&lt;/p&gt;
&lt;p&gt;Trace the thread and you get four eras of one omission -- Helios in 2012 [@bpw-2016-771], Frozen Heart in 2022 [@tob-frozenheart-p1], Solana in 2025 [@solana-june-2025], and the zkVMs in 2026 [@osec-zkvms]. The same missing line of code, re-shipped for fourteen years, each time in a more powerful system than the last.&lt;/p&gt;
&lt;p&gt;Three layers, three ways to accept a false proof -- and a pattern nagging underneath all of them. Every break we have seen has the same shape. What is it?&lt;/p&gt;
&lt;h2&gt;5. One Lens for Every Break&lt;/h2&gt;
&lt;p&gt;Line the incidents up -- Zcash, Dark Forest, Aztec, Frozen Heart, Solana, the 2026 zkVMs, and the BNB bridge next door -- and a single sentence explains all of them.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; Name the artifact layer -- construction, circuit, verifier, or transcript -- and you have named the break. Across the entire disclosed record, the cryptographic hardness floor never appears. No incident attacked the discrete logarithm, a pairing, or a hash. Every one attacked the human-built layer above them.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That is the whole thesis, stated as a sorting rule. It is falsifiable -- one confirmed break of the discrete-log or pairing assumption in a shipping system would refute it -- and so far nothing in the record refutes it.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; This is not only a reading of a handful of anecdotes. The SoK 2024 study of 141 real-world SNARK vulnerabilities found under-constrained circuits to be the single most prevalent category, and the disclosed corpus is overwhelmingly made of &lt;em&gt;soundness&lt;/em&gt; bugs rather than privacy or completeness failures [@chaliasos-sok-arxiv, @chaliasos-sok-usenix]. The pattern the incidents suggest, the data confirms. And the record is non-exhaustive by design -- new incidents keep arriving -- so what holds is the invariant, not the count: the layer explains the break, every time.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;You do not have to take the sorting rule on faith. Here is the ledger -- a non-exhaustive but growing record -- laid out so you can audit the claim yourself. Read the &quot;layer&quot; column and the &quot;tag&quot; column together: the clean pure-circuit cases are near-misses, the Groth16 verifier-key drains were exploited and then recovered, and every &lt;em&gt;confirmed&lt;/em&gt; theft lives in a verifier, a settlement boundary, or integration code. BNB remains the largest confirmed theft.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Incident&lt;/th&gt;
&lt;th&gt;Year&lt;/th&gt;
&lt;th&gt;Artifact layer&lt;/th&gt;
&lt;th&gt;Tag&lt;/th&gt;
&lt;th&gt;What it enabled&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Zcash counterfeiting&lt;/td&gt;
&lt;td&gt;2018-19&lt;/td&gt;
&lt;td&gt;Construction&lt;/td&gt;
&lt;td&gt;Near-miss&lt;/td&gt;
&lt;td&gt;Forge shielded coins from a redundant CRS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dark Forest RangeProof&lt;/td&gt;
&lt;td&gt;~2021&lt;/td&gt;
&lt;td&gt;Circuit&lt;/td&gt;
&lt;td&gt;Near-miss&lt;/td&gt;
&lt;td&gt;Prove an out-of-range coordinate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Aztec 2.0 nullifier&lt;/td&gt;
&lt;td&gt;~2022&lt;/td&gt;
&lt;td&gt;Circuit&lt;/td&gt;
&lt;td&gt;Near-miss&lt;/td&gt;
&lt;td&gt;Spend the same note multiple times&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Circom-Pairing&lt;/td&gt;
&lt;td&gt;~2022&lt;/td&gt;
&lt;td&gt;Circuit&lt;/td&gt;
&lt;td&gt;Near-miss&lt;/td&gt;
&lt;td&gt;Forge signatures on a pairing library&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Aztec PlonK &quot;0-bug&quot;&lt;/td&gt;
&lt;td&gt;~2022&lt;/td&gt;
&lt;td&gt;Verifier&lt;/td&gt;
&lt;td&gt;Near-miss&lt;/td&gt;
&lt;td&gt;Accept forged proofs via a zero encoding&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Frozen Heart (Bulletproofs, PlonK)&lt;/td&gt;
&lt;td&gt;2022&lt;/td&gt;
&lt;td&gt;Transcript&lt;/td&gt;
&lt;td&gt;Near-miss&lt;/td&gt;
&lt;td&gt;Forge proofs for false statements&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Solana ZK ElGamal&lt;/td&gt;
&lt;td&gt;2025&lt;/td&gt;
&lt;td&gt;Transcript&lt;/td&gt;
&lt;td&gt;Near-miss&lt;/td&gt;
&lt;td&gt;Mint unlimited tokens or drain balances&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Six zkVMs (Jolt, Nexus, Cairo-M, Ceno, Expander, Binius64)&lt;/td&gt;
&lt;td&gt;2026&lt;/td&gt;
&lt;td&gt;Transcript&lt;/td&gt;
&lt;td&gt;Near-miss&lt;/td&gt;
&lt;td&gt;Forge proofs by an unbound public claim&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Zcash Orchard counterfeiting&lt;/td&gt;
&lt;td&gt;2026&lt;/td&gt;
&lt;td&gt;Circuit&lt;/td&gt;
&lt;td&gt;Near-miss&lt;/td&gt;
&lt;td&gt;Forge unlimited shielded ZEC via an under-constrained EC-mult gadget&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;BNB Token Hub&lt;/td&gt;
&lt;td&gt;2022&lt;/td&gt;
&lt;td&gt;Verifier (Merkle, adjacent)&lt;/td&gt;
&lt;td&gt;Confirmed theft&lt;/td&gt;
&lt;td&gt;Mint about 2,000,000 BNB via a phantom node&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Veil Cash&lt;/td&gt;
&lt;td&gt;2026&lt;/td&gt;
&lt;td&gt;Verifier (trusted setup)&lt;/td&gt;
&lt;td&gt;Exploited, recovered&lt;/td&gt;
&lt;td&gt;Forge Groth16 withdrawals after &lt;code&gt;delta2 == gamma2&lt;/code&gt;, drain 2.9 ETH, returned in full&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FOOMCASH&lt;/td&gt;
&lt;td&gt;2026&lt;/td&gt;
&lt;td&gt;Verifier (trusted setup)&lt;/td&gt;
&lt;td&gt;Exploited, recovered&lt;/td&gt;
&lt;td&gt;Copycat Groth16 &lt;code&gt;gamma == delta&lt;/code&gt;, about $2.26M gross, about 80 percent white-hat-recovered&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Aztec Connect&lt;/td&gt;
&lt;td&gt;2026&lt;/td&gt;
&lt;td&gt;Circuit + settlement&lt;/td&gt;
&lt;td&gt;Confirmed theft&lt;/td&gt;
&lt;td&gt;Mint unbacked L2 balance through gap slots whose zero constraint was either bypassed or not imposed, drain about $2.19M&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hinkal&lt;/td&gt;
&lt;td&gt;2026&lt;/td&gt;
&lt;td&gt;Verifier integration&lt;/td&gt;
&lt;td&gt;Reported&lt;/td&gt;
&lt;td&gt;Reported proofless deposit accepted without a ZK proof, about $820K laundered&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The money framing needs the same care. The artifact is what leaked, and where it guarded a rollup, a pool, or a bridge, the leak was measured in funds minted or drained. &quot;The bugs that minted money&quot; is discharged by reading the ledger &lt;em&gt;by category&lt;/em&gt;, not by count: the permanent thefts are BNB Token Hub -- still the &lt;em&gt;largest&lt;/em&gt; -- and Aztec Connect [@coinbase-bnb, @slowmist-aztec]; the Veil Cash and FOOMCASH verifier-key drains were exploited-then-recovered, not permanent theft [@darknavy-veil, @quillaudits-foom, @rekt-unfinished]; and Hinkal is &lt;em&gt;reported&lt;/em&gt;, its &quot;proofless deposit&quot; root cause resting on a security firm&apos;s reading with a full post-mortem still to follow, its loss put at roughly $800,000 to $830,000, so this article uses about $820,000 [@dextools-hinkal, @cryptotimes-hinkal].&lt;/p&gt;
&lt;p&gt;Every one reinforces the scoped claim rather than disturbing it: each is a verifier, settlement, trusted-setup, or integration mismatch. What the record still shows &lt;em&gt;no&lt;/em&gt; confirmed theft of is the pure case -- an in-circuit under-constraint &lt;em&gt;independent of&lt;/em&gt; any settlement, verifier, setup, or integration mismatch. That is not luck to rely on; it is a fact about the record, and the record is still short.&lt;/p&gt;
&lt;p&gt;A lens that names every break is worth little if you cannot act on it. So the field built tools -- one for each layer. How well do they work?&lt;/p&gt;
&lt;h2&gt;6. How the Industry Hunts the Bug Now&lt;/h2&gt;
&lt;p&gt;Each defense in use today is a scar -- a direct response to a documented failure. Read them from most sound to most human, and keep one tension in view the whole way down. The tools that can &lt;em&gt;prove&lt;/em&gt; a circuit is properly constrained cannot yet run on production-size circuits, and the tools that run on production-size circuits cannot prove anything. No deployed tool is both. Call it the sound-versus-scalable frontier.&lt;/p&gt;
&lt;h3&gt;Sound uniqueness verification&lt;/h3&gt;
&lt;p&gt;The flagship family targets the dominant bug class head-on.&lt;/p&gt;

**Formal verification** of a circuit proves a property holds for *all* possible inputs, rather than checking a few. For under-constraint, the property is *uniqueness*: every output signal is determined by the inputs, so no second witness exists. A sound tool either proves uniqueness or hands back a concrete counterexample -- two distinct witnesses for the same input, the $y \cdot y = x$ signature made mechanical.
&lt;p&gt;The lineage runs from a first sketch to increasingly capable engines. &lt;strong&gt;Ecne&lt;/strong&gt; was the first widely known open checker that R1CS outputs are uniquely determined by inputs [@ecne-github, @ecne-writeup].Ecne carries a caveat that sharpens what &quot;sound&quot; means: it is a &lt;em&gt;partial&lt;/em&gt; prover. A &lt;code&gt;false&lt;/code&gt; result &quot;does not mean that the constraints aren&apos;t sound, it just means that Ecne is unable to prove&quot; they are [@ecne-github]. Picus, CIVER, and ConsCS go further, returning an actual witness pair.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Picus / QED2&lt;/strong&gt; made it quantitative, pairing an SMT solver with uniqueness inference to either prove a signal is determined or return a two-witness exploit; it resolved about 70 percent of a 163-circuit Circom benchmark and found 8 previously unknown vulnerabilities [@picus-2023-512, @picus-github, @pldi23-picus]. &lt;strong&gt;CIVER&lt;/strong&gt; checks uniqueness compositionally, verifying a gadget once and reusing it, from inside the Circom compiler [@civer-github]. &lt;strong&gt;ConsCS&lt;/strong&gt; is the current accuracy leader on Circom, lifting the solved rate above 80 percent [@conscs-polyu]. And &lt;strong&gt;halo2-analyzer&lt;/strong&gt; (also called korrekt) carries the whole approach beyond R1CS to Plonkish and Halo2 circuits [@halo2-analyzer-eprint, @halo2-analyzer-github].&lt;/p&gt;
&lt;p&gt;The governing bound is negative and structural. What these tools actually decide is &lt;em&gt;non-uniqueness&lt;/em&gt;: they search for a second, distinct witness that satisfies the same constraints on the same input, and that witness &lt;em&gt;pair&lt;/em&gt; is the certificate. Finding one is an NP search, because R1CS satisfiability is itself NP-complete -- so a sound checker cannot also be complete and efficient on every circuit, barring $P = NP$ [@picus-2023-512].&lt;/p&gt;
&lt;p&gt;Deciding &lt;em&gt;uniqueness itself&lt;/em&gt; is harder still -- it asks both that a witness exists and that no second one does, which places it above NP, in the class of differences of NP sets; the canonical Unique-SAT problem there is complete under randomized reductions [@papadimitriou-yannakakis-1984, @blass-gurevich-1982, @valiant-vazirani-1986]. Either way the conclusion is the same and unavoidable: every tool in this family is &lt;em&gt;sound but incomplete&lt;/em&gt;. It may answer &quot;unknown&quot; or time out. That is not an implementation gap; it is the shape of the problem.&lt;/p&gt;

flowchart LR
    subgraph SOUND[&quot;Sound: a clean result proves absence&quot;]
        S1[&quot;Ecne, Picus, CIVER, ConsCS, halo2-analyzer -- run on gadgets&quot;]
    end
    subgraph SCALE[&quot;Scalable: runs on hundreds of production circuits&quot;]
        S2[&quot;ZKAP, Circomspect -- lint, but no proof of absence&quot;]
    end
    S1 -. &quot;no tool sits in both boxes&quot; .-&amp;gt; S2
&lt;h3&gt;Scalable static analysis&lt;/h3&gt;
&lt;p&gt;When you cannot prove, you can still scan. &lt;strong&gt;ZKAP&lt;/strong&gt; implements nine detectors over a &lt;em&gt;circuit dependence graph&lt;/em&gt; and ran across 258 circuits from popular Circom projects, surfacing previously unknown vulnerabilities [@zkap-2023-190, @zkap-usenix]. Trail of Bits&apos; &lt;strong&gt;Circomspect&lt;/strong&gt; is a linter that flags the tell-tale patterns -- a signal assigned with &lt;code&gt;&amp;lt;--&lt;/code&gt; but never constrained with &lt;code&gt;&amp;lt;==&lt;/code&gt;, or an output left unconstrained [@circomspect]. Both scale to hundreds of circuits, and both are &lt;em&gt;unsound&lt;/em&gt;: a clean scan means &quot;no known-bad pattern found,&quot; not &quot;no bug.&quot;&lt;/p&gt;
&lt;h3&gt;Prevention and the human net&lt;/h3&gt;
&lt;p&gt;Two more defenses sit at the ends of the spectrum. At the preventive end, &lt;strong&gt;strong-Fiat-Shamir transcript libraries&lt;/strong&gt; bind the domain separator, the statement, and every public input by construction, removing the avoidable Frozen Heart class before it can be written. The 2026 zkVM breaks named the state-of-practice version precisely: &lt;em&gt;merge the proof and transcript&lt;/em&gt; so every value the prover sends is absorbed before any challenge is drawn -- &quot;Halo2 follows this pattern, and Binius is transcript-centric as well,&quot; though even transcript-centric Binius still missed binding the statement [@osec-zkvms].&lt;/p&gt;
&lt;p&gt;Alongside it sits a newer, humbler discipline: &lt;strong&gt;trusted-setup ceremony hygiene as a shipped step&lt;/strong&gt; -- confirming the deployed verification key actually finished Phase 2, with $\delta$ independent of $\gamma$, before it holds value. That is prevention by checklist, not a formal tool, but it is exactly the gap Veil Cash and FOOMCASH walked through, and a one-line check would have closed it [@zksecurity-groth16].&lt;/p&gt;
&lt;p&gt;At the human end sit &lt;strong&gt;specialist audits and bug bounties&lt;/strong&gt; -- the last net, and the one that actually caught the headline cases. The Solana report arrived through disclosure; so did the BNB analysis and the 2026 Groth16 drains [@solana-june-2025, @rekt-unfinished]. Firms like Veridise operationalize the sound branch commercially, folding uniqueness checkers into human audits of Circom, Halo2, and other circuits [@veridise-audits].&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Run a fast linter (Circomspect, ZKAP) on every commit to catch the obvious absences [@circomspect]. Reserve sound uniqueness checkers (Picus) for the small, high-value gadgets where a proof of absence justifies the runtime [@picus-github]. Before deployment, confirm the trusted setup actually finished -- that the verification key&apos;s $\delta$ is not still equal to $\gamma$ [@zksecurity-groth16]. Commission a specialist audit before mainnet, and keep a bounty live afterward -- it is how the Solana bug surfaced [@solana-june-2025].&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So we have sound tools that do not scale and scalable tools that are not sound. Which philosophy should a team betting real money actually trust?&lt;/p&gt;
&lt;h2&gt;7. Four Philosophies, No Winner&lt;/h2&gt;
&lt;p&gt;This is where the zero-knowledge security community actually argues. Not over which tool is best -- over which &lt;em&gt;kind&lt;/em&gt; of tool deserves your trust when funds are on the line. Four camps coexist, and the honest answer is that none of them wins outright.&lt;/p&gt;
&lt;p&gt;The first axis is soundness versus scale, which we have already met. Sound formal tools like Picus prove output uniqueness or hand you a two-witness exploit, but only on gadgets; scalable analyzers like Circomspect and ZKAP run across whole projects but can only flag patterns they already know.&lt;/p&gt;
&lt;p&gt;The second axis is prevention versus detection: strong-Fiat-Shamir libraries and constraint-safer languages try to make a class of bug &lt;em&gt;unwritable&lt;/em&gt;, while linters and verifiers hunt bugs in artifacts that already exist.&lt;/p&gt;
&lt;p&gt;The third axis is human versus automated versus economic: an expert audit reasons about intent, a tool reasons about rules, and a bug bounty pays outsiders to find what both missed.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;Sound?&lt;/th&gt;
&lt;th&gt;Scales?&lt;/th&gt;
&lt;th&gt;Prevents or detects&lt;/th&gt;
&lt;th&gt;Catches&lt;/th&gt;
&lt;th&gt;Misses&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Sound uniqueness verification&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Detects&lt;/td&gt;
&lt;td&gt;Under-constrained circuits, with a counterexample&lt;/td&gt;
&lt;td&gt;Anything past its timeout; non-circuit layers&lt;/td&gt;
&lt;td&gt;Picus&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Static analysis / linting&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Detects&lt;/td&gt;
&lt;td&gt;Known-bad patterns across many circuits&lt;/td&gt;
&lt;td&gt;Novel absences it has no rule for&lt;/td&gt;
&lt;td&gt;Circomspect, ZKAP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Strong Fiat-Shamir by construction&lt;/td&gt;
&lt;td&gt;n/a&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Prevents&lt;/td&gt;
&lt;td&gt;The avoidable weak-transcript class&lt;/td&gt;
&lt;td&gt;Circuit and verifier bugs&lt;/td&gt;
&lt;td&gt;Strong-FS libraries&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Expert audit&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Partly&lt;/td&gt;
&lt;td&gt;Detects&lt;/td&gt;
&lt;td&gt;What a skilled reviewer reasons through&lt;/td&gt;
&lt;td&gt;What the reviewer never thinks to check&lt;/td&gt;
&lt;td&gt;Veridise&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bug bounty&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Detects after ship&lt;/td&gt;
&lt;td&gt;Bugs a motivated outsider finds&lt;/td&gt;
&lt;td&gt;Everything, until value is already at risk&lt;/td&gt;
&lt;td&gt;Solana, BNB disclosures&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Read the table as a set of holes, not a ranking. The sound tools miss everything past their timeout and every layer that is not the circuit. The linters miss the novel absence they have no rule for. Prevention closes only the class it was designed for. Audits miss what the auditor does not think to check. And a bounty, by definition, reports a bug that already shipped.&lt;/p&gt;
&lt;p&gt;The Veil Cash-to-FOOMCASH sequence makes the sharpest case against trusting any single net. The first drain was disclosed with a full technical write-up; six days later the same broken pattern was aimed at a larger target, so a published post-mortem had become an attack -- and rescue -- blueprint inside a week [@zksecurity-groth16, @rekt-unfinished].&lt;/p&gt;
&lt;p&gt;When a bug class&apos;s signature is &lt;em&gt;a missing step with a valid-looking default&lt;/em&gt;, it is trivially easy to reintroduce and just as easy for a copycat to find once the first case is public. Detection after the fact cannot keep pace with that; only prevention -- a Phase-2 check on every deployed key -- gets ahead of it.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Both the Solana and BNB reports arrived through disclosure -- which is to say, after the code was live [@solana-june-2025]. The 2026 Groth16 drains are the sharper lesson: white-hats returned most or all of the money, but only &lt;em&gt;after&lt;/em&gt; the broken verifier had already moved it on-chain [@rekt-unfinished]. A bounty is the last line, and a necessary one, but a bug caught by a bounty is a bug that was already exposed. Treat it as a safety net under the other defenses, never as a substitute for them.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Because the bug is an absence, and absences spread across three layers, no single method can promise to catch them. So the working consensus is not a winner but a discipline: &lt;strong&gt;defense-in-depth, one net per bug type.&lt;/strong&gt; Lint every commit, prove the gadgets, prevent the avoidable class by construction, audit before mainnet, and keep a bounty live after. Each net has holes; the bet is that the holes do not line up.&lt;/p&gt;
&lt;p&gt;Defense-in-depth buys coverage, not certainty. Push on &lt;em&gt;why&lt;/em&gt; no single tool closes the gap, and you hit something deeper than engineering effort -- a wall.&lt;/p&gt;
&lt;h2&gt;8. Why You Cannot Test It Away&lt;/h2&gt;
&lt;p&gt;Suppose every team ran every tool on this list. The bug class still would not disappear -- and the reasons are theorems, not to-do items.&lt;/p&gt;
&lt;p&gt;The first ceiling is the one we met in Section 3, now stated in full generality. A missing constraint is an &lt;em&gt;absence&lt;/em&gt;, not a behavior you can enumerate. Testing samples witnesses and checks that valid ones produce valid proofs; but the illegitimate witnesses live in exactly the region the missing equation was supposed to forbid, so no finite suite of positive tests ever reaches them. You cannot sample your way to a constraint that is not there.&lt;/p&gt;
&lt;p&gt;The second ceiling is the sound-versus-scalable frontier, and it is structural rather than temporary. Detecting under-constraint means searching for a second, distinct satisfying witness -- an NP-hard search, since R1CS satisfiability is NP-complete -- so a sound checker cannot also run efficiently on every input, and deciding uniqueness outright is harder still [@picus-2023-512, @papadimitriou-yannakakis-1984].&lt;/p&gt;
&lt;p&gt;Production circuits are enormous; sound tools resolve gadgets and time out on the rest, which is why roughly a third of even a modest 163-circuit benchmark went unresolved [@picus-2023-512]. Verify gadgets, not whole zkEVMs -- not because the tools are immature, but because the problem is hard in the complexity-theoretic sense.&lt;/p&gt;
&lt;p&gt;The third ceiling sits beneath even a perfectly constrained circuit. The Fiat-Shamir transform has no &lt;em&gt;general, unconditional&lt;/em&gt; standard-model soundness guarantee for the succinct &lt;em&gt;arguments&lt;/em&gt; that SNARKs compress: Goldwasser and Tauman Kalai showed in 2003 that the methodology can be &lt;em&gt;uninstantiable&lt;/em&gt; there, with no guarantee that any concrete hash makes it sound [@gk-2003-034].&lt;/p&gt;
&lt;p&gt;The scope of that claim is worth stating exactly, because it is easy to overstate. For a broad class of public-coin interactive &lt;em&gt;proofs&lt;/em&gt; -- the trapdoor Sigma-protocols -- Fiat-Shamir &lt;em&gt;is&lt;/em&gt; provably sound in the standard model, through correlation-intractable hash families built from the plain Learning-With-Errors assumption [@clw-2018-1248, @peikert-shiehian-2019-158]. But those results instantiate a concrete hash inside a common reference string and cover statistically sound &lt;em&gt;proofs&lt;/em&gt;; they do not reach the succinct &lt;em&gt;arguments&lt;/em&gt;, compiled under the random-oracle heuristic, that shipping SNARKs use -- which sharpens the point rather than softening it.&lt;/p&gt;
&lt;p&gt;And the risk is not academic: in 2025, Khovratovich, Rothblum, and Soukhanov exhibited a &lt;em&gt;practical&lt;/em&gt; Fiat-Shamir break on a widely studied GKR-based argument, holding &quot;for every choice of FS hash function&quot; [@khovratovich-2025-118]. Strong Fiat-Shamir removes the &lt;em&gt;avoidable&lt;/em&gt; class -- forgetting to hash a public value -- but not the heuristic risk underneath it.&lt;/p&gt;
&lt;p&gt;A fourth, quieter limit runs in the same spirit. In the plain model, with no common reference string or random oracle, non-interactive zero-knowledge exists only for languages a verifier could already decide on its own (Goldreich and Oren, 1994), so a hard-language SNARK can &lt;em&gt;relocate&lt;/em&gt; its trusted setup but never &lt;em&gt;remove&lt;/em&gt; it [@goldreich-oren-1994]. The 2026 &lt;code&gt;delta == gamma&lt;/code&gt; drains are that theorem&apos;s practical mirror: a setup cannot be wished away, only executed correctly -- and a skipped Phase-2 step is not an abstraction but a live money bug [@zksecurity-groth16].&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; The ceiling is a set of theorems, not a backlog. You cannot test an absence away, because a test only exercises what is present. No tool is both sound and scalable, because detecting under-constraint inherits NP-hardness. Strong Fiat-Shamir removes the avoidable mistake without removing the heuristic beneath it. And the trusted setup can be moved but not eliminated. Better tools will help. They will not make the bug class vanish.&lt;/p&gt;
&lt;/blockquote&gt;

The full derivations belong to the structural sibling, [*A Valid Proof of a False Thing*](/blog/a-valid-proof-of-a-false-thing-how-zero-knowledge-proofs-act/): why Fiat-Shamir is uninstantiable in the standard model for the arguments SNARKs use [@gk-2003-034], how a 2025 result forges proofs on a GKR-based argument for every choice of hash function [@khovratovich-2025-118], and why the plain model forces a setup at all [@goldreich-oren-1994]. Two caveats travel with them. Strong Fiat-Shamir closes the mistake of forgetting a public value, not the heuristic risk itself. And the hardness floor is unbroken *so far*, not unbreakable: a STARK&apos;s post-quantum security is a conjecture, not a proof [@starks-2018-046], and a large enough quantum computer running Shor&apos;s algorithm is the one clocked risk to the pairing-based floor.
&lt;p&gt;That last caveat is the article&apos;s other standing hedge. &quot;The crypto never broke&quot; is a statement about the record to date, not a permanence guarantee. The floor has held in every disclosed case. It has not been proven unbreakable, and one class of it -- the algebraic, pairing-based SNARKs -- has a known expiry condition if &lt;a href=&quot;https://paragmali.com/blog/how-q-day-breaks-everything-shors-algorithm-and-the-simultan/&quot; rel=&quot;noopener&quot;&gt;large-scale quantum computing&lt;/a&gt; arrives.&lt;/p&gt;
&lt;p&gt;If the bug class cannot be eliminated, the work is never &quot;done.&quot; It is &lt;em&gt;managed&lt;/em&gt;. So where is that management still failing?&lt;/p&gt;
&lt;h2&gt;9. The Seams Still Open&lt;/h2&gt;
&lt;p&gt;The frontier is a list of gaps everyone in the field can name.&lt;/p&gt;
&lt;p&gt;The first is &lt;strong&gt;scaling sound verification&lt;/strong&gt;. The unresolved tail of the uniqueness checkers -- the roughly one-third of circuits that time out -- is not random. It is exactly the large, non-linear region where production zkEVMs live [@picus-2023-512]. Closing that tail would move sound verification from gadgets to the systems that actually hold billions, and no one yet knows how.&lt;/p&gt;
&lt;p&gt;The second is &lt;strong&gt;portability across representations&lt;/strong&gt;, and it stopped being hypothetical in 2026. Most sound tooling grew up on Circom and R1CS, but the industry has moved to Plonkish arithmetization, AIR, and full zkVMs. When OtterSec broke six of those zkVMs through unbound Fiat-Shamir transcripts, none of the deployed uniqueness or anti-pattern tools would have caught it: they hunt a &lt;em&gt;missing algebraic constraint over circuit signals&lt;/em&gt;, while this was a &lt;em&gt;transcript-ordering&lt;/em&gt; defect one layer up, in the verifier&apos;s absorb-then-challenge sequence [@osec-zkvms].&lt;/p&gt;
&lt;p&gt;The construction-side answer is emerging -- a 2026 framework shows how to compose a special-sound polynomial protocol with a suitably binding commitment so that applying Fiat-Shamir yields a &lt;em&gt;tightly&lt;/em&gt; knowledge-sound SNARK [@eprint-2026-326]. But the detection-side analogue -- a sound automated checker for transcript binding in arbitrary zkVM verifier code, the Picus of the Fiat-Shamir layer -- does not yet exist, and testing cannot stand in for it: honest unit tests run the honest prover, and only manual analysis catches the omission [@osec-zkvms].&lt;/p&gt;
&lt;p&gt;The third is &lt;strong&gt;standardizing transcripts and disclosure&lt;/strong&gt;. The Frozen Heart class exists partly because there was no agreed, hard-to-misuse convention for what a Fiat-Shamir transcript must bind. Strong-FS libraries and transcript-proof fusion help, but a genuinely standard transcript format -- and a shared norm for how ZK bugs are disclosed and dated -- is still forming.&lt;/p&gt;
&lt;p&gt;Running parallel to these is a setup-side gap the 2026 drains exposed. Catching a mis-finalized trusted setup -- a verification key whose &lt;code&gt;delta&lt;/code&gt; still equals &lt;code&gt;gamma&lt;/code&gt; -- is trivial &lt;em&gt;if you know to look&lt;/em&gt;, yet there is no standard, enforced pre-deployment gate that a shipped key actually completed its ceremony [@zksecurity-groth16]. That is a tooling-and-process gap, not a theoretical one, which is what makes it both fixable and, so far, unfixed.&lt;/p&gt;
&lt;p&gt;The fourth is the hardest to see, because it is about the evidence itself.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Every prevalence claim in this article -- including &quot;under-constrained circuits are the most common bug&quot; -- is drawn from the bugs that were &lt;em&gt;found&lt;/em&gt; [@chaliasos-sok-arxiv]. The corpus is survivorship-biased by construction. It cannot count the bugs still sitting undisclosed in shipping code, and it cannot tell us whether the clean zero-knowledge record reflects genuinely sound systems or merely undiscovered ones. Read the whole article with that bound in mind.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Measuring &lt;em&gt;true&lt;/em&gt; prevalence -- not just disclosed prevalence -- would require watching for bugs that were never reported, which is close to a contradiction. It may be the one open problem here that tooling alone cannot solve.&lt;/p&gt;
&lt;p&gt;Underneath all four is a maturity gap. Circuits ship on product timelines; verification tools mature on research timelines. As long as the first clock runs faster than the second -- and it now ships whole zkVMs while the tooling is still learning to read them -- the record will keep adding near-misses, and the industry will keep hoping they stay near-misses.&lt;/p&gt;
&lt;p&gt;None of that helps the engineer shipping a circuit this quarter. So here is what you can do today.&lt;/p&gt;
&lt;h2&gt;10. A Reviewer&apos;s Checklist&lt;/h2&gt;
&lt;p&gt;Everything above collapses into a handful of questions you can ask of any zero-knowledge system before it holds value. Ask them layer by layer.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Circuit.&lt;/strong&gt; Is &lt;em&gt;every&lt;/em&gt; signal constrained, not merely assigned -- is there any &lt;code&gt;&amp;lt;--&lt;/code&gt; or &lt;code&gt;=&lt;/code&gt; without a matching &lt;code&gt;&amp;lt;==&lt;/code&gt;? Is there a range or bit-length check on every input that needs one, before any gadget that assumes it? That single question would have caught Dark Forest, whose fix was exactly a pair of &lt;code&gt;Num2Bits&lt;/code&gt; checks added ahead of a comparator [@df-fix-commit]. Is every uniqueness-critical value -- a nullifier above all -- constrained to a fixed length so it cannot take two forms [@aztec-disclosure]?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Verifier.&lt;/strong&gt; Is the on-chain verifier itself the &lt;em&gt;audited&lt;/em&gt; artifact, and is it correct at the edges -- point-at-infinity, zero encodings, on-curve membership? Aztec&apos;s circuit was not the problem; its C++ verifier accepting a zero was [@aztec-0bug].&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Trusted setup.&lt;/strong&gt; If the verifier is a Groth16-style SNARK, did its trusted setup actually finish -- is the verification key&apos;s &lt;code&gt;delta&lt;/code&gt; independent of &lt;code&gt;gamma&lt;/code&gt;, rather than the placeholder generator the two share before Phase 2? A one-line comparison of those two points would have caught Veil Cash and FOOMCASH before either held value [@zksecurity-groth16].&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Transcript.&lt;/strong&gt; Does the Fiat-Shamir transcript bind the domain separator, the statement, and &lt;em&gt;every&lt;/em&gt; public input, before any challenge is drawn? This is the one-line question behind Frozen Heart, behind Solana&apos;s 2025 near-miss, and behind the 2026 zkVM breaks [@solana-june-2025, @osec-zkvms].&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Settlement and integration.&lt;/strong&gt; Does the on-chain settlement loop cover &lt;em&gt;every&lt;/em&gt; public-input slot the circuit committed to, and is a proof &lt;em&gt;actually verified&lt;/em&gt; on every path that moves value? Aztec Connect&apos;s L1 loop read only the slots up to an attacker-controlled count while the proof committed all 32, and Hinkal was reported to have accepted a deposit with no proof at all [@slowmist-aztec, @dextools-hinkal].&lt;/p&gt;
&lt;p&gt;{`
// A toy stand-in for a transcript hash: just concatenate the bound parts.
function challenge(parts) {
  return parts.join(&apos;|&apos;);
}&lt;/p&gt;
&lt;p&gt;const commitment = &apos;C&apos;;
const statementA = &apos;honest public statement&apos;;
const statementB = &apos;false statement the prover prefers&apos;;&lt;/p&gt;
&lt;p&gt;// WEAK Fiat-Shamir: bind only the commitment. The statement is NOT bound.
const weakA = challenge([commitment]);
const weakB = challenge([commitment]);
console.log(&apos;weak:   same challenge for two statements? &apos; + (weakA === weakB));
// true -&amp;gt; the prover can swap the statement without moving the challenge.&lt;/p&gt;
&lt;p&gt;// STRONG Fiat-Shamir: bind the statement and every public input too.
const strongA = challenge([commitment, statementA]);
const strongB = challenge([commitment, statementB]);
console.log(&apos;strong: same challenge for two statements? &apos; + (strongA === strongB));
// false -&amp;gt; swapping the statement changes the challenge, blocking the forge.
`}&lt;/p&gt;
&lt;p&gt;{`
// A Groth16 key that skipped Phase 2 ships with delta == gamma, both still
// the G2 generator placeholder. Detecting it needs no pairing algebra -- just
// a point-equality check: are the two verification-key points identical?&lt;/p&gt;
&lt;p&gt;const G2_GENERATOR = [[10857046, 11559732], [8495653, 4082367]];&lt;/p&gt;
&lt;p&gt;// A key that finished Phase 2: delta was randomized, so it differs from gamma.
const goodKey = {
  vk_gamma_2: [[10857046, 11559732], [8495653, 4082367]],
  vk_delta_2: [[19824234, 55049283], [77712121, 30310670]],
};&lt;/p&gt;
&lt;p&gt;// A key that skipped Phase 2: delta is still the generator, equal to gamma.
const brokenKey = {
  vk_gamma_2: G2_GENERATOR,
  vk_delta_2: G2_GENERATOR,
};&lt;/p&gt;
&lt;p&gt;function phase2Skipped(key) {
  return JSON.stringify(key.vk_gamma_2) === JSON.stringify(key.vk_delta_2);
}&lt;/p&gt;
&lt;p&gt;console.log(&apos;good key   -- Phase 2 skipped? &apos; + phase2Skipped(goodKey));   // false
console.log(&apos;broken key -- Phase 2 skipped? &apos; + phase2Skipped(brokenKey)); // true
console.log(&apos;Equal gamma and delta means the verifier accepts forged proofs.&apos;);
`}&lt;/p&gt;
&lt;p&gt;Then map the tools onto the layers. The point is not to run everything, but to run the right net at the right layer.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Bug class&lt;/th&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Detect with&lt;/th&gt;
&lt;th&gt;Prevent with&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Assigned but not constrained&lt;/td&gt;
&lt;td&gt;Circuit&lt;/td&gt;
&lt;td&gt;Circomspect, ZKAP, Picus&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;==&lt;/code&gt; discipline, uniqueness checks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Missing range / bit-length check&lt;/td&gt;
&lt;td&gt;Circuit&lt;/td&gt;
&lt;td&gt;Picus, ZKAP&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Num2Bits&lt;/code&gt; on every bounded input&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nondeterministic nullifier&lt;/td&gt;
&lt;td&gt;Circuit&lt;/td&gt;
&lt;td&gt;Picus (uniqueness)&lt;/td&gt;
&lt;td&gt;Constrain the tag to a fixed length&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Verifier edge case (zero, infinity)&lt;/td&gt;
&lt;td&gt;Verifier&lt;/td&gt;
&lt;td&gt;Audit, differential testing&lt;/td&gt;
&lt;td&gt;Encoding and on-curve checks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Weak Fiat-Shamir&lt;/td&gt;
&lt;td&gt;Transcript&lt;/td&gt;
&lt;td&gt;Audit, transcript review&lt;/td&gt;
&lt;td&gt;Strong-FS library, transcript-proof fusion&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Skipped trusted-setup Phase 2 (&lt;code&gt;delta == gamma&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;Verifier / setup&lt;/td&gt;
&lt;td&gt;Compare &lt;code&gt;vk_delta_2&lt;/code&gt; and &lt;code&gt;vk_gamma_2&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Run &lt;code&gt;zkey contribute&lt;/code&gt;, confirm &lt;code&gt;delta != gamma&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unchecked settlement slot / proofless path&lt;/td&gt;
&lt;td&gt;Settlement / integration&lt;/td&gt;
&lt;td&gt;Audit, differential testing&lt;/td&gt;
&lt;td&gt;Settle every committed slot, verify a proof on every value path&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; A clean Circomspect or ZKAP run means no known-bad pattern was found -- not that the circuit is sound [@circomspect]. Only a sound uniqueness checker, and only within its timeout, proves absence [@picus-github]. Never let a passing lint stage stand in for a proof that a constraint is actually present.&lt;/p&gt;
&lt;/blockquote&gt;

Given a `RangeProof` that calls `LessThan(64)` on an input, ask: where is that input constrained to 64 bits? If the only bit-length logic in the circuit lives *inside* `LessThan`, there is none -- the gadget assumes it. The fix is a `Num2Bits(64)` on each input before the comparison, which is exactly the commit Dark Forest shipped [@df-fix-commit]. The verifier-side analogue is just as mechanical: given a deployed Groth16 key, print `vk_gamma_2` and `vk_delta_2` and compare them. If they are the same point, Phase 2 was skipped and the verifier will accept forged proofs -- the Veil Cash and FOOMCASH bug in a single check [@zksecurity-groth16].
&lt;p&gt;One last item belongs on any honest checklist: knowing when zero-knowledge is the wrong tool. A proof system adds a large, subtle artifact -- three layers, each independently fallible -- on top of whatever it protects. If a cheaper mechanism gives the same guarantee, the safest circuit is the one you never had to write.&lt;/p&gt;
&lt;p&gt;A checklist is only as good as the misconceptions it clears. Here are the ones this topic breeds.&lt;/p&gt;
&lt;h2&gt;11. Questions the Record Answers&lt;/h2&gt;
&lt;p&gt;The topic breeds a handful of durable misconceptions. Here are the ones worth clearing.&lt;/p&gt;


Not on its own. Soundness is relative to the circuit&apos;s specification. A valid proof means *some* witness satisfied the constraints that were actually written. If a constraint is missing, an illegitimate witness qualifies, and the proof certifies something the designer never intended. The proof is honest about the constraints; the constraints may not be honest about the world.


No. Every publicly disclosed case is an artifact-layer soundness bug -- in a circuit, a verifier, or a transcript. The discrete-log, pairing, and hash assumptions underneath never appear in the record, and the empirical corpus of 141 SNARK vulnerabilities is overwhelmingly made of these soundness bugs [@chaliasos-sok-arxiv].


Yes, more than once -- but the honest answer needs tiers. Every confirmed on-chain money loss traces to a verifier, settlement, trusted-setup, or integration mismatch rather than a pure in-circuit under-constraint [@samczsun-bnb, @slowmist-aztec, @darknavy-veil], while the pure in-circuit cases -- Zcash, Dark Forest, Solana&apos;s ElGamal program -- were disclosed-and-patched near-misses; the full incident-by-incident accounting, with figures and disposition tags, is the ledger above.


No. It was a proactive, coordinated disclosure across Girault, Bulletproofs, and PlonK implementations; affected parties remediated before publication [@tob-frozenheart-p1]. The same weak-Fiat-Shamir omission has now recurred across four eras -- Helios in 2012, Frozen Heart in 2022, Solana&apos;s ZK ElGamal program in 2025, and six production zkVMs disclosed by OtterSec in 2026 -- and every instance was a disclosed near-miss with no known exploitation [@bpw-2016-771, @solana-june-2025, @osec-zkvms]. &quot;In the wild&quot; here means the weak transcript was present in shipping code, not that anyone forged a proof with it.


Under-constrained circuits. In the SoK 2024 study of 141 real-world SNARK vulnerabilities, they were the single most prevalent category, in a corpus dominated by soundness failures rather than privacy or completeness ones [@chaliasos-sok-arxiv].


Only partly. Sound tools such as Picus prove output uniqueness or hand you a two-witness exploit, but they do not scale to production-size circuits [@picus-2023-512]. Scalable linters such as Circomspect and ZKAP run across whole projects but are unsound -- a clean scan is not a proof [@circomspect, @zkap-2023-190]. And none of them checks whether your *trusted setup* was actually finalized, the gap the 2026 Groth16 drains walked through [@zksecurity-groth16]. No deployed tool covers every layer, which is why the discipline is defense-in-depth.


No. The structural sibling *derives* the mechanisms -- why a redundant CRS, a weak transcript, or an uninstantiable heuristic can break a proof. This article is the *documented record* of those mechanisms shipping, breaking, and being caught, tagged incident by incident.

&lt;p&gt;Step back and the ledger reads the same from every angle. In each publicly disclosed failure of a production zero-knowledge system, the cryptographic floor held: no curve solved, no hash reversed, no pairing broken. The human-built artifact above it leaked -- a circuit missing a constraint, a verifier mishandling a zero, a transcript that hashed too little, or integration code that forgot to check the proof at all.&lt;/p&gt;
&lt;p&gt;And where that artifact guarded a rollup, a pool, or a bridge, the leak was denominated in money: minted or drained in the near-misses, moved and then returned in the 2026 Groth16 drains, and stolen outright in the confirmed cases, with the largest still in the adjacent BNB Merkle verifier.No confirmed theft in this record is a &lt;em&gt;pure&lt;/em&gt; zk-SNARK circuit break: BNB was a Merkle-proof verifier bug; Veil Cash and FOOMCASH were Groth16 verifier-key bugs; Hinkal was reported as a proofless-deposit integration bug; and Aztec Connect paired a circuit-side gap-slot problem with a settlement-layer flaw. That is the distinction this whole article has kept, so that &quot;the bugs that minted money&quot; never quietly becomes &quot;zero-knowledge cryptography was broken.&quot;&lt;/p&gt;

The crypto held; the code didn&apos;t -- in every disclosed case, and so far.
&lt;p&gt;That refrain carries its own hedge. &quot;So far&quot; is doing real work: the record is short, the corpus counts only the bugs that were found, and the floor is unbroken rather than unbreakable -- a STARK&apos;s post-quantum security is still a conjecture, and Shor&apos;s algorithm remains the one clocked risk [@starks-2018-046].&lt;/p&gt;
&lt;p&gt;The honest claim is not that zero-knowledge cannot break at the math. It is that, to date, it never has -- and every time it broke in real life, the fault was a line of code a human forgot to write. To see &lt;em&gt;why&lt;/em&gt; those mechanisms can fail, read the structural sibling, &lt;a href=&quot;https://paragmali.com/blog/a-valid-proof-of-a-false-thing-how-zero-knowledge-proofs-act/&quot; rel=&quot;noopener&quot;&gt;&lt;em&gt;A Valid Proof of a False Thing&lt;/em&gt;&lt;/a&gt;; to see how the proof systems are built in the first place, read &lt;a href=&quot;https://paragmali.com/blog/never-decrypted-proving-you-ran-the-computation-without-reve/&quot; rel=&quot;noopener&quot;&gt;&lt;em&gt;Never Decrypted: Proving You Ran the Computation&lt;/em&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&amp;lt;StudyGuide slug=&quot;how-zero-knowledge-broke-in-real-life&quot; keyTerms={[
  { term: &quot;Soundness&quot;, definition: &quot;The guarantee that no one can produce a convincing proof of a false statement; the property every incident in this article attacks.&quot; },
  { term: &quot;Under-constrained circuit&quot;, definition: &quot;A circuit whose equations admit a witness the designer never intended, because the excluding constraint was never written; the most prevalent class in the disclosed corpus.&quot; },
  { term: &quot;Range check&quot;, definition: &quot;A constraint forcing a value to fit in n bits; omitting it lets an oversized field element pass as in-range, as in the Dark Forest bug.&quot; },
  { term: &quot;Verifier&quot;, definition: &quot;The code that checks a proof, usually an on-chain contract; a separate artifact from the circuit that can be independently wrong, as in the Aztec 0-bug.&quot; },
  { term: &quot;Fiat-Shamir transform&quot;, definition: &quot;Replacing a public-coin verifier&apos;s random challenge with a hash of the transcript; sound only when the hash binds the statement and all public inputs.&quot; },
  { term: &quot;Weak Fiat-Shamir&quot;, definition: &quot;A transcript that hashes too little, letting a prover steer the challenge; the Frozen Heart, Solana, and zkVM root cause.&quot; },
  { term: &quot;Nullifier&quot;, definition: &quot;A unique tag published when a note is spent to prevent double-spends; must be deterministic, or one note yields many tags.&quot; }
]} questions={[
  { q: &quot;Why can a valid proof still certify a false statement?&quot;, a: &quot;Because soundness is relative to the constraints actually written. If a constraint is missing, an illegitimate witness satisfies the system and the proof certifies it.&quot; },
  { q: &quot;Why can&apos;t unit tests catch a missing constraint?&quot;, a: &quot;The bug is an absence. Positive tests all pass, because the illegitimate witnesses live in exactly the space the missing equation was meant to forbid.&quot; },
  { q: &quot;What distinguishes the BNB theft from the zero-knowledge near-misses?&quot;, a: &quot;BNB was a general IAVL Merkle proof-verifier bug that skipped a branch, not a zk-SNARK circuit under-constraint; it is the adjacent archetype, and the largest confirmed theft.&quot; },
  { q: &quot;Why is no single tool enough?&quot;, a: &quot;Sound tools do not scale and scalable tools are not sound, so the working answer is defense-in-depth: one net per bug type across all three layers.&quot; }
]} /&amp;gt;&lt;/p&gt;
</content:encoded><category>zero-knowledge-proofs</category><category>zk-snarks</category><category>under-constrained-circuits</category><category>soundness</category><category>fiat-shamir</category><category>circuit-security</category><category>zk-security</category><category>blockchain-security</category><author>noreply@paragmali.com (Parag Mali)</author></item><item><title>A Valid Proof of a False Thing: How Zero-Knowledge Proofs Actually Break</title><link>https://paragmali.com/blog/a-valid-proof-of-a-false-thing-how-zero-knowledge-proofs-act/</link><guid isPermaLink="true">https://paragmali.com/blog/a-valid-proof-of-a-false-thing-how-zero-knowledge-proofs-act/</guid><description>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.</description><pubDate>Mon, 20 Jul 2026 00:00:00 GMT</pubDate><content:encoded>
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&apos;s transparent, hash-based construction only sheds one seam while inheriting the others, and &quot;post-quantum secure&quot; remains a conjecture, not a proof.
&lt;h2&gt;1. A Valid Proof of a False Thing&lt;/h2&gt;
&lt;p&gt;A verifier checks a zero-knowledge proof and returns a single bit: &lt;code&gt;true&lt;/code&gt;. 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.&lt;/p&gt;
&lt;p&gt;That sentence should read like a contradiction. A proof is supposed to be a guarantee, and &quot;verified&quot; is supposed to mean &quot;true.&quot; Dissolving the paradox is the entire project of this article, and the resolution is deceptively small: a proof is valid relative to &lt;em&gt;the constraints someone wrote down&lt;/em&gt;, not relative to the truth they meant to capture.&lt;/p&gt;

A protocol that convinces a verifier a statement is true while revealing nothing beyond its truth. The word &quot;argument&quot; (as opposed to &quot;proof&quot;) signals *computational* soundness: the guarantee holds against a prover with bounded computing power, which is the setting every deployed SNARK actually lives in.
&lt;p&gt;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.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; A verifying proof means &quot;the prover found some witness the circuit accepts,&quot; not &quot;the statement is true.&quot; 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.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;To see how that gap opens, picture a zero-knowledge system as two stacked layers.&lt;/p&gt;

flowchart TD
    subgraph Artifact[&quot;Human-built artifact -- where every break lives&quot;]
      C[Arithmetized circuit]
      S[Trusted-setup string]
      T[Fiat-Shamir transcript]
    end
    Artifact --&amp;gt; Floor
    subgraph Floor[&quot;Hardness floor -- unbroken so far&quot;]
      D[Discrete logarithm]
      P[Elliptic-curve pairings]
      H[Collision-resistant hash]
    end
&lt;p&gt;At the bottom sits a &lt;strong&gt;hardness floor&lt;/strong&gt;: a small set of problems believed intractable, such as computing discrete logarithms, solving pairing relations, or finding hash collisions. At the top sits a &lt;strong&gt;human-built artifact&lt;/strong&gt;: 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.&lt;/p&gt;
&lt;p&gt;Here is the thesis this article defends, drawn from the documented vulnerability record rather than from any theorem: &lt;strong&gt;production zero-knowledge proofs almost never fail at the cryptographic hardness. They fail at soundness in the layer humans build&lt;/strong&gt; [@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.&lt;/p&gt;
&lt;p&gt;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, &quot;How Zero-Knowledge Broke in Real Life.&quot; Here, every bug we examine would still be a bug if the code ran on perfect hardware with a perfect random-number generator.&lt;/p&gt;
&lt;p&gt;To understand why the floor and the seams are even &lt;em&gt;separable&lt;/em&gt; -- 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.&lt;/p&gt;
&lt;h2&gt;2. Where Soundness Was Born&lt;/h2&gt;
&lt;p&gt;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 &lt;em&gt;fused&lt;/em&gt; to the statement: a false claim simply has no accepting witness, so cheating is impossible by definition.&lt;/p&gt;
&lt;p&gt;But that same fusion meant there was no vocabulary for a cheating prover. You could not ask &lt;em&gt;how much&lt;/em&gt; a liar might get away with, or measure it apart from the underlying hard problem. There was no &quot;soundness error&quot; to reason about, because soundness was all-or-nothing and welded to truth.&lt;/p&gt;
&lt;p&gt;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 &lt;em&gt;randomized&lt;/em&gt; verifier trade messages, the verifier issues fresh unpredictable challenges, and it accepts only probabilistically [@gmr85].&lt;/p&gt;
&lt;p&gt;In doing so they separated a proof into three properties that had never been named apart before -- &lt;strong&gt;completeness&lt;/strong&gt;, &lt;strong&gt;soundness&lt;/strong&gt;, and &lt;strong&gt;zero-knowledge&lt;/strong&gt; -- and, decisively, they made soundness a &lt;em&gt;quantity&lt;/em&gt;. A cheating prover now succeeds with some probability, the soundness error, that you can drive toward zero but analyze on its own terms [@gmr85].&lt;/p&gt;
&lt;p&gt;A &quot;proof&quot; in the strict sense has soundness against a computationally &lt;em&gt;unbounded&lt;/em&gt; cheating prover; an &quot;argument&quot; 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.&lt;/p&gt;
&lt;p&gt;That separation is the hinge of everything that follows. Because soundness is measured against &lt;em&gt;the statement as encoded&lt;/em&gt;, 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.&lt;/p&gt;
&lt;p&gt;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].&lt;/p&gt;

A method for making an interactive proof non-interactive by computing the verifier&apos;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.
&lt;p&gt;The Fiat-Shamir transform is the universal glue beneath essentially every non-interactive proof and &lt;a href=&quot;https://paragmali.com/blog/the-discrete-log-held-the-proofs-leaked-a-field-guide-to-com/&quot; rel=&quot;noopener&quot;&gt;Schnorr-style signature&lt;/a&gt; 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 &lt;strong&gt;common reference string&lt;/strong&gt; 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&apos;s reference string, the &quot;powers of tau&quot; ceremonies -- is a concrete descendant of that idea.&lt;/p&gt;

flowchart LR
    A[1985 GMR: three properties] --&amp;gt; B[1986 Fiat-Shamir transform]
    B --&amp;gt; C[1988 BFM setup string]
    C --&amp;gt; D[2012 Helios weak Fiat-Shamir]
    D --&amp;gt; E[2016 Groth16 toxic waste]
    E --&amp;gt; F[2019 Zcash redundant CRS]
    F --&amp;gt; G[2022 Frozen Heart]
    G --&amp;gt; H[2025 practical Fiat-Shamir break]
&lt;p&gt;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 &lt;em&gt;some&lt;/em&gt; kind is mandatory. This theorem is why a careful engineer says a transparent system has &quot;no &lt;em&gt;trusted&lt;/em&gt; setup&quot; and never &quot;no setup.&quot; The distinction is load-bearing, not pedantic, and it will decide how we read STARKs later.&lt;/p&gt;
&lt;p&gt;&quot;Transparent&quot; 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 &lt;em&gt;trusted&lt;/em&gt; setup to a public-coin one, but you can never reach &lt;em&gt;no&lt;/em&gt; setup for a non-trivial statement.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h2&gt;3. Three Ways to Fail, One That Pays&lt;/h2&gt;
&lt;p&gt;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.&lt;/p&gt;

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.

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.

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.
&lt;p&gt;Line those three up against their consequences and the asymmetry is stark.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Failure axis&lt;/th&gt;
&lt;th&gt;What goes wrong&lt;/th&gt;
&lt;th&gt;Consequence&lt;/th&gt;
&lt;th&gt;Who attacks it&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Completeness&lt;/td&gt;
&lt;td&gt;Honest prover gets rejected&lt;/td&gt;
&lt;td&gt;Liveness / denial of service&lt;/td&gt;
&lt;td&gt;Usually no one -- it is a self-inflicted bug&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Soundness&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;False statement accepted&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Forgery, minting, theft&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;A malicious prover&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Zero-knowledge&lt;/td&gt;
&lt;td&gt;Witness is exposed&lt;/td&gt;
&lt;td&gt;Privacy loss, de-anonymization&lt;/td&gt;
&lt;td&gt;A malicious verifier or observer&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Read the table as a threat model. A &lt;strong&gt;completeness&lt;/strong&gt; failure is annoying but not lucrative: your honest users cannot get their valid proofs accepted, so the system grinds rather than bleeds. A &lt;strong&gt;zero-knowledge&lt;/strong&gt; 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.&lt;/p&gt;
&lt;p&gt;A &lt;strong&gt;soundness&lt;/strong&gt; 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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;

&quot;Several million dollars worth of cryptocurrency have been stolen due to underconstrained arithmetic circuits.&quot; -- the Picus/QED2 authors and the IACR announcement of their work [@picus23][@iacr20490]
&lt;p&gt;Notice what that sentence does &lt;em&gt;not&lt;/em&gt; say. It does not say the &lt;a href=&quot;https://paragmali.com/blog/the-log-was-never-the-weak-part-how-discrete-log-cryptograph/&quot; rel=&quot;noopener&quot;&gt;discrete-logarithm assumption&lt;/a&gt; fell, or that someone found a hash collision, or that a pairing was inverted. The theft came from circuits that were &lt;em&gt;under-constrained&lt;/em&gt; -- 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.&lt;/p&gt;
&lt;p&gt;Every real break in the rest of this article lands on the soundness axis, and each one lives in a &lt;em&gt;different layer&lt;/em&gt; of the artifact.&lt;/p&gt;
&lt;h2&gt;4. The Break Surface, Layer by Layer&lt;/h2&gt;
&lt;p&gt;The artifact humans build has three parts, and each one has failed in production. We will walk them from the top down: the &lt;strong&gt;arithmetization&lt;/strong&gt; layer where a computation becomes equations, the &lt;strong&gt;setup&lt;/strong&gt; layer where the system is parameterized, and the &lt;strong&gt;transform&lt;/strong&gt; 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.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; 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 &lt;em&gt;orthogonal&lt;/em&gt;. 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.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;We start with the arithmetization layer, the one that breaks most often and most expensively [@sok24].&lt;/p&gt;
&lt;h3&gt;4a. The Circuit -- Under-Constrained Arithmetization&lt;/h3&gt;
&lt;p&gt;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.&lt;/p&gt;

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 &quot;one linear combination of the signals, times another, equals a third.&quot; The signals are the inputs, outputs, and intermediate values; &quot;proving the computation&quot; means exhibiting a signal assignment that satisfies every constraint at once.
&lt;p&gt;A circuit, then, is a hand-written compiler artifact: the programmer&apos;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?&lt;/p&gt;

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.
&lt;p&gt;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 &lt;em&gt;is&lt;/em&gt; present, and hands the verifier a proof that checks perfectly.&lt;/p&gt;

flowchart TD
    A[Computation compiled to a constraint system] --&amp;gt; B[One constraint omitted]
    B --&amp;gt; C{&quot;More than one witness fits?&quot;}
    C --&amp;gt;|Yes| D[Prover supplies an out-of-spec value]
    C --&amp;gt;|No| E[Output uniquely determined and safe]
    D --&amp;gt; F[Verifier accepts a valid proof of a false statement]
&lt;p&gt;The canonical toy version is a square. Suppose you want to prove &quot;I know the secret value the designer had in mind, and its square is &lt;code&gt;x&lt;/code&gt;.&quot; The obvious constraint is that the square of the witness equals &lt;code&gt;x&lt;/code&gt;. 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.&lt;/p&gt;
&lt;p&gt;{`
// A toy field-arithmetic &quot;circuit&quot; over the small prime p = 23.
// Intended statement: &quot;I know the specific value y the designer meant,
// and its square is x.&quot; 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) =&amp;gt; ((a % p) + p) % p;&lt;/p&gt;
&lt;p&gt;function circuitAccepts(x, y) {
  return mod(y * y) === mod(x);   // the single constraint actually enforced
}&lt;/p&gt;
&lt;p&gt;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&lt;/p&gt;
&lt;p&gt;console.log(&quot;intended y =&quot;, intended, &quot;accepted:&quot;, circuitAccepts(x, intended));
console.log(&quot;forged   y =&quot;, forged,   &quot;accepted:&quot;, circuitAccepts(x, forged));
// Both print true. A proof that &quot;verifies&quot; has not pinned the value the
// specification intended -- it only certifies that SOME accepted witness exists.
`}&lt;/p&gt;
&lt;p&gt;In a toy square the extra witness is harmless. In a withdrawal circuit that was supposed to enforce &quot;this note has not already been spent&quot; or &quot;this balance is non-negative,&quot; 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.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; A verifying proof certifies that the prover satisfied the &lt;em&gt;circuit&lt;/em&gt;, not that they satisfied your &lt;em&gt;specification&lt;/em&gt;. If a constraint you assumed is missing, the verifier will accept out-of-spec witnesses all day. &quot;Valid&quot; is a claim about the equations on the page, never about your intentions.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is the payoff of the first aha moment. The naive reading -- &quot;a verifying proof means the statement is true&quot; -- fails because a verifying proof only means the prover found &lt;em&gt;some&lt;/em&gt; 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.&lt;/p&gt;
&lt;p&gt;The archetypal way this bug enters real code is a one-character slip.&lt;/p&gt;
&lt;p&gt;In Circom, the most widely used circuit language, &lt;code&gt;&amp;lt;--&lt;/code&gt; assigns a value to a signal but adds no constraint, while &lt;code&gt;&amp;lt;==&lt;/code&gt; assigns &lt;em&gt;and&lt;/em&gt; constrains. Writing &lt;code&gt;&amp;lt;--&lt;/code&gt; where you meant &lt;code&gt;&amp;lt;==&lt;/code&gt; 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].&lt;/p&gt;
&lt;p&gt;The field arithmetic hides further traps: values that alias modulo the field prime, and &quot;hint&quot; 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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;

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.
&lt;p&gt;A missing constraint is a bug in a layer &lt;em&gt;you&lt;/em&gt; wrote and can, in principle, read. But what if the flaw is baked into a string you were &lt;em&gt;handed&lt;/em&gt; -- one you cannot even inspect, generated in a ritual you had to trust?&lt;/p&gt;
&lt;h3&gt;4b. The Reference String -- Trusted-Setup Subversion and Toxic Waste&lt;/h3&gt;
&lt;p&gt;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?&lt;/p&gt;

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 &quot;toxic waste.&quot; 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.
&lt;p&gt;This is the setup layer&apos;s first and cleanest failure mode, and Groth16 is its reference example. Jens Groth&apos;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 &lt;em&gt;operational&lt;/em&gt; 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.&lt;/p&gt;

flowchart TD
    A[Setup samples secret scalars] --&amp;gt; B[Publish only their group encodings as the SRS]
    B --&amp;gt; C{&quot;Trapdoor destroyed?&quot;}
    C --&amp;gt;|Yes, via ceremony| D[No one can forge, system is sound]
    C --&amp;gt;|No, someone kept it| E[Holder forges any statement at will]
&lt;p&gt;This is &lt;em&gt;why the ceremony matters&lt;/em&gt;. 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 &lt;em&gt;every&lt;/em&gt; participant colludes, which flips the requirement into a reassuring form -- the setup is secure if &lt;em&gt;at least one&lt;/em&gt; of the participants was honest [@eth_kzg_blog].&lt;/p&gt;
&lt;p&gt;These are the &quot;powers of tau&quot; ceremonies, named for the secret scalar tau whose successive powers structure the string. The security property is the &quot;1-of-N honest&quot; assumption: among N contributors, a single honest one who truly destroyed their randomness makes the trapdoor unrecoverable [@eth_kzg_blog].&lt;/p&gt;
&lt;p&gt;The modern scale of this is remarkable. Ethereum&apos;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 &lt;em&gt;per-circuit&lt;/em&gt; -- every new program needs its own [@eth_kzg_blog].&lt;/p&gt;
&lt;p&gt;Now the accuracy-critical part. There is a &lt;em&gt;second&lt;/em&gt;, entirely different setup-layer failure that is routinely conflated with the first, and conflating them gets the physics wrong.&lt;/p&gt;

**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.&lt;p&gt;&lt;strong&gt;Mode 2 -- a construction soundness bug.&lt;/strong&gt; The setup string itself is built wrong -- it contains elements that should not be there -- so a prover can forge &lt;em&gt;even though no secret was ever retained&lt;/em&gt;. No ceremony helps, because nothing leaked. The construction is the defect.
&lt;/p&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;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 &lt;em&gt;different&lt;/em&gt; statement -- undetectable, unlimited counterfeiting, with the hardness assumption completely intact [@gabizon19].&lt;/p&gt;

The flaw let an attacker &quot;transform the proof of one statement into an ostensibly valid proof of a different statement, thereby breaking ... soundness.&quot; -- the official record of CVE-2019-7167 [@cve19]
&lt;p&gt;The timeline is the quietly alarming part, and it hides two windows that are easy to conflate. The Electric Coin Company&apos;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].&lt;/p&gt;
&lt;p&gt;Those two windows are very different lengths. The &lt;em&gt;exploitable&lt;/em&gt; window was the long one: the flaw was present from Zcash&apos;s 2016 launch until the Sapling fix, roughly two years in which the currency was silently, undetectably counterfeitable. The &lt;em&gt;secrecy&lt;/em&gt; 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&apos;s minimal reference string removed this entire class by construction, because it contains only the elements the proof actually needs [@groth16].&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The Zcash counterfeiting flaw was &lt;em&gt;not&lt;/em&gt; a leaked secret and &lt;em&gt;not&lt;/em&gt; a failed ceremony. It was a soundness bug in how the reference string was built [@gabizon19][@cve19]. Filing it under &quot;toxic waste&quot; hides the lesson: even a setup whose secrets were perfectly destroyed can be forgeable if the string is structured wrong.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h3&gt;4c. The Transcript -- The Frozen Heart&lt;/h3&gt;
&lt;p&gt;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.&lt;/p&gt;

Weak Fiat-Shamir derives the challenge from the prover&apos;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.
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;Weak Fiat-Shamir accidentally hands the &lt;em&gt;attacker&lt;/em&gt; 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 &lt;em&gt;which&lt;/em&gt; 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 &lt;em&gt;chosen&lt;/em&gt; out-of-range value -- the case that turns the flaw into forged money.&lt;/p&gt;

flowchart TD
    subgraph Weak[&quot;Weak Fiat-Shamir&quot;]
      W1[Challenge hashes the commitment only] --&amp;gt; W2[Attacker fixes the challenge first]
      W2 --&amp;gt; W3[Back-fills a bogus commitment and response]
      W3 --&amp;gt; W4[Forged proof verifies]
    end
    subgraph Strong[&quot;Strong Fiat-Shamir&quot;]
      S1[Challenge hashes statement, inputs, transcript] --&amp;gt; S2[Challenge pinned to the statement]
      S2 --&amp;gt; S3[No freedom to back-fill]
      S3 --&amp;gt; S4[Forgery blocked]
    end
&lt;p&gt;The tell is that the challenge is &lt;em&gt;identical&lt;/em&gt; for two different statements under weak Fiat-Shamir, so a transcript built for one carries over to another. Watch it happen.&lt;/p&gt;
&lt;p&gt;{`
// A stand-in hash (FNV-1a). Not cryptographic -- illustrative only.
function H(s) {
  let h = 2166136261 &amp;gt;&amp;gt;&amp;gt; 0;
  for (const ch of String(s)) {
    h = Math.imul(h ^ ch.charCodeAt(0), 16777619) &amp;gt;&amp;gt;&amp;gt; 0;
  }
  return h &amp;gt;&amp;gt;&amp;gt; 0;
}&lt;/p&gt;
&lt;p&gt;// Two DIFFERENT statements a prover might try to prove.
const statementA = &quot;pubkeyA-owns-100&quot;;
const statementB = &quot;pubkeyB-owns-100&quot;;
const commitment = &quot;prover-first-message&quot;;&lt;/p&gt;
&lt;p&gt;// WEAK Fiat-Shamir: the challenge ignores the statement entirely.
const weakA = H(commitment);
const weakB = H(commitment);
console.log(&quot;weak FS   -- same challenge for A and B?&quot;, weakA === weakB); // true&lt;/p&gt;
&lt;p&gt;// STRONG Fiat-Shamir: the challenge binds statement + inputs + transcript.
const strongA = H(statementA + &quot;|&quot; + commitment);
const strongB = H(statementB + &quot;|&quot; + commitment);
console.log(&quot;strong FS -- same challenge for A and B?&quot;, strongA === strongB); // false
`}&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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&apos;s scheme, Bulletproofs, and PlonK, and gave the class a name [@tob_fh].&lt;/p&gt;

&quot;We&apos;ve dubbed this class of vulnerabilities Frozen Heart. The word frozen is an acronym for FoRging Of ZEro kNowledge proofs.&quot; -- Trail of Bits, coordinated disclosure, April 2022 [@tob_fh]
&lt;p&gt;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].&lt;/p&gt;
&lt;p&gt;The 2022 disclosure reached implementations across many libraries -- iden3&apos;s snarkjs, ConsenSys&apos;s gnark, Dusk Network&apos;s PLonK library, and ZenGo&apos;s zk-paillier among them -- all remediated before publication [@tob_fh].&lt;/p&gt;
&lt;p&gt;Now the sentence that carries the thesis. Bulletproofs has &lt;em&gt;no trusted setup&lt;/em&gt; -- it is transparent, drawing all its randomness from public coins [@stanford_bp]. And it was &lt;em&gt;still&lt;/em&gt; 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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h2&gt;5. Name the Layer, Name the Break&lt;/h2&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; 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.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The lens works because the seams are &lt;em&gt;orthogonal&lt;/em&gt;. 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.&lt;/p&gt;
&lt;p&gt;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. &quot;Name the layer&quot; fully determines &quot;name the break&quot; only because the layers do not bleed into one another.&lt;/p&gt;

flowchart TD
    A[A verifying proof of a false statement] --&amp;gt; B{&quot;Which layer leaked?&quot;}
    B --&amp;gt;|Circuit| C[Under-constrained: a missing constraint]
    B --&amp;gt;|Setup string| D[Trapdoor retained, or a redundant CRS]
    B --&amp;gt;|Transcript| E[Weak Fiat-Shamir, the Frozen Heart]
    B --&amp;gt;|Hardness floor| F[Never observed in the record]
&lt;p&gt;Laid out as a table, the whole incident record fits on a single grid.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Seam&lt;/th&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Mechanism&lt;/th&gt;
&lt;th&gt;Canonical incident&lt;/th&gt;
&lt;th&gt;Caught by&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Under-constrained circuit&lt;/td&gt;
&lt;td&gt;Arithmetization&lt;/td&gt;
&lt;td&gt;Missing constraint admits multiple witnesses&lt;/td&gt;
&lt;td&gt;Rollup and mixer withdrawal bugs [@sok24]&lt;/td&gt;
&lt;td&gt;Picus, ZKAP, Circomspect&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Toxic-waste retention&lt;/td&gt;
&lt;td&gt;Setup string&lt;/td&gt;
&lt;td&gt;Retained trapdoor forges any statement&lt;/td&gt;
&lt;td&gt;Groth16 setup, mitigated by ceremony [@groth16]&lt;/td&gt;
&lt;td&gt;MPC ceremony, transparency&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Redundant reference string&lt;/td&gt;
&lt;td&gt;Setup string&lt;/td&gt;
&lt;td&gt;Malformed string lets a statement be swapped&lt;/td&gt;
&lt;td&gt;Zcash BCTV14 [@cve19]&lt;/td&gt;
&lt;td&gt;Minimal SRS, audit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Frozen Heart&lt;/td&gt;
&lt;td&gt;Transform&lt;/td&gt;
&lt;td&gt;Weak Fiat-Shamir omits the statement&lt;/td&gt;
&lt;td&gt;Girault, Bulletproofs, PlonK [@cve22]&lt;/td&gt;
&lt;td&gt;Strong-Fiat-Shamir transcripts&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The middle two rows are both the setup seam, split into its two distinct modes. Read down the &quot;Caught by&quot; 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.&lt;/p&gt;
&lt;p&gt;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&apos;s thesis rendered as a coordinate grid.&lt;/p&gt;

The seams are orthogonal: closing one does nothing to the others.
&lt;p&gt;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 &lt;em&gt;so far&lt;/em&gt;, not &lt;em&gt;unbreakable&lt;/em&gt;. There is exactly one place where the floor itself is on the clock -- a large quantum computer running Shor&apos;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.&lt;/p&gt;
&lt;p&gt;If every break has a home layer, then every &lt;em&gt;defense&lt;/em&gt; has a target layer too. So how far has the field gotten at sewing each seam shut?&lt;/p&gt;
&lt;h2&gt;6. Sewing the Seams Shut&lt;/h2&gt;
&lt;p&gt;There is no single &quot;secure zero-knowledge&quot; 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].&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The arithmetization seam&lt;/strong&gt; gets the most-developed tooling, because it is the most-exploited layer, and the tools sit on a spectrum that trades soundness against scale.&lt;/p&gt;
&lt;p&gt;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].&lt;/p&gt;
&lt;p&gt;The catch is that deciding uniqueness over nonlinear field constraints has no efficient general algorithm, so Picus is &lt;em&gt;sound but incomplete&lt;/em&gt;: 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].&lt;/p&gt;
&lt;p&gt;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 &lt;code&gt;&amp;lt;--&lt;/code&gt; versus &lt;code&gt;&amp;lt;==&lt;/code&gt; slip and other unconstrained-signal patterns at authoring time [@circomspect_gh][@circomspect_blog].&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;Guarantee&lt;/th&gt;
&lt;th&gt;Scaling&lt;/th&gt;
&lt;th&gt;Output&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Picus / QED2&lt;/td&gt;
&lt;td&gt;SMT proof of output uniqueness&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Sound&lt;/strong&gt;, incomplete&lt;/td&gt;
&lt;td&gt;Weakest (solver cost)&lt;/td&gt;
&lt;td&gt;Determinism proof or exploit witness pair&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ZKAP&lt;/td&gt;
&lt;td&gt;Anti-pattern queries over a dependence graph&lt;/td&gt;
&lt;td&gt;Bug-finder; not sound&lt;/td&gt;
&lt;td&gt;Strong&lt;/td&gt;
&lt;td&gt;Ranked vulnerability findings&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Circomspect&lt;/td&gt;
&lt;td&gt;Multi-pass linter&lt;/td&gt;
&lt;td&gt;Heuristic warnings&lt;/td&gt;
&lt;td&gt;Strongest (near-linear)&lt;/td&gt;
&lt;td&gt;Warnings (stdout or SARIF)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Run Circomspect in CI to catch the &lt;code&gt;&amp;lt;--&lt;/code&gt; and &lt;code&gt;&amp;lt;==&lt;/code&gt; slips as they are written, ZKAP for corpus-scale triage, and reserve Picus to &lt;em&gt;prove&lt;/em&gt; your critical circuits deterministic. No single tool is both sound and scalable, so the practical answer is to stack them.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;The setup seam&lt;/strong&gt; 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&apos;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.&lt;/p&gt;

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.
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The transform seam&lt;/strong&gt; is closed by refusing to hand-roll transcripts. Strong-Fiat-Shamir libraries make weak Fiat-Shamir the non-default.&lt;/p&gt;
&lt;p&gt;Merlin, built on the STROBE framework, folds every value into a running transcript through a labeled &lt;code&gt;append_message&lt;/code&gt; 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].&lt;/p&gt;
&lt;p&gt;Used correctly, a strong-transcript library eliminates the entire &lt;em&gt;avoidable&lt;/em&gt; 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 &quot;avoidable.&quot; 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.&lt;/p&gt;
&lt;p&gt;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?&lt;/p&gt;
&lt;h2&gt;7. SNARK or STARK: Which Seams You Inherit&lt;/h2&gt;
&lt;p&gt;The usual comparison of proof systems asks which is fastest or smallest. The thesis reframes the question: &lt;strong&gt;which seam does each construction avoid, and which does it inherit?&lt;/strong&gt; Read that way, the four most-deployed families tell a single clean story.&lt;/p&gt;
&lt;p&gt;Groth16&apos;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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;PlonK keeps the succinct proof but &lt;em&gt;relocates&lt;/em&gt; 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 &lt;em&gt;avoids&lt;/em&gt; the setup seam entirely by being transparent [@bulletproofs] -- and was &lt;em&gt;also&lt;/em&gt; a Frozen Heart victim [@tob_fh]. STARKs avoid the setup seam too, and additionally trade the algebraic hardness assumption for a hash-based one.&lt;/p&gt;

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&apos;s algorithm targets.
&lt;p&gt;Put them on one grid and read the columns, not the rows.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dimension&lt;/th&gt;
&lt;th&gt;Groth16 (2016)&lt;/th&gt;
&lt;th&gt;PlonK (2019)&lt;/th&gt;
&lt;th&gt;Bulletproofs (2018)&lt;/th&gt;
&lt;th&gt;STARK / FRI (2018)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Proof size&lt;/td&gt;
&lt;td&gt;3 elements, ~128 to 192 B&lt;/td&gt;
&lt;td&gt;~constant, few hundred B&lt;/td&gt;
&lt;td&gt;$2\log_2 n + 9$ elements&lt;/td&gt;
&lt;td&gt;polylog, tens to hundreds of KB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Verify cost&lt;/td&gt;
&lt;td&gt;~3 pairings (constant)&lt;/td&gt;
&lt;td&gt;~constant&lt;/td&gt;
&lt;td&gt;$O(n)$, batchable&lt;/td&gt;
&lt;td&gt;polylog&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Setup seam&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Inherited&lt;/strong&gt; (per-circuit toxic waste)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Relocated&lt;/strong&gt; (universal trapdoor)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Avoided&lt;/strong&gt; (transparent)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Avoided&lt;/strong&gt; (transparent)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Transform seam&lt;/td&gt;
&lt;td&gt;Inherited&lt;/td&gt;
&lt;td&gt;Inherited (Frozen Heart)&lt;/td&gt;
&lt;td&gt;Inherited (Frozen Heart)&lt;/td&gt;
&lt;td&gt;Inherited&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hardness floor&lt;/td&gt;
&lt;td&gt;Pairings (Shor-breakable)&lt;/td&gt;
&lt;td&gt;Pairings + KZG (Shor-breakable)&lt;/td&gt;
&lt;td&gt;Discrete log (Shor-breakable)&lt;/td&gt;
&lt;td&gt;Hashes (plausibly PQ)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Post-quantum&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Conjectural&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;Two disciplined caveats keep this table honest.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; For STARKs, &quot;post-quantum secure&quot; is a &lt;em&gt;conjecture&lt;/em&gt;. Security rests on the conjectured collision resistance of a hash, which no one has proved survives a quantum adversary, and Grover&apos;s algorithm forces larger hash parameters to hold the same margin [@stark18]. Write &quot;plausibly post-quantum,&quot; never &quot;post-quantum secure&quot; as if it were settled.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The second caveat is about the word &quot;no.&quot; Transparent cores are often wrapped for deployment.&lt;/p&gt;
&lt;p&gt;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&apos;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 &quot;no trusted setup&quot; is true of the STARK &lt;em&gt;core&lt;/em&gt;, not necessarily of the whole &lt;em&gt;pipeline&lt;/em&gt; -- a re-introduced surface by construction, not a documented exploited break.&lt;/p&gt;

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.
&lt;p&gt;How each of these systems is actually built -- the QAP compilation, the FRI protocol, the polynomial commitments -- is the subject of the companion article &quot;&lt;a href=&quot;https://paragmali.com/blog/never-decrypted-proving-you-ran-the-computation-without-reve/&quot; rel=&quot;noopener&quot;&gt;Never Decrypted: Proving You Ran the Computation&lt;/a&gt;.&quot; Here we only need enough to see where each one leaks.&lt;/p&gt;
&lt;p&gt;Transparency, strong transcripts, sound circuits: with enough care, can we close every seam? The theorems say no -- and they say precisely how far &quot;careful&quot; is even allowed to go.&lt;/p&gt;
&lt;h2&gt;8. What Being Careful Cannot Fix&lt;/h2&gt;
&lt;p&gt;Some of the failures in this article are not sloppiness. They are pinned by theorems that tell you how careful you are even &lt;em&gt;allowed&lt;/em&gt; to be. Four bounds define the ceiling.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Soundness error is never exactly zero.&lt;/strong&gt; 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].&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; 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]. &quot;Sound&quot; is always shorthand for &quot;sound except with negligible probability.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Setup is necessary.&lt;/strong&gt; Goldreich and Oren proved that non-trivial non-interactive zero-knowledge in the plain model collapses to BPP [@go94]. Transparency can downgrade a &lt;em&gt;trusted&lt;/em&gt; setup to a public-coin one, but &quot;no setup at all&quot; is unreachable. This is the theorem behind the whole &quot;no &lt;em&gt;trusted&lt;/em&gt; setup, never no setup&quot; discipline.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Fiat-Shamir is uninstantiable in the standard model.&lt;/strong&gt; In 2003, Shafi Goldwasser and Yael Tauman Kalai showed there exists a secure interactive protocol whose Fiat-Shamir version is insecure for &lt;em&gt;every&lt;/em&gt; concrete hash function [@gk03]. Strong-transcript engineering removes the &lt;em&gt;avoidable&lt;/em&gt; weak-Fiat-Shamir failures, but it cannot lift this ceiling: even a perfectly bound transcript rests on the random-oracle heuristic, not a proof.&lt;/p&gt;
&lt;p&gt;For two decades that impossibility was a contrived, almost pathological construction. Then it got real.&lt;/p&gt;

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&apos;s collision resistance. The hardness floor still stands; it is the transform that is now provably crackable in a real case.
&lt;p&gt;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&apos;s heuristic layer, not the discrete-log or hash hardness underneath.&lt;/p&gt;
&lt;p&gt;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 &lt;a href=&quot;https://paragmali.com/blog/how-q-day-breaks-everything-shors-algorithm-and-the-simultan/&quot; rel=&quot;noopener&quot;&gt;Shor&apos;s algorithm&lt;/a&gt; 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&apos;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.&lt;/p&gt;

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.
&lt;p&gt;Grover&apos;s algorithm gives a quadratic speedup on brute-force search, which effectively halves a hash function&apos;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.&lt;/p&gt;
&lt;p&gt;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 &quot;no setup&quot; part; Goldwasser-Kalai forbids the standard-model soundness proof that non-interactivity would need; and no construction has a &lt;em&gt;theorem&lt;/em&gt; of post-quantum security, only a conjecture with Grover eating into the margin.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;If the perfect system is provably out of reach, the honest question is not &quot;is it solved?&quot; but &quot;where, exactly, are the seams still open?&quot;&lt;/p&gt;
&lt;h2&gt;9. Where the Seams Are Still Open&lt;/h2&gt;
&lt;p&gt;Five frontiers are visibly moving, and each one is a seam not yet fully sewn.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Sound and scalable circuit verification.&lt;/strong&gt; 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.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Closing the Fiat-Shamir theory-practice gap.&lt;/strong&gt; Strong transcripts eliminate the &lt;em&gt;avoidable&lt;/em&gt; 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.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Trust-minimized setup at Groth16&apos;s profile.&lt;/strong&gt; Transparent systems remove the trapdoor but pay in proof size, verification cost, or a merely conjectural post-quantum guarantee. No construction today matches Groth16&apos;s roughly 128-byte, constant-verify profile &lt;em&gt;and&lt;/em&gt; transparency &lt;em&gt;and&lt;/em&gt; proven post-quantum security at once [@plonk][@stark18][@eth_kzg_blog]. Goldreich-Oren forbids &quot;no setup,&quot; so the reachable target is &quot;no &lt;em&gt;trusted&lt;/em&gt; setup&quot; -- and the race is to close the size-and-speed penalty.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Provably, not conjecturally, post-quantum arguments.&lt;/strong&gt; Every deployed pairing or discrete-log SNARK is on Shor&apos;s clock, and the hash-based hedge trades that for a conjecture with Grover eating the margin [@stark18]. A succinct argument with an &lt;em&gt;unconditional&lt;/em&gt; post-quantum soundness theorem -- not an assumption about a hash -- does not yet exist.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Audit tooling that keeps pace with new arithmetizations.&lt;/strong&gt; 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.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The gap between &quot;we linted it&quot; and &quot;we proved it deterministic&quot; 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.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h2&gt;10. A Layer-by-Layer Design Review&lt;/h2&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Layer 1, arithmetization.&lt;/strong&gt; Is &lt;em&gt;every&lt;/em&gt; signal constrained, with no stray &lt;code&gt;&amp;lt;--&lt;/code&gt; where a &lt;code&gt;&amp;lt;==&lt;/code&gt; 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 &quot;hint&quot; 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.&lt;/p&gt;
&lt;p&gt;{`
// Echoes the question Picus/QED2 asks: for fixed inputs, is the output unique?
const p = 13;
const mod = (a) =&amp;gt; ((a % p) + p) % p;&lt;/p&gt;
&lt;p&gt;// The author intends &quot;out = 1 exactly when inp is nonzero&quot;, 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);
}&lt;/p&gt;
&lt;p&gt;function acceptedOutputs(inp) {
  const outs = [];
  for (let out = 0; out &amp;lt; p; out++) {
    if (constraintHolds(inp, out)) outs.push(out);
  }
  return outs;
}&lt;/p&gt;
&lt;p&gt;for (let inp = 0; inp &amp;lt;= 2; inp++) {
  console.log(&quot;input&quot;, inp, &quot;-&amp;gt; accepted outputs&quot;, 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.
`}&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Layer 2, setup.&lt;/strong&gt; 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&apos;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.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Layer 3, transform.&lt;/strong&gt; Does the challenge hash bind the domain separator, the statement, &lt;em&gt;all&lt;/em&gt; 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.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; 1. &lt;strong&gt;Circuit:&lt;/strong&gt; Is every signal constrained, and is every output uniquely determined by the inputs? 2. &lt;strong&gt;Setup:&lt;/strong&gt; Trusted or transparent -- and if trusted, is your string the audited one and the ceremony credibly honest? 3. &lt;strong&gt;Transcript:&lt;/strong&gt; Does the challenge hash bind the statement and every public input? Three questions, one per seam, in the order bugs appear.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;There is a fourth question that sits above the other three: should this be a zero-knowledge system at all?&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; 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.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h2&gt;11. Frequently Asked Questions&lt;/h2&gt;


Not by itself. Soundness is defined relative to the *circuit&apos;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]. &quot;Valid&quot; means &quot;some witness satisfied the constraints,&quot; which equals &quot;true&quot; only when the constraints were written to force it.


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.


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&apos;s algorithm forces larger hash parameters to keep the same margin [@stark18]. The correct phrase is &quot;plausibly post-quantum,&quot; never &quot;post-quantum secure&quot; as if it were a theorem.


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.


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 &quot;How Zero-Knowledge Broke in Real Life.&quot; Everything here is *structural*: it would still be a bug on perfect hardware with a perfect random-number generator.


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.


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.

&lt;h2&gt;12. Making &quot;Verified&quot; Mean What You Think&lt;/h2&gt;
&lt;p&gt;Return one last time to the verifier that returned &lt;code&gt;true&lt;/code&gt;. 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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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&apos;s clock running. The one place the floor itself is genuinely at risk is Q-Day, when Shor&apos;s algorithm reaches the algebraic assumptions -- the single scenario this article cannot file under &quot;the layer humans built.&quot;&lt;/p&gt;

The cryptography was never the weak point. The discipline is making sure that when the verifier says &quot;true,&quot; it means the thing you actually intended -- constraint by constraint, string by string, hash input by hash input.
&lt;p&gt;Name the layer, and you have named the break. For how these systems are constructed in the first place, see &quot;Never Decrypted: Proving You Ran the Computation.&quot; For the implementation and side-channel failures deliberately held out of scope here, see &quot;How Zero-Knowledge Broke in Real Life.&quot; Both are the other half of learning to trust a proof you did not watch being made.&lt;/p&gt;
&lt;p&gt;&amp;lt;StudyGuide slug=&quot;how-zero-knowledge-proofs-actually-break&quot; keyTerms={[
  { term: &quot;Soundness&quot;, definition: &quot;The guarantee that a cheating prover cannot convince an honest verifier of a false statement except with negligible probability; the property attackers target.&quot; },
  { term: &quot;Under-constrained circuit&quot;, definition: &quot;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.&quot; },
  { term: &quot;Toxic waste&quot;, definition: &quot;The secret scalars a trusted setup samples and must destroy; whoever retains them holds a trapdoor that forges any statement.&quot; },
  { term: &quot;Fiat-Shamir transform&quot;, definition: &quot;Replacing a verifier&apos;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.&quot; },
  { term: &quot;Transparent setup&quot;, definition: &quot;A setup using only public randomness, with no secret trapdoor to leak; still a setup, just not a trusted one.&quot; },
  { term: &quot;Knowledge soundness&quot;, definition: &quot;A strengthening of soundness requiring that any prover who convinces the verifier must actually know a witness, formalized by an extractor.&quot; }
]} /&amp;gt;&lt;/p&gt;
</content:encoded><category>zero-knowledge-proofs</category><category>zk-snarks</category><category>cryptography</category><category>soundness</category><category>fiat-shamir</category><category>trusted-setup</category><category>circuit-security</category><category>zk-starks</category><author>noreply@paragmali.com (Parag Mali)</author></item><item><title>The Anonymity That Actually Holds: What Zcash and Monero Prove, and How Each Proof Breaks</title><link>https://paragmali.com/blog/the-anonymity-that-actually-holds-what-zcash-and-monero-prov/</link><guid isPermaLink="true">https://paragmali.com/blog/the-anonymity-that-actually-holds-what-zcash-and-monero-prov/</guid><description>A mechanism-level teardown of on-chain anonymity: how Zcash&apos;s zk-SNARK proof and Monero&apos;s ring-signature ambiguity work, and the exact break inside each.</description><pubDate>Sun, 19 Jul 2026 00:00:00 GMT</pubDate><content:encoded>
On a public ledger, anonymity is not asserted -- it is *proved*, and every proof carries the break condition of the assumption it rests on. Zcash proves a spend is valid and hidden in the shielded set; its break is an *unsound* proof (the 2018 BCTV14 flaw that would have allowed undetectable counterfeiting) or a set too small to hide in. Monero makes the real spend *statistically* ambiguous among decoys; its break is anything that makes that spend guessable, as the zero-mixin cascades and &quot;guess the newest&quot; heuristic did to early Monero. Name the proof, and you have named the break -- and by 2026 the sharpest break is legal, not cryptographic.
&lt;h2&gt;1. Two Coins, Two Hidden Break Conditions&lt;/h2&gt;
&lt;p&gt;For roughly the first two years of Zcash&apos;s life, a flaw buried in its zero-knowledge proof meant that someone holding the right secret could have minted counterfeit shielded coins out of nothing, and no node, auditor, or observer could ever have caught them at it [@ecc-postmortem]. In those same years, a team of academics sat down with the Monero blockchain -- a ledger marketed as untraceable -- and correctly guessed the real coin behind roughly four out of five &quot;hidden&quot; spends [@moser-popets2018]. Both coins were sold as anonymous. Each shipped with a break condition sealed inside its own proof.&lt;/p&gt;
&lt;p&gt;That pairing is no coincidence; it is the whole argument of this article. Zcash and Monero are the two most serious answers to one hard question: how do you move value on a public ledger so sender, recipient, and amount stay hidden while everyone can still verify it? They answered in opposite ways. Zcash writes a &lt;em&gt;mathematical proof&lt;/em&gt; that a valid, authorized, balanced spend is hidden somewhere in a set. Monero makes the real spend &lt;em&gt;one of many plausible candidates&lt;/em&gt; and proves nothing about which is real. They are not merely different mechanisms but different break conditions, chosen at design time.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; Anonymity on a public ledger is not asserted; it is &lt;em&gt;proved&lt;/em&gt;, and a proof is only as strong as the one assumption whose failure is its break condition. Zcash proves a spend is valid and hidden, so its break is a flaw in the proof system (soundness) or in the crowd the proof ranges over (the shielded set). Monero writes a &lt;em&gt;statistical&lt;/em&gt; proof that the spend is one of N, so its break is anything that makes N collapse -- a guessable real spend. Name the proof, and you have named the break.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The distinction that organizes everything below is &lt;em&gt;hidden&lt;/em&gt; versus &lt;em&gt;provably hidden&lt;/em&gt;. A magician&apos;s coin is hidden: you cannot see it, but nothing stops you reasoning about where it went. A &lt;em&gt;provably&lt;/em&gt; hidden coin comes with a guarantee -- a statement of exactly what an adversary cannot learn, under exactly which assumption. The value of the guarantee is the value of that assumption. When the assumption is &quot;this proof system is sound,&quot; the break is a soundness bug. When it is &quot;the real spend is indistinguishable from its decoys,&quot; the break is a statistical tell.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Every privacy claim in this article resolves to one question: what, precisely, is being proved, and what happens when the single assumption behind that proof fails? &quot;My transactions are hidden&quot; is marketing. &quot;This spend is one of sixteen, and here is the exact way that set can be narrowed&quot; is engineering. The rest of the article is about turning the first sentence into the second, for both coins.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So this is not a chronology or a feature comparison. It is one claim, argued twice: the anonymity that holds is the anonymity you can prove, and each proof hands you its own break condition for free. The path runs from two 2013 designs that forked at the root, through each coin&apos;s construction and documented failure, to the moment the two failures turn out to be the same shape -- and then to a 2026 break no proof, however perfect, can touch. First, though, we need one precise definition of what on-chain anonymity has to prove.&lt;/p&gt;
&lt;h2&gt;2. What On-Chain Anonymity Actually Has to Prove&lt;/h2&gt;
&lt;p&gt;Start with what everyone half-knows: a Bitcoin address is not a name, but it is not anonymous either. Because the ledger is public and permanent, every payment an address ever makes or receives is visible forever, and addresses can be linked to each other and, often, to a person. That is &lt;em&gt;pseudonymity&lt;/em&gt;, not anonymity.&lt;/p&gt;
&lt;p&gt;The gap between the two -- &lt;a href=&quot;https://paragmali.com/blog/pseudonymous-was-never-anonymous-what-bitcoins-transaction-g/&quot; rel=&quot;noopener&quot;&gt;the taxonomy of pseudonymity, unlinkability, and untraceability&lt;/a&gt; -- is the subject of the companion post &quot;Pseudonymous Was Never Anonymous,&quot; which this article assumes rather than repeats, along with how Bitcoin&apos;s base layer gets deanonymized in practice. Here we pick up where it leaves off: given that a transparent pseudonymous ledger is not enough, what would a &lt;em&gt;genuinely&lt;/em&gt; private payment have to accomplish?&lt;/p&gt;
&lt;p&gt;The answer is more demanding than &quot;hide who I am,&quot; because a payment system also has to stay honest. On a public ledger, anyone can verify the rules: no coins are created from nothing, no coin is spent twice, and only the owner can spend. A privacy design cannot quietly drop those checks. It has to hide the transaction &lt;em&gt;and&lt;/em&gt; keep proving it valid at the same time.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Every private on-chain payment must do four things simultaneously: hide the &lt;strong&gt;sender&lt;/strong&gt;, hide the &lt;strong&gt;recipient&lt;/strong&gt;, hide the &lt;strong&gt;amount&lt;/strong&gt;, and still &lt;strong&gt;prove the transaction is valid&lt;/strong&gt; (balanced, authorized, and not a double-spend). Drop any of the first three and you have leaked privacy; drop the fourth and you have built a counterfeiting machine. The entire difficulty is doing all four together, in public, with no trusted referee.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Both coins inherit Bitcoin&apos;s basic accounting shape. Money exists as discrete &lt;em&gt;outputs&lt;/em&gt;; a transaction consumes some existing outputs as inputs and creates new outputs, and the network must confirm that the inputs were real, unspent, and authorized, and that value in equals value out. Transparency makes those checks trivial, because everything is in the clear. Privacy makes them hard, because now the checks have to pass over data nobody is allowed to see. That tension -- verify what you cannot look at -- is the technical heart of the field.&lt;/p&gt;

The anonymity set is the collection of possibilities an adversary cannot distinguish among after doing their best analysis. If your spend is genuinely indistinguishable from any of $N$ candidates, your anonymity set has size $N$ and the adversary&apos;s best guess is correct with probability $1/N$. The *nominal* anonymity set is the size the design advertises; the *effective* anonymity set is what survives an adversary&apos;s heuristics. Almost every result in this article is a gap between those two numbers.
&lt;p&gt;There are only two known ways to make those four requirements hold at once without a trusted referee, and they define the two halves of this article. The first is to attach a &lt;strong&gt;cryptographic proof&lt;/strong&gt;: hide the spend behind commitments, then publish a zero-knowledge argument that a valid spend exists in a committed set, revealing nothing else. The second is to manufacture &lt;strong&gt;ambiguity&lt;/strong&gt;: present the real spend alongside decoys so that, to any observer, it is merely one of many, and design the rest of the transaction so validity still checks out. Zcash took the first road, Monero the second.&lt;/p&gt;
&lt;p&gt;The pseudonymity-versus-anonymity taxonomy, and the specifics of Bitcoin base-layer deanonymization, belong to the companion post and are deliberately out of scope here. This piece is about the two &lt;em&gt;constructions&lt;/em&gt; that sit above that boundary, and about the precise adversary each one faces -- which is also why the recurring question below is never just &quot;is it private?&quot; but &quot;private against whom, within which set?&quot; That framing comes from the corpus&apos;s &lt;a href=&quot;https://paragmali.com/blog/secure-against-whom-the-security-definitions-every-protocol-/&quot; rel=&quot;noopener&quot;&gt;threat-model post&lt;/a&gt;, &quot;Secure Against Whom.&quot;&lt;/p&gt;
&lt;p&gt;One more thing follows from the definition of an anonymity set: a privacy claim is meaningless until you name the adversary. &quot;Anonymous&quot; against a casual block-explorer user is a different property than &quot;anonymous&quot; against a well-funded chain-analysis firm with heuristics, timing data, and network telemetry. Keep that question -- &lt;em&gt;anonymous against whom?&lt;/em&gt; -- in view; it is the question that will still be standing at the end, after every proof has been named and every break condition counted. Two designs answered all of this in 2013, months apart, and they disagreed from the first line about what &quot;hidden&quot; should mean.&lt;/p&gt;
&lt;h2&gt;3. Two Bloodlines That Forked in 2013&lt;/h2&gt;
&lt;p&gt;Both design philosophies were born in the same year, months apart, as two answers to the same frustration. By 2013 the state of the art in on-chain privacy was &lt;em&gt;mixing&lt;/em&gt;: hand your coins to a tumbler that shuffles them with other people&apos;s, or coordinate a joint transaction (CoinJoin) so the graph shows only &quot;one of $k$ paid one of $k$.&quot; Mixing works operationally but proves nothing.&lt;/p&gt;
&lt;p&gt;A tumbler can steal your coins or keep logs that unwind every link; and CoinJoin, as the Zerocash authors noted, &quot;can thus only mix small volumes of coins amongst users who are currently online, is prone to denial-of-service attacks, and requires effort to find mixing partners&quot; [@zerocash-sp2014]. Both 2013 lineages set out to remove the trusted third party and the online coordination -- and split immediately on &lt;em&gt;how&lt;/em&gt;.&lt;/p&gt;

A ring signature lets someone sign a message on behalf of a *set* (a &quot;ring&quot;) of public keys, so that any verifier is convinced that *some* member of the ring signed, but cannot tell *which one*. Unlike a group signature, there is no manager and no way to later revoke the anonymity. The ring is assembled ad hoc by the signer, who is the only real member; the rest are decoys pulled from the ledger.
&lt;p&gt;The &lt;strong&gt;ambiguity&lt;/strong&gt; lineage came first in spirit. The CryptoNote v2.0 whitepaper, published in 2013 under the pseudonym Nicolas van Saberhagen, defined untraceability through ring signatures (a spend is signed by one of $N$ possible keys) and unlinkability through one-time addresses (each payment lands at a freshly derived key) [@cryptonote-v2]. In April 2014 a community launched Monero as a durable, no-premine deployment of that design [@monero-upgrades]. Nothing here is proved about which output was really spent; the security comes entirely from the spend being lost in a crowd the spender assembles.&lt;/p&gt;
&lt;p&gt;CryptoNote&apos;s own history is murky: the v2.0 whitepaper is dated 17 October 2013, but a v1.0 carries a December 2012 date and researchers have questioned whether the timestamps were backdated. &quot;Nicolas van Saberhagen&quot; is a pseudonym whose identity has never been established. This article treats &quot;2013 (CryptoNote v2.0)&quot; as the design of record and does not attribute the work to any named person [@cryptonote-v2].&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;proof&lt;/strong&gt; lineage started the same year with the opposite instinct. Zerocoin (Miers, Garman, Green, and Rubin, 2013 IEEE Symposium on Security and Privacy) asked whether anonymity could be &lt;em&gt;proved&lt;/em&gt; rather than merely obscured: mint a coin by publishing a commitment that joins a public accumulator of minted coins, then spend it by proving in zero knowledge that you know the opening of &lt;em&gt;some&lt;/em&gt; commitment in it, plus a serial number to stop double-spends [@zerocoin-sp2013]. The idea was correct and the proofs enormous -- more than 45 kB each and around 450 ms to verify, far too heavy to be a currency at scale [@zerocash-sp2014].&lt;/p&gt;
&lt;p&gt;That cost set up the successor. Zerocash (Ben-Sasson, Chiesa, Garman, Green, Miers, Tromer, and Virza, 2014 IEEE Symposium) replaced the heavy accumulator proof with a succinct one -- a zk-SNARK over note commitments and nullifiers that hides sender, recipient, and amount while proving the transaction balanced and authorized [@zerocash-sp2014]. That construction became Zcash.&lt;/p&gt;

A zk-SNARK is a Zero-Knowledge Succinct Non-interactive ARgument of Knowledge. &quot;Zero-knowledge&quot; means the proof reveals nothing beyond the truth of the statement; &quot;succinct&quot; means the proof is small and fast to check regardless of how complex the statement is; &quot;non-interactive&quot; means it is a single message with no back-and-forth; and &quot;argument of knowledge&quot; means a computationally bounded prover cannot produce a convincing proof unless the statement is true and they actually know a witness for it.
&lt;p&gt;A second footnote to Zerocoin&apos;s story matters more than the proof size, because it foreshadows the whole article. Zerocoin carried a break with nothing to do with the soundness of its zero-knowledge proof. Ruffing, Thyagarajan, Ronge, and Schroeder showed in 2018, in a paper aptly titled &quot;Burning Zerocoins,&quot; that a network attacker could permanently destroy honest users&apos; coins -- a &quot;cryptographic denial-of-spending attack&quot; that, in the authors&apos; words, &quot;does not contradict the formal security analyses&quot; of the original scheme, because it exploited a property nobody had modeled [@ruffing-burning-zerocoins].&lt;/p&gt;
&lt;p&gt;Hold onto that shape: a construction can be &lt;em&gt;proved&lt;/em&gt;, in the exact sense its authors intended, and still break at the edge of what the proof was ever about. &quot;Proved&quot; and &quot;unbreakable&quot; are different words, and the distance between them is where most of this article lives.&lt;/p&gt;
&lt;p&gt;There is one more seam worth flagging now, because it detonates in Section 5. Zerocash&apos;s succinct proof rode on the pairing-based zk-SNARK template introduced by Pinocchio (Parno, Howell, Gentry, and Raykova, 2013), and the concrete construction Zcash shipped -- known as BCTV14, after Ben-Sasson, Chiesa, Tromer, and Virza -- inherited its security argument from that lineage rather than carrying a dedicated proof of its own [@pinocchio-pghr13] [@bctv14]. Hold that thought too.&lt;/p&gt;

flowchart TD
    Root[&quot;2013: a public ledger is transparent and pseudonyms are linkable&quot;]
    Root --&amp;gt; Proof[&quot;Proof school: hide the spend, prove it is valid anyway&quot;]
    Root --&amp;gt; Amb[&quot;Ambiguity school: make the real spend one of N candidates&quot;]
    Proof --&amp;gt; P1[&quot;Zerocoin 2013 (accumulator + zero-knowledge membership)&quot;]
    P1 --&amp;gt; P2[&quot;Zcash Sprout 2016 (BCTV14 zk-SNARK)&quot;]
    P2 --&amp;gt; P3[&quot;Zcash Sapling 2018 (Groth16)&quot;]
    P3 --&amp;gt; P4[&quot;Zcash Orchard 2022 (Halo 2, no trusted setup)&quot;]
    Amb --&amp;gt; A1[&quot;CryptoNote 2013, Monero 2014 (ring signatures + stealth addresses)&quot;]
    A1 --&amp;gt; A2[&quot;RingCT 2017 (amounts hidden)&quot;]
    A2 --&amp;gt; A3[&quot;Mandatory RingCT 2017; ring 11 + Bulletproofs 2018&quot;]
    A3 --&amp;gt; A4[&quot;CLSAG 2020; ring 16 + Bulletproofs+ 2022&quot;]
    P4 --&amp;gt; Law[&quot;AMLR Article 79 legal ban, effective 10 July 2027&quot;]
    A4 --&amp;gt; Law
&lt;p&gt;Here is the thesis in historical form: the split in &lt;em&gt;proof strategy&lt;/em&gt; is the split in &lt;em&gt;break condition&lt;/em&gt;, present at birth. Choose to hide by proof, and you inherit two ways to fail -- an unsound proof, or a set too small to hide in. Choose to hide by ambiguity, and you inherit exactly one -- a real spend that becomes guessable. Everything that follows is those two inheritances playing out. The proof school promised something that sounds impossible, so we start there: how do you prove a spend is valid without revealing which spend it is?&lt;/p&gt;
&lt;h2&gt;4. The Zcash Construction: Proving a Spend You Cannot See&lt;/h2&gt;
&lt;p&gt;Zcash&apos;s shielded pool is best understood as a machine for making one specific statement believable without evidence you can inspect. To spend, you must convince every node that a real, unspent coin you own is being consumed and that value balances -- while showing them none of: which coin, whose coin, or how much. It pulls this off with three moving parts and a proof that ties them together.&lt;/p&gt;
&lt;p&gt;The first part is the &lt;strong&gt;note&lt;/strong&gt;. A shielded coin is a &lt;em&gt;note&lt;/em&gt; -- a bundle of (value, owner key, randomness) -- that never appears on chain in the clear. What appears is its commitment.&lt;/p&gt;

A note commitment is a cryptographic commitment to the contents of a note (its value, the owner&apos;s key, and a random nonce). Like a sealed envelope, it *binds* the sender to those exact contents (they cannot change them later) while *hiding* them from everyone. Every new note&apos;s commitment is appended to a global, append-only Merkle tree, so &quot;this note exists&quot; becomes &quot;this commitment is a leaf in the tree.&quot;
&lt;p&gt;The second part solves double-spending without identity. In a transparent chain you stop double-spends by pointing at the specific coin being consumed; here you are not allowed to point at anything. The answer is the &lt;strong&gt;nullifier&lt;/strong&gt;.&lt;/p&gt;

A nullifier is a deterministic tag derived from a note&apos;s secret data such that each note maps to exactly one nullifier, but the nullifier reveals nothing about which note produced it. Spending publishes the nullifier; the network records it in a set and rejects any future transaction that reuses it. This voids the note -- provably, and exactly once -- without ever disclosing which leaf of the commitment tree was spent.
&lt;p&gt;The third part is the &lt;strong&gt;zk-SNARK&lt;/strong&gt; that ties it together. A shielded spend carries a succinct zero-knowledge proof of the following statement: &lt;em&gt;I know an opening of a note whose commitment is a leaf in the tree; its nullifier is correctly derived; I am authorized to spend it; and the input value equals the output value&lt;/em&gt; [@zerocash-sp2014] [@zcash-protocol-spec]. A verifier who checks that proof learns only one bit -- the statement is true -- and nothing about the note, the owner, or the amount.&lt;/p&gt;

flowchart TD
    Note[&quot;A note commitment sits in the append-only tree (value + owner key + randomness)&quot;]
    Auth[&quot;Spender demonstrates spend authorization&quot;]
    Null[&quot;Nullifier is published, voiding the note without naming it&quot;]
    SNARK{&quot;zk-SNARK verifies: membership + authorization + nullifier + balance&quot;}
    Out[&quot;Fresh output note commitments are appended&quot;]
    Note --&amp;gt; SNARK
    Auth --&amp;gt; SNARK
    SNARK --&amp;gt; Null
    SNARK --&amp;gt; Out
&lt;p&gt;That is the model. Everything else in Zcash&apos;s history is a change of &lt;em&gt;proving system&lt;/em&gt; underneath the same model -- and each change was forced by a break condition, which is exactly the thesis. The pool has run three proving systems.&lt;/p&gt;
&lt;p&gt;The first, &lt;strong&gt;Sprout&lt;/strong&gt;, shipped at the 2016 mainnet launch on the BCTV14 zk-SNARK, encoding a spend proof in 288 bytes [@zcash-begins] [@zcash-protocol-spec].The protocol specification encodes the Sprout proof in 288 bytes and the Sapling Groth16 proof in 192 bytes; the older literature sometimes quotes 296 bytes for Sprout because of a difference in point encoding [@zcash-protocol-spec]. Its parameters came from a multi-party &quot;trusted setup&quot; ceremony -- which introduces the one idea most people get wrong.&lt;/p&gt;

A pairing-based zk-SNARK needs public parameters called a structured reference string (SRS), generated from secret randomness. That secret is the &quot;toxic waste&quot;: anyone who retains it can forge proofs of false statements. A trusted-setup ceremony has many participants jointly generate the SRS so that the waste is safe as long as *at least one* participant honestly destroys their share. Note carefully what the waste can and cannot do.

The single most common misconception about Zcash is that the trusted-setup &quot;toxic waste&quot; is a key that decrypts people&apos;s transactions. It is not. The secret governs the proof system&apos;s *soundness*, not its *privacy*. A party who kept the waste could counterfeit coins by proving false statements, which is catastrophic for the money supply -- but they could not deanonymize a single shielded transaction, because the hiding comes from the commitments and the zero-knowledge property, not from the SRS. When you read &quot;trusted setup risk,&quot; read &quot;counterfeiting risk,&quot; never &quot;surveillance backdoor.&quot;
&lt;p&gt;The second system, &lt;strong&gt;Sapling&lt;/strong&gt; (October 2018), kept the model but swapped in Jens Groth&apos;s 2016 pairing-based zk-SNARK, &quot;Groth16,&quot; whose proof is just three group elements -- 192 bytes -- and, unlike BCTV14, carries a rigorous, independently reviewed security analysis [@groth16] [@zcash-protocol-spec] [@ecc-sapling-foundations]. Purpose-built curve and circuit engineering cut the prover&apos;s cost by roughly 80% in time and 98% in memory versus Sprout, which is what first made shielding feasible on a phone [@ecc-sapling-faster]. Sapling still needs a per-circuit trusted setup; it fixed the soundness of the proof, not the existence of the ceremony.&lt;/p&gt;
&lt;p&gt;The third system, &lt;strong&gt;Orchard&lt;/strong&gt;, activated at Network Upgrade 5 on 31 May 2022 [@zcash-v5-release] and specified in ZIP 224 [@zcash-zip224], removed the ceremony altogether. It is built on &lt;strong&gt;Halo 2&lt;/strong&gt;, whose inner-product argument descends from the 2019 Halo proposal for recursion without a trusted setup [@halo-2019] and, in the Electric Coin Company&apos;s words, &quot;did not require a trusted setup and had relatively small proof sizes, at the cost of poor verification performance&quot; [@ecc-halo2]. Orchard runs over the Pallas and Vesta (&quot;Pasta&quot;) curves [@zcash-protocol-spec] -- a transparent, no-setup instantiation, not a universal or updatable SNARK.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Proving system (era)&lt;/th&gt;
&lt;th&gt;Spend proof size&lt;/th&gt;
&lt;th&gt;Trusted setup&lt;/th&gt;
&lt;th&gt;Curve&lt;/th&gt;
&lt;th&gt;Status&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Sprout / BCTV14 (2016)&lt;/td&gt;
&lt;td&gt;288 bytes [@zcash-protocol-spec]&lt;/td&gt;
&lt;td&gt;Yes, per-circuit&lt;/td&gt;
&lt;td&gt;pairing-friendly (BN)&lt;/td&gt;
&lt;td&gt;Superseded 2018&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sapling / Groth16 (2018)&lt;/td&gt;
&lt;td&gt;192 bytes, three elements [@zcash-protocol-spec]&lt;/td&gt;
&lt;td&gt;Yes, per-circuit&lt;/td&gt;
&lt;td&gt;BLS12-381 + Jubjub&lt;/td&gt;
&lt;td&gt;Active&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Orchard / Halo 2 (2022)&lt;/td&gt;
&lt;td&gt;Logarithmic in circuit size, no fixed constant [@ecc-halo2]&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Pallas / Vesta (&quot;Pasta&quot;)&lt;/td&gt;
&lt;td&gt;Active default&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The seam that mattered: BCTV14 shipped without a dedicated security proof and leaned on the Pinocchio/PGHR13 analysis it descended from. Bryan Parno had already found a &lt;em&gt;different&lt;/em&gt; error in BCTV14 in 2015, a hint that the construction&apos;s proof story was thinner than its deployment suggested [@pinocchio-pghr13] [@parno15]. That gap between &quot;used in production&quot; and &quot;proven sound&quot; is precisely where the 2018 break was hiding.&lt;/p&gt;
&lt;p&gt;Read the table against the thesis and the shape is clear: this is a &lt;em&gt;literal mathematical proof&lt;/em&gt;, so the guarantee is exactly as strong as the proof&apos;s soundness. Sapling and Orchard improved the guarantee by improving the proof -- better analysis, then no ceremony. But that framing carries an unavoidable implication. If your anonymity is a proof, then a false proof is a broken anonymity. This proof shipped in 2016. In 2018, someone discovered it was unsound. What does it even mean for a proof to be false?&lt;/p&gt;
&lt;h2&gt;5. When the Proof Itself Is Unsound: The BCTV14 Break&lt;/h2&gt;
&lt;p&gt;On 1 March 2018, Ariel Gabizon, a cryptographer at the Electric Coin Company, was studying the BCTV14 setup and noticed something that should not have been there. The parameter-generation procedure, as described in the paper, &quot;mistakenly produces extra elements that violate the soundness of the proving system&quot; [@ecc-postmortem]. An honest prover ignores those elements and never notices. But their presence, in the postmortem&apos;s exact words, &quot;allows a cheating prover to circumvent a consistency check, and thereby transform the proof of one statement into a valid-looking proof of a different statement&quot; [@ecc-postmortem].&lt;/p&gt;

&quot;The parameter setup algorithm ... mistakenly produces extra elements that violate the soundness of the proving system.&quot; -- Electric Coin Company postmortem [@ecc-postmortem]

A proof system is *sound* if no prover can produce a convincing proof of a false statement, except with negligible probability. For succinct *arguments* like zk-SNARKs the guarantee is *computational* soundness: no prover bounded to feasible computation can forge a proof, assuming some hardness problem holds. Soundness is the promise that &quot;verified&quot; means &quot;true.&quot; Break it, and a verified proof means nothing.
&lt;p&gt;What &lt;em&gt;were&lt;/em&gt; those extra elements, concretely? This is the part summaries skip, and the mechanism is the whole lesson. Gabizon&apos;s own write-up (IACR ePrint 2019/119) states it precisely: the flaw &quot;stems from including redundant elements in the CRS,&quot; the structured reference string -- elements the Pinocchio/PGHR13 template had &lt;em&gt;deliberately excluded&lt;/em&gt; and that are, in his words, &quot;vital not to expose&quot; [@gabizon-2019-119]. Publishing them let a cheating prover do something specific and devastating: &quot;creating a proof of knowledge for &lt;em&gt;any&lt;/em&gt; public input given a valid proof for &lt;em&gt;some&lt;/em&gt; public input&quot; [@gabizon-2019-119].&lt;/p&gt;
&lt;p&gt;In other words, hand the attacker one honestly generated proof, and the redundant CRS elements let them retarget it to a statement of their choosing. The BCTV14 paper&apos;s own errata later acknowledged it &quot;fixes an error in Appendix B, found by Ariel Gabizon&quot; [@bctv14]. With the excluded elements kept out, the construction was secure in an idealized generic-group model -- but the deployed parameters kept them in [@gabizon-2019-119].&lt;/p&gt;
&lt;p&gt;Read what that retargeting buys an attacker. The shielded pool&apos;s whole security rests on the proof meaning &quot;this spend is valid and balanced.&quot; If a cheating prover can attach that same certificate to a &lt;em&gt;false&lt;/em&gt; statement, they can prove a spend is balanced when it is not -- creating shielded value from nothing. This is not a privacy bug; it is a counterfeiting bug, and it would have been invisible.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Forged value minted &lt;em&gt;inside&lt;/em&gt; the shielded pool is cryptographically undetectable: every proof still verifies, so no node, auditor, or user can distinguish an honest shielded balance from an inflated one. The only instrument that could ever register an anomaly is the &quot;turnstile&quot; that meters &lt;em&gt;net&lt;/em&gt; value crossing the boundary between the transparent and shielded pools in aggregate -- and it measures totals, not individuals. Zcash&apos;s turnstile never showed such an anomaly, and there is no evidence the flaw was exploited [@ecc-postmortem]. &quot;Undetectable&quot; here is a statement about the mathematics, not a guess about attacker behavior.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The proof was real mathematics, and it was false. The human story around that sentence is small and tense. Gabizon confirmed the flaw with his colleague Sean Bowe, and the two briefed chief executive Zooko Wilcox and engineer Nathan Wilcox. For a time just four people knew that the currency they were building could, in principle, be counterfeited without a trace [@ecc-postmortem].&lt;/p&gt;
&lt;p&gt;The exploit required data that survived only in the original Sprout multi-party ceremony transcript -- the extra elements are present there even though the shipped parameter files omit them -- so the company quietly removed the transcript, maintaining a cover story that it had been lost to accidental deletion [@ecc-postmortem].&lt;/p&gt;
&lt;p&gt;The fix was not a patch, because you do not patch an unsound proof. You replace it. Sapling&apos;s move to the independently analyzed Groth16 circuit, activated 28 October 2018, migrated shielded value off the flawed system entirely, and only then -- on 5 February 2019, eleven months after discovery -- did the company disclose publicly [@ecc-postmortem].&lt;/p&gt;
&lt;p&gt;The postmortem records how far the flaw reached: the same bug had propagated into Fuchsbauer&apos;s subversion-zero-knowledge adaptation and the ADSNARK scheme, and the Sprout parameters themselves came from a multi-party protocol (BGG17) that carried the extra elements forward [@fuchsbauer17] [@adsnark-bbfr14] [@bgg17]. Parno&apos;s 2015 note had already flagged a separate soundness problem in the same construction [@parno15]. The mathematics was reviewed, published, deployed -- and still wrong for years.&lt;/p&gt;

flowchart TD
    Setup[&quot;BCTV14 parameter setup emits redundant CRS elements (unused by honest provers)&quot;]
    Setup --&amp;gt; Honest[&quot;Honest prover ignores the extra elements&quot;]
    Setup --&amp;gt; Cheat[&quot;Cheating prover keeps the extra elements&quot;]
    Honest --&amp;gt; HCheck{&quot;Consistency check&quot;}
    HCheck --&amp;gt; Sound[&quot;Only true statements pass: valid spends&quot;]
    Cheat --&amp;gt; Bypass{&quot;Consistency check is circumvented&quot;}
    Bypass --&amp;gt; Forge[&quot;Some-input to any-input: a false statement passes, undetectable in-pool counterfeiting&quot;]
&lt;p&gt;The postmortem says the flaw was responsibly disclosed to two other affected projects but does not name them; secondary press identified them as Horizen and Komodo. Attribute those names to reporting, not to the primary source, which names no one [@ecc-postmortem].&lt;/p&gt;
&lt;p&gt;That is one way a proof-based system breaks: the proof is wrong. There is a second, quieter way that survives even a perfect proof. A zero-knowledge proof of membership only hides you inside the set it ranges over -- and for Zcash, that set is whatever value has &lt;em&gt;actually&lt;/em&gt; been shielded.&lt;/p&gt;
&lt;p&gt;Kappos, Yousaf, Maller, and Meiklejohn measured the deployed pool: only 14.96% of transactions ever interacted with the shielded pool at all, roughly 3.9 million ZEC flowed in and back out in a near-perfect reflection dominated by founders and miners, and a handful of simple usage heuristics cut the effective anonymity set by 69.1% [@kappos-usenix2018]. The proof was sound; the crowd it proved over was thin.&lt;/p&gt;
&lt;p&gt;The Kappos measurement is blunt about &lt;em&gt;who&lt;/em&gt; was in the pool: about 3,901,124 ZEC were deposited and 3,788,889 withdrawn in a &quot;near-perfect reflection ... accounted for almost fully by the founders and miners,&quot; with roughly 76.7% of pool deposits coming from miners [@kappos-usenix2018]. A shielded pool used mostly as a pass-through by two known cohorts is a small crowd to hide in.&lt;/p&gt;
&lt;p&gt;So the proof school&apos;s break condition has two faces, and we can write the first row of a table we will fill in completely later. What Zcash proves, and how, and the exact assumption whose failure is the break:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;System&lt;/th&gt;
&lt;th&gt;What is proved&lt;/th&gt;
&lt;th&gt;By what mechanism&lt;/th&gt;
&lt;th&gt;Break condition&lt;/th&gt;
&lt;th&gt;Anonymous against whom / within which set&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Zcash Sapling&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&quot;This spend is valid, authorized, balanced, and hidden in the shielded set&quot;&lt;/td&gt;
&lt;td&gt;Groth16 zk-SNARK over BLS12-381, note commitments and nullifiers&lt;/td&gt;
&lt;td&gt;An &lt;strong&gt;unsound proof&lt;/strong&gt; (the BCTV14 precedent) or a &lt;strong&gt;trusted-setup leak&lt;/strong&gt; -- and a &lt;strong&gt;thin real set&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A chain observer, within the set that actually shielded (small in practice) [@kappos-usenix2018]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Zcash Orchard&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The same statement, with no ceremony to trust&lt;/td&gt;
&lt;td&gt;Halo 2 inner-product argument (transparent) plus recursion&lt;/td&gt;
&lt;td&gt;An &lt;strong&gt;unsound argument or discrete-log break&lt;/strong&gt;; the trusted-setup class is removed, but the &lt;strong&gt;set is still thin&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A chain observer, within the Orchard set (opt-in) [@ecc-halo2]&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The proof school&apos;s failure was a discrete event with a date and four names. The other school never had a single break at all. Its anonymity eroded slowly, one measurement at a time -- and to see why, we need Monero&apos;s construction.&lt;/p&gt;
&lt;h2&gt;6. The Monero Construction: Hiding in a Crowd You Assemble Yourself&lt;/h2&gt;
&lt;p&gt;Monero makes the opposite bet. It proves nothing about which output was spent. Instead it makes the real spend one of many plausible candidates and arranges the rest of the transaction so validity still checks out. Four primitives work in lockstep to hide sender, recipient, and amount while proving the payment is honest.&lt;/p&gt;
&lt;p&gt;Sender ambiguity comes from the &lt;strong&gt;ring signature&lt;/strong&gt;. To spend an output, you assemble a ring of that one real output plus a set of decoys pulled from the chain, and produce a signature proving that &lt;em&gt;some&lt;/em&gt; ring member authorized the spend, without revealing which [@cryptonote-v2]. A verifier sees a valid signature over a ring of, today, sixteen members, and has no cryptographic handle on which one is real.&lt;/p&gt;
&lt;p&gt;That immediately raises a problem: if you never say which output you spent, how does the network stop you from spending it twice? The answer is a &lt;em&gt;linkable&lt;/em&gt; ring signature and its &lt;strong&gt;key image&lt;/strong&gt;.&lt;/p&gt;

A key image is a deterministic tag derived from the real signing key, published with every Monero spend. It is unique to the key that actually signed, but reveals nothing about which ring member that was. The network keeps a set of seen key images and rejects any transaction that reuses one. This is the exact analogue of Zcash&apos;s nullifier: it prevents double-spends by catching reuse, not by naming the coin.
&lt;p&gt;Recipient unlinkability comes from &lt;strong&gt;stealth addresses&lt;/strong&gt;.&lt;/p&gt;

A stealth, or one-time, address is a fresh public key that the sender derives for each payment from the recipient&apos;s long-term address, using a shared secret only the two of them can compute. Funds land at that one-time key, never at the published address, so two payments to the same person produce two unrelated on-chain destinations that no observer can link.
&lt;p&gt;Amount hiding comes from &lt;strong&gt;RingCT&lt;/strong&gt; (Ring Confidential Transactions), which bolts &lt;a href=&quot;https://paragmali.com/blog/the-discrete-log-held-the-proofs-leaked-a-field-guide-to-com/&quot; rel=&quot;noopener&quot;&gt;Pedersen-committed amounts&lt;/a&gt; and range proofs onto the ring signature [@ringct-mrl0005] [@confidential-tx].&lt;/p&gt;

A Pedersen commitment to a value $v$ with random blinding factor $r$ is $C = vG + rH$, where $G$ and $H$ are fixed group generators with no known relationship. It perfectly hides $v$ (any $C$ is consistent with any value, given the right $r$) and is *additively homomorphic*: $C(v_1, r_1) + C(v_2, r_2) = C(v_1 + v_2,\; r_1 + r_2)$. That homomorphism is what lets a verifier confirm inputs minus outputs equal zero without learning a single amount.
&lt;p&gt;The homomorphism is worth seeing directly, because it is how a public network checks that hidden numbers balance. Commit two amounts with independent blinding factors, and the product (in a multiplicative group) or sum (in the additive elliptic-curve notation Monero actually uses) of the commitments equals the commitment to the summed values -- so &quot;the amounts balance&quot; becomes a single equality on commitments.&lt;/p&gt;
&lt;p&gt;{`
// Illustrative only: real Monero uses a large elliptic-curve group with additive
// notation C = vG + rH. Here we use modular exponentiation in a prime field, where
// the same homomorphism reads as a product of commitments. The point is the algebra.
const p = 2n ** 61n - 1n;   // a Mersenne prime, big enough for the demo
const g = 5n, h = 7n;       // two public generators with no known relationship&lt;/p&gt;
&lt;p&gt;const modpow = (base, exp, m) =&amp;gt; {
  let r = 1n; base %= m;
  while (exp &amp;gt; 0n) { if (exp &amp;amp; 1n) r = (r * base) % m; base = (base * base) % m; exp &amp;gt;&amp;gt;= 1n; }
  return r;
};&lt;/p&gt;
&lt;p&gt;// commit(v, r) = g^v * h^r  mod p  -- hides v behind the blinding factor r
const commit = (v, r) =&amp;gt; (modpow(g, v, p) * modpow(h, r, p)) % p;&lt;/p&gt;
&lt;p&gt;const v1 = 40n, r1 = 12345n;   // amount 1 and its blinding factor
const v2 = 25n, r2 = 67890n;   // amount 2 and its blinding factor&lt;/p&gt;
&lt;p&gt;const C1 = commit(v1, r1);
const C2 = commit(v2, r2);
const Csum = commit(v1 + v2, r1 + r2);&lt;/p&gt;
&lt;p&gt;console.log(&apos;C1 . C2 mod p        =&apos;, (C1 * C2) % p);
console.log(&apos;commit(v1+v2, r1+r2) =&apos;, Csum);
console.log(&apos;do they match?        &apos;, ((C1 * C2) % p) === Csum);
// A verifier tests whether input commitments minus output commitments cancel,
// confirming value in = value out, while learning neither v1 nor v2.
`}&lt;/p&gt;
&lt;p&gt;Range proofs finish the job. Without them, you could &quot;balance&quot; a transaction by committing to a &lt;em&gt;negative&lt;/em&gt; output, minting value while the equation still holds.&lt;/p&gt;

A range proof is a zero-knowledge proof that a committed value lies in a valid range (say, between zero and $2^{64}-1$) without revealing the value. It closes the negative-amount loophole in a confidential-transaction scheme. Monero&apos;s first range proofs (Borromean) were large; Bulletproofs replaced them with logarithmic-size proofs that need no trusted setup, and Bulletproofs+ shrank them further.
&lt;p&gt;Monero&apos;s Borromean range proofs were large and dominated transaction size. In October 2018 they were replaced by &lt;strong&gt;Bulletproofs&lt;/strong&gt; -- logarithmic-size range proofs with no trusted setup -- which cut the average Monero transaction size by at least 80% [@bulletproofs-sp2018] [@moneropedia-bulletproofs] [@stanford-bulletproofs]. A shorter successor, Bulletproofs+, shipped in August 2022 [@bulletproofs-plus].&lt;/p&gt;

flowchart TD
    Ring[&quot;Assemble a ring: 1 real output + 15 decoys from the chain&quot;]
    Sig[&quot;Linkable ring signature proves one member signed, not which&quot;]
    KI[&quot;Key image published, unique to the real key, catches double-spends&quot;]
    Stealth[&quot;Payment lands at a fresh one-time stealth address&quot;]
    Amt[&quot;Amount hidden by a Pedersen commitment + Bulletproof+ range proof&quot;]
    Ring --&amp;gt; Sig
    Sig --&amp;gt; KI
    Sig --&amp;gt; Stealth
    Stealth --&amp;gt; Amt
&lt;p&gt;The deployment history is a ratchet, and every notch is dated in the project&apos;s source of record: RingCT went live as optional in January 2017, became mandatory in September 2017, the ring size was fixed at 11 in October 2018, and raised to 16 in August 2022 [@monero-upgrades]. Alongside those, the signature scheme moved from MLSAG to the smaller, faster CLSAG in October 2020, with the same one-of-N guarantee [@clsag].&lt;/p&gt;
&lt;p&gt;That the signature could be swapped so freely -- MLSAG to CLSAG, with no change to the security story -- is itself a tell. A 2024 Cypher Stack review even found two gaps in CLSAG&apos;s formal security &lt;em&gt;proof&lt;/em&gt;, with, in the project&apos;s words, no effect on &quot;the Monero implementation of CLSAG, so users are not at risk&quot; [@clsag]. The break in Monero was never the signature scheme; it was always the decoys. You can optimize or even re-prove the signature all you like and the anonymity is unaffected, because the anonymity lives in the ring, not in the algebra of the signature [@clsag].&lt;/p&gt;
&lt;p&gt;Now hold this construction next to Zcash&apos;s and the deep difference is exact. Zcash makes a cryptographic proof, so it can have a soundness flaw. Monero makes a &lt;em&gt;statistical&lt;/em&gt; proof of ambiguity, so there is no soundness flaw to find -- there is no cryptographic assertion about which output was spent that could be false. The guarantee is instead exactly as strong as the crowd is indistinguishable. Which raises the only question that can break it: what if the decoys do not look like real spends?&lt;/p&gt;
&lt;h2&gt;7. When the Crowd Collapses: Monero&apos;s Statistical Break&lt;/h2&gt;
&lt;p&gt;Nobody forged a Monero coin. The break was subtler and, for a privacy coin, arguably worse: researchers simply &lt;em&gt;guessed&lt;/em&gt; which ring member was real, and were right most of the time. Because the hiding is statistical, the whole guarantee is only as good as the decoys, and early Monero decoys were terrible.&lt;/p&gt;
&lt;p&gt;The first lever was the ring of size one. In early Monero many inputs used no decoys, and a ring with one member is not a ring -- the real spend is named outright. Kumar, Fischer, Tople, and Saxena showed in 2017 that these zero-mixin inputs are not just individually traceable; they &lt;em&gt;cascade&lt;/em&gt; [@kumar-esorics2017]. Once an output is proven spent, it can be eliminated as a decoy everywhere it later appears. Each elimination can shrink another ring to one candidate, which is itself then proven spent, poisoning the next ring, and so on. One collapse propagates through the graph.&lt;/p&gt;

flowchart TD
    Z[&quot;Zero-mixin input, ring size 1: the real spend is named outright&quot;]
    Z --&amp;gt; P1[&quot;That output is now known to be spent&quot;]
    P1 --&amp;gt; R1[&quot;A later ring used it as a decoy, so eliminate it&quot;]
    R1 --&amp;gt; C1[&quot;That ring shrinks and its real input can be forced&quot;]
    C1 --&amp;gt; R2[&quot;The forced output poisons the next ring in turn&quot;]
    R2 --&amp;gt; C2[&quot;The crowd collapses ring by ring, a chain reaction&quot;]
&lt;p&gt;Moser and ten co-authors measured how bad this was and added a second, independent attack [@moser-popets2018]. As of one April 2017 snapshot, 64.04% of all transaction inputs carried no mixins at all, and about 62% of inputs with one or more mixins were deducible by that chain-reaction elimination.&lt;/p&gt;
&lt;p&gt;Then the temporal attack. Because early decoys were sampled with no regard for how old real spends tend to be, the real input was almost always the &lt;em&gt;newest&lt;/em&gt; member of its ring. The &quot;guess the newest&quot; heuristic was correct about 80% of the time over all transactions with at least one mixin, and still 81% even among transactions meeting the minimum ring size mandated after September 2017 [@moser-popets2018].&lt;/p&gt;

&quot;The real input is usually the &apos;newest&apos; input ... [we can] guess the real input with 80% accuracy over all transactions with 1 or more mixins.&quot; -- Moser et al., PoPETs 2018 [@moser-popets2018]
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; There is no theorem to disprove here and no soundness bug to find. Monero&apos;s ambiguity is a claim about a &lt;em&gt;probability distribution&lt;/em&gt;: the real spend should be indistinguishable from its decoys. The attacks do not break any math; they exploit the gap between the decoy distribution the software produced and the distribution real spends actually follow. When those two distributions differ, the crowd is only nominally a crowd.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The simulation below shows the mechanism and, just as importantly, the fix. With decoys sampled uniformly from all history, a genuinely recent real spend stands out as the newest ring member almost every time. Sample decoys from the &lt;em&gt;same&lt;/em&gt; recency distribution that real spends follow, and the real output stops standing out -- the attacker&apos;s success falls back toward the one-in-sixteen a random guess would give.&lt;/p&gt;
&lt;p&gt;{`
// Toy model of Monero decoy selection. Outputs are created over time; a higher index
// means a newer output. Real spends tend to be RECENT. We build a ring of the real
// output plus decoys, then apply the attacker&apos;s heuristic: guess the newest member.
const RING = 16;        // ring size (Monero has used 16 since August 2022)
const TRIALS = 50000;
const POOL = 100000;    // number of past outputs available as decoys&lt;/p&gt;
&lt;p&gt;function gaussian() { // Box-Muller, standard normal
  const u = Math.random(), v = Math.random();
  return Math.sqrt(-2 * Math.log(u)) * Math.cos(2 * Math.PI * v);
}
// Real spends skew recent: age is a small offset back from the newest output.
const realAge = () =&amp;gt; Math.floor(Math.abs(gaussian()) * 1800);&lt;/p&gt;
&lt;p&gt;function runSim(pickDecoy) {
  let correct = 0;
  for (let t = 0; t &amp;lt; TRIALS; t++) {
    const realIndex = POOL - 1 - realAge();     // a recent real output
    const ring = [realIndex];
    while (ring.length &amp;lt; RING) ring.push(pickDecoy());
    const guess = Math.max(...ring);            // heuristic: newest is real
    if (guess === realIndex) correct++;
  }
  return (100 * correct / TRIALS).toFixed(1);
}&lt;/p&gt;
&lt;p&gt;// 1) Naive UNIFORM decoys (early Monero): spread across all history.
const uniform = () =&amp;gt; Math.floor(Math.random() * POOL);
// 2) REALISTIC decoys: same recency skew as real spends.
const realistic = () =&amp;gt; POOL - 1 - realAge();&lt;/p&gt;
&lt;p&gt;console.log(&apos;Uniform decoys   -&amp;gt; guess-newest correct:&apos;, runSim(uniform),   &apos;%&apos;);
console.log(&apos;Realistic decoys -&amp;gt; guess-newest correct:&apos;, runSim(realistic), &apos;%&apos;);
console.log(&apos;Random baseline  -&amp;gt; 1 in 16:&apos;, (100 / RING).toFixed(1), &apos;%&apos;);
`}&lt;/p&gt;
&lt;p&gt;That is why every Monero mitigation reads like a direct answer to the attack that named it, which is the pattern this failure-mode style prizes. Mandatory RingCT (September 2017) meant RingCT outputs could no longer be spent by zero-mixin inputs, structurally ending the cascade at its source. Zero-decoy spends were eliminated. The ring size was fixed at 11, then 16, so no transaction advertises a distinctive ring size and the crowd is uniform. And decoy selection moved to a gamma distribution designed to mimic the real spend-age curve, directly targeting the guess-newest tell [@monero-upgrades] [@moser-popets2018].&lt;/p&gt;

Guess-newest works because real spends cluster at a particular age -- recent, but not the newest block, since coins need confirmations and people spend on human timescales. Uniform decoy selection ignores that shape, so the real input&apos;s age stands out. A gamma distribution is a two-parameter family tunable to the observed spend-age curve, so a decoy drawn from it looks, age-wise, like a plausible real spend. The mitigation is not &quot;more decoys&quot; but &quot;decoys drawn from the right distribution&quot; -- which is why sixteen realistic decoys beat sixty-four obvious ones, and why decoy realism, not ring size, is the live problem [@moser-popets2018].
&lt;p&gt;Kumar et al. (ESORICS 2017) and Moser et al. (PoPETs 2018) were concurrent and independent; the Moser paper cites the Kumar work as related. Read together they are the canonical measurement of Monero&apos;s statistical break, and the reason gamma-distributed decoy selection and larger rings became protocol priorities [@kumar-esorics2017] [@moser-popets2018].&lt;/p&gt;
&lt;p&gt;Note the honest epistemics, because they are the whole point. None of this made Monero &lt;em&gt;unconditionally&lt;/em&gt; private. It is a ratchet, not a proof: each turn raises the cost of the statistical attack, but the guarantee stays statistical and the crowd stays bounded at sixteen. Decoy realism versus improving classifiers is a permanent arms race.&lt;/p&gt;
&lt;p&gt;So the Monero row of our recurring table writes itself: what is proved is one-of-N ambiguity; the mechanism is ring signature plus stealth address plus RingCT; the break is that N collapses when a real spend becomes guessable; and it holds against a chain observer within a ring of sixteen, only as far as the decoys are realistic. Two coins, two breaks -- one a flaw in a proof, the other a collapse of a crowd. Are those different problems, or the same problem wearing two masks?&lt;/p&gt;
&lt;h2&gt;8. Head-to-Head: Cryptographic Anonymity Versus Statistical Anonymity&lt;/h2&gt;
&lt;p&gt;Put the two proofs on one table and the symmetry is startling. Here is the recurring device, now filled for both coins and both frontier systems: what each proves, by what mechanism, the single assumption whose failure is its break, and the adversary and set it holds against.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;System&lt;/th&gt;
&lt;th&gt;What is proved&lt;/th&gt;
&lt;th&gt;By what mechanism&lt;/th&gt;
&lt;th&gt;Break condition&lt;/th&gt;
&lt;th&gt;Anonymous against whom / within which set&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Zcash Sapling&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;This spend is valid, authorized, balanced, and hidden in the shielded set&lt;/td&gt;
&lt;td&gt;Groth16 zk-SNARK over BLS12-381, commitments and nullifiers&lt;/td&gt;
&lt;td&gt;An unsound proof (the BCTV14 precedent) or a trusted-setup leak; and a thin real set&lt;/td&gt;
&lt;td&gt;A chain observer, within the set that actually shielded, small in practice [@kappos-usenix2018]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Zcash Orchard&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The same statement, with no ceremony to trust&lt;/td&gt;
&lt;td&gt;Halo 2 inner-product argument (transparent) plus recursion&lt;/td&gt;
&lt;td&gt;An unsound argument or discrete-log break; the trusted-setup class is removed; the set is still thin [@ecc-halo2]&lt;/td&gt;
&lt;td&gt;A chain observer, within the Orchard set (opt-in)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Monero RingCT&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The real spend is one of sixteen ring members; amounts balance and are in range&lt;/td&gt;
&lt;td&gt;CLSAG linkable ring signature, stealth addresses, RingCT with Bulletproofs+&lt;/td&gt;
&lt;td&gt;The N collapses: guessable decoys, chain-reaction near 62% and guess-newest near 80% before mitigation; a bounded ring [@moser-popets2018]&lt;/td&gt;
&lt;td&gt;A chain observer, within a ring of sixteen, only as far as decoys are realistic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Monero FCMP++&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The real spend is one of every output on the chain&lt;/td&gt;
&lt;td&gt;Curve-Trees membership proof, Generalized Bulletproof, Generalized Schnorr&lt;/td&gt;
&lt;td&gt;An unsound Generalized Bulletproof or discrete-log break; soundness proofs still being formalized [@fcmp-ccs]&lt;/td&gt;
&lt;td&gt;A chain observer, within the entire output set, roughly 100,000,000 [@fcmp-blog]&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The engineering trade-offs behind those rows are what a protocol designer actually weighs.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dimension&lt;/th&gt;
&lt;th&gt;Zcash Sapling&lt;/th&gt;
&lt;th&gt;Zcash Orchard&lt;/th&gt;
&lt;th&gt;Monero RingCT&lt;/th&gt;
&lt;th&gt;Monero FCMP++&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Anonymity model&lt;/td&gt;
&lt;td&gt;Cryptographic proof&lt;/td&gt;
&lt;td&gt;Cryptographic proof, transparent&lt;/td&gt;
&lt;td&gt;Statistical ambiguity&lt;/td&gt;
&lt;td&gt;Cryptographic membership proof&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Trusted setup&lt;/td&gt;
&lt;td&gt;Yes, per-circuit&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Proof / tx size&lt;/td&gt;
&lt;td&gt;About 192-byte proof, about 2.8 KB tx [@zcash-protocol-spec] [@curve-trees]&lt;/td&gt;
&lt;td&gt;Logarithmic proof, larger than Groth16 [@ecc-halo2]&lt;/td&gt;
&lt;td&gt;At least 80% smaller than pre-Bulletproofs [@moneropedia-bulletproofs]&lt;/td&gt;
&lt;td&gt;About 2.9 KB proof, about 4 KB tx [@curve-trees]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Verify cost&lt;/td&gt;
&lt;td&gt;Constant, cheapest&lt;/td&gt;
&lt;td&gt;Heavier, near-constant when batched [@ecc-halo2]&lt;/td&gt;
&lt;td&gt;Grows with ring size times inputs&lt;/td&gt;
&lt;td&gt;About 80 ms single tx, about 5 ms batched over 100 [@curve-trees]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Anonymity set (nominal)&lt;/td&gt;
&lt;td&gt;All shielded notes&lt;/td&gt;
&lt;td&gt;All Orchard notes&lt;/td&gt;
&lt;td&gt;16 per input [@fcmp-blog]&lt;/td&gt;
&lt;td&gt;About 100,000,000 [@fcmp-blog]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Anonymity set (effective)&lt;/td&gt;
&lt;td&gt;Far below nominal, founder and miner dominated [@kappos-usenix2018]&lt;/td&gt;
&lt;td&gt;Same structural risk, opt-in&lt;/td&gt;
&lt;td&gt;16 or fewer, often collapsible [@moser-popets2018]&lt;/td&gt;
&lt;td&gt;Equal to nominal, the full chain&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Post-quantum&lt;/td&gt;
&lt;td&gt;No, pairings&lt;/td&gt;
&lt;td&gt;No, discrete log&lt;/td&gt;
&lt;td&gt;No, discrete log&lt;/td&gt;
&lt;td&gt;Privacy forward-secret against a DL oracle, soundness not PQ [@fcmp-blog]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Default privacy&lt;/td&gt;
&lt;td&gt;Opt-in&lt;/td&gt;
&lt;td&gt;Opt-in&lt;/td&gt;
&lt;td&gt;Always-on&lt;/td&gt;
&lt;td&gt;Always-on&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deployed?&lt;/td&gt;
&lt;td&gt;Yes (2018)&lt;/td&gt;
&lt;td&gt;Yes (2022, default)&lt;/td&gt;
&lt;td&gt;Yes (current)&lt;/td&gt;
&lt;td&gt;No (audited, in development)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Every nominal anonymity-set number in these tables has a smaller twin. Zcash&apos;s pool is nominally every shielded note but effectively a much smaller set once the founder and miner round-trips are removed [@kappos-usenix2018]; Monero&apos;s ring is nominally sixteen but was effectively closer to one for early transactions [@moser-popets2018]. The adversary never faces the number on the box. They face the number that survives their heuristics, and closing that gap is the real work.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Now read the four rows against the thesis and the deep structure appears. The two Zcash rows and the FCMP++ row all say the same thing -- &lt;em&gt;a proof is exactly as strong as its soundness and the set it ranges over&lt;/em&gt;. The Monero RingCT row is the ambiguity variant -- &lt;em&gt;a crowd is exactly as strong as it is indistinguishable&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;And a startling diagonal runs through the table. As Monero moves from RingCT down to FCMP++, it stops being statistical and &lt;em&gt;becomes a proof&lt;/em&gt;: the anonymity set jumps from sixteen to the whole chain, but the break changes species, from &quot;a guessable decoy&quot; to &quot;an unsound Generalized Bulletproof.&quot; Monero, chasing a bigger crowd, walks straight into the proof school&apos;s break condition. Meanwhile Zcash, moving from Sapling to Orchard, keeps the proof but discards the trusted-setup break entirely.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; The two schools are converging on the same ideal from opposite corners. Monero is adding a proof to escape a collapsible crowd; Zcash is removing a trusted setup to shed a soundness liability. The shared destination is a transparent, sound, full-set membership proof -- and the moment Monero reaches it, it inherits exactly the break condition Zcash spent a decade learning to fear. Name the proof, and you have named the break.&lt;/p&gt;
&lt;/blockquote&gt;

flowchart TD
    S[&quot;Zcash Sapling: a sound proof, but a trusted setup&quot;]
    O[&quot;Zcash Orchard: proof kept, trusted setup removed&quot;]
    R[&quot;Monero RingCT: statistical ambiguity over a ring of 16&quot;]
    F[&quot;Monero FCMP++: a membership proof over the whole chain&quot;]
    Ideal[&quot;Transparent, sound, full-set membership proof&quot;]
    S --&amp;gt; O
    O --&amp;gt; Ideal
    R --&amp;gt; F
    F --&amp;gt; Ideal
&lt;p&gt;If both roads lead to the same ideal proof, a natural question follows: is there a ceiling neither road can pass? There is, and it is a theorem rather than an engineering wart.&lt;/p&gt;
&lt;h2&gt;9. The Ceiling and the Floor: What No Construction Can Escape&lt;/h2&gt;
&lt;p&gt;Suppose the proof is perfect and the trusted setup is gone. Are you finally, unconditionally anonymous? No -- and the reasons are theorems, not engineering warts. Three limits sit under every system in this article, and naming them turns the whole comparison from a horse race into a map.&lt;/p&gt;
&lt;p&gt;The first is a ceiling. You cannot be more anonymous than the crowd your proof ranges over. If the set an argument covers has size $k$, then an adversary with no other information already guesses the real spender with probability at least $1/k$, and every usable heuristic only pushes that up. This $k$-anonymity ceiling is why FCMP++ is a genuine theoretical advance rather than an optimization: raising the ring from sixteen to the whole chain does not tune a constant, it raises the ceiling itself, from $k \approx 16$ to $k \approx 10^{8}$ [@fcmp-blog] [@curve-trees].&lt;/p&gt;
&lt;p&gt;It is also why the empirical papers matter so much. Moser&apos;s and Kappos&apos;s results are precisely measurements of the gap between the nominal ceiling and the effective set the adversary actually faces [@moser-popets2018] [@kappos-usenix2018]. The ceiling is set by design; the distance you fall below it is set by details.&lt;/p&gt;
&lt;p&gt;The second is a floor. The succinct systems here are not &quot;proofs&quot; in the strongest sense at all; they are &lt;em&gt;arguments&lt;/em&gt;.&lt;/p&gt;

A *proof* is sound even against an adversary with unlimited computing power: a false statement simply cannot be proved. An *argument* is only *computationally* sound: forging a proof of a false statement is possible in principle but requires breaking a hardness assumption, so no feasible attacker can do it. Every succinct system deployed by Zcash or Monero is an argument. Its soundness rests on a problem being hard -- pairings and knowledge-of-exponent for Groth16, discrete log for inner-product arguments, Bulletproofs, ring signatures, and Curve Trees.
&lt;p&gt;That floor is load-bearing, and BCTV14 is the proof of it. The argument was accepted mathematics, and it was unsound, and the only thing standing between &quot;unsound argument&quot; and &quot;silent counterfeiting&quot; was the aggregate turnstile [@ecc-postmortem]. An argument is a promise conditional on an assumption, and a conditional promise can fail two ways: the assumption is false, or the reduction to it is buggy.&lt;/p&gt;
&lt;p&gt;The third limit is the one place the field genuinely closed a &lt;em&gt;practical&lt;/em&gt; gap -- and it is the single most mis-stated fact in popular writing about these systems, so state it exactly. You will often read that pairing-based SNARKs &quot;provably require&quot; a structured reference string while transparent systems use &quot;no setup at all.&quot; Both halves are wrong, and the correct statement is older and more fundamental.&lt;/p&gt;
&lt;p&gt;Non-interactive zero-knowledge was &lt;em&gt;defined&lt;/em&gt; by Blum, Feldman, and Micali in 1988 relative to a shared common reference string -- setup was baked into the model from the start [@blum-feldman-micali-1988]. And Goldreich and Oren proved in 1994 that &lt;em&gt;non-interactive&lt;/em&gt; zero-knowledge in the plain model, with no shared setup at all, exists only for trivial (BPP) languages -- so a non-interactive zero-knowledge argument for any &lt;em&gt;non-trivial&lt;/em&gt; language must have &lt;em&gt;some&lt;/em&gt; common setup: a common reference string or a random oracle [@goldreich-oren-1994]. Every system here therefore has a setup. The only question is whether you must &lt;em&gt;trust&lt;/em&gt; it.&lt;/p&gt;
&lt;p&gt;A pairing SNARK like Groth16 uses a &lt;em&gt;structured&lt;/em&gt; reference string whose secret randomness is toxic waste, which is why it needs a ceremony. Transparent arguments -- Halo 2&apos;s inner-product argument, Bulletproofs, Curve Trees -- use a &lt;em&gt;public, untrusted&lt;/em&gt; setup: nothing-up-my-sleeve generators and public hash functions that hide no secret and need no ceremony. The advance is not &quot;no setup&quot;; it is &quot;setup nobody has to trust,&quot; and it now comes at transaction sizes within a small constant of trusted-setup SNARKs -- roughly 4 KB (with a 2.9 KB membership proof) against Sapling&apos;s 2.8 KB transaction [@ecc-halo2] [@curve-trees] [@stanford-bulletproofs]. The toxic-waste break class is not mitigated here; it is removed.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The correct impossibility is not &quot;pairing SNARKs need a trusted setup and transparent ones need none.&quot; It is that non-interactive zero-knowledge for any non-trivial language needs &lt;em&gt;some&lt;/em&gt; common setup at all -- a common reference string or a random oracle (Blum-Feldman-Micali 1988; Goldreich-Oren 1994) [@blum-feldman-micali-1988] [@goldreich-oren-1994]. Trusted-setup and transparent systems differ only in whether that setup must be &lt;em&gt;trusted&lt;/em&gt;: a structured reference string hides toxic waste, a transparent public setup hides nothing. Say &quot;no &lt;em&gt;trusted&lt;/em&gt; setup,&quot; never &quot;no setup.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;One more subtlety belongs here, because it is routinely conflated with the setup question. SNARK &lt;em&gt;succinctness itself&lt;/em&gt; cannot be proved secure from standard falsifiable assumptions by black-box reductions -- Gentry and Wichs showed any such argument must instead rest on non-falsifiable &quot;knowledge&quot; assumptions or an idealized model like the random oracle [@gentry-wichs-2011]. That is a statement about the &lt;em&gt;assumptions&lt;/em&gt; a short proof needs, not about whether its reference string is structured.&lt;/p&gt;
&lt;p&gt;Put the three together and the honest reading is bracing: a succinct argument leans on a strong assumption to exist at all, on a hardness assumption to be sound, and on some public setup to be non-interactive. Removing the &lt;em&gt;trusted&lt;/em&gt; part of the setup, as Halo 2 and Curve Trees do, is a real and hard-won advance -- but it removes exactly one of those three dependencies, not all of them.&lt;/p&gt;

It is tempting to file every privacy technology under &quot;zk-SNARK,&quot; but the family is broader than that. [Direct Anonymous Attestation](/blog/direct-anonymous-attestation-the-zero-knowledge-proof-alread/) -- the scheme that lets a hardware security chip prove it is genuine without becoming a tracking beacon, covered elsewhere in this corpus -- is a zero-knowledge *proof of knowledge*, but it is an *interactive sigma-protocol* in the Schnorr and Camenisch-Lysyanskaya tradition, not a succinct non-interactive argument [@daa-ccs-2004]. Same broad idea of proving a secret without revealing it; entirely different construction, assumptions, and break conditions. When someone says &quot;it uses zero-knowledge proofs,&quot; always ask which kind.

Every deployed system here rests on discrete log or pairings -- both fall to a large quantum computer, and soundness and privacy fail on different clocks. A future quantum adversary could forge proofs -- but a *store-now, decrypt-later* adversary threatens today&apos;s *privacy* the moment it records the chain, because the hiding also rests on discrete log. The same logic drives migration to lattice schemes (ML-KEM, ML-DSA) and hash-based signatures (SLH-DSA) [@nist-pqc-2024], examined in the corpus&apos;s &quot;how X would break&quot; post-quantum entries. FCMP++&apos;s &quot;forward secrecy&quot; targets exactly this: it protects privacy against a future discrete-log oracle, though its soundness is not post-quantum [@fcmp-blog].
&lt;p&gt;Every one of these limits is the thesis restated at the level of theory: the ceiling is the set your proof ranges over, the floor is the soundness your argument rests on, and even the one problem the field solved was solved only in part -- the setup stayed, but the &lt;em&gt;trust&lt;/em&gt; in it went away. So if the ceiling is the set and the floor is soundness, where do today&apos;s systems still leak?&lt;/p&gt;
&lt;h2&gt;10. Where the Proofs Still Leak&lt;/h2&gt;
&lt;p&gt;Every unsolved break condition in this field is just a proof not yet strong enough, or a leak the proof was never meant to cover. Five stand out.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Decoy realism (Monero).&lt;/strong&gt; Choosing ring decoys so that no statistical test -- age, chain-reaction, timing -- beats $1/N$ is the entire security of the ambiguity model, and it is never provably perfect. Mandatory RingCT, zero-decoy elimination, larger rings, and gamma-distributed selection blunted the Moser attacks but did not end the arms race [@moser-popets2018]. The clean resolution is to stop selecting decoys at all, which is exactly what a full-chain membership proof does [@fcmp-blog].&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Shielded-pool adoption (Zcash).&lt;/strong&gt; Making the &lt;em&gt;effective&lt;/em&gt; set approach the &lt;em&gt;nominal&lt;/em&gt; pool is an incentive and user-experience problem, not a cryptographic one. A perfectly sound proof over a thin, founder-dominated pool yields little real anonymity [@kappos-usenix2018]. Cheaper shielding and better wallets help, but as long as transparent addresses exist and shielding is opt-in, the pool&apos;s composition stays a live analysis target.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The network layer.&lt;/strong&gt; No on-chain proof hides &lt;em&gt;who broadcast&lt;/em&gt; a transaction. IP and timing metadata sit entirely outside both constructions, and they defeat privacy at the peer-to-peer layer for Zcash and Monero alike. Dandelion++ raises the cost of first-relayer deanonymization, but remote-node and light-wallet usage still leaks [@dandelionpp-2018].&lt;/p&gt;
&lt;p&gt;Dandelion++ is a network-layer defense, not a cryptographic one: it splits transaction propagation into a randomized &quot;stem&quot; phase before the public &quot;fluff&quot; broadcast, so the first node to see a transaction cannot easily be assumed to be its origin [@dandelionpp-2018]. It is a genuine improvement, but it protects a surface -- who &lt;em&gt;broadcast&lt;/em&gt; -- that ring signatures and zk-SNARKs never claimed to cover, which is exactly why it belongs on a separate layer of the map below.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Formal verification of the deployed circuit.&lt;/strong&gt; BCTV14 was a flaw in the construction &lt;em&gt;as described&lt;/em&gt;, invisible to expert review for years, so auditing the abstract scheme is not enough -- you must audit the deployed circuit. The 2024 Cypher Stack review of CLSAG found two gaps in its security &lt;em&gt;proof&lt;/em&gt; with no impact on the Monero implementation, a reassuring near-miss [@clsag]. Less reassuring: FCMP++&apos;s Generalized Bulletproof soundness proofs were still being written at proposal time, and that risk runs straight back to Gabizon&apos;s redundant-CRS-element discovery [@fcmp-ccs] [@gabizon-2019-119]. The gap between &quot;implemented&quot; and &quot;proven sound&quot; is the BCTV14 gap, reopened for the successor.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Post-quantum private payments.&lt;/strong&gt; No deployed private coin is post-quantum &lt;em&gt;sound&lt;/em&gt; at competitive cost, and a store-now-decrypt-later adversary threatens &lt;em&gt;privacy&lt;/em&gt; today. FCMP++&apos;s forward secrecy protects privacy against a future discrete-log oracle but leaves soundness classical [@fcmp-blog]. This is the same pressure that the corpus&apos;s &quot;how X would break&quot; post-quantum entries trace across &lt;a href=&quot;https://paragmali.com/blog/how-ml-kem-breaks-in-real-life-the-machine-leaked-while-the-/&quot; rel=&quot;noopener&quot;&gt;ML-KEM&lt;/a&gt;, &lt;a href=&quot;https://paragmali.com/blog/how-falcon-would-break-ntru-lattices-and-the-structure-nobod/&quot; rel=&quot;noopener&quot;&gt;Falcon&lt;/a&gt;, and &lt;a href=&quot;https://paragmali.com/blog/how-slh-dsa-would-break-the-signature-that-can-only-fall-if-/&quot; rel=&quot;noopener&quot;&gt;SLH-DSA&lt;/a&gt;, and it is the most pressing open problem here.&lt;/p&gt;

flowchart TD
    OnChain[&quot;On-chain proof: is the argument sound?&quot;]
    Set[&quot;Anonymity set: is it large and real?&quot;]
    Net[&quot;Network layer: who broadcast the transaction?&quot;]
    Circuit[&quot;Circuit: is the deployed code actually sound?&quot;]
    PQ[&quot;Post-quantum: does a quantum solver break it?&quot;]
    Legal[&quot;Legal: are you allowed to trade it at all?&quot;]
    Q{&quot;Anonymous against whom?&quot;}
    OnChain --&amp;gt; Q
    Set --&amp;gt; Q
    Net --&amp;gt; Q
    Circuit --&amp;gt; Q
    PQ --&amp;gt; Q
    Legal --&amp;gt; Q
&lt;p&gt;The frontier tying several of these together is FCMP++, and it deserves an honest description because the hype around it is wrong in a specific way. It would replace the ring of sixteen with a zero-knowledge membership proof over every output on the chain, taking each input&apos;s anonymity set from sixteen to roughly 100,000,000, with no trusted setup, on a transparent Curve-Trees accumulator [@fcmp-blog] [@curve-trees] -- with a reference implementation taking shape in the monero-oxide Rust codebase [@monero-oxide]. In doing so it converts the ambiguity school into the proof school and inherits its break condition: the soundness of a Generalized Bulletproof [@fcmp-ccs].&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Watch the exact swap. Today&apos;s Monero has a small, sometimes-collapsible anonymity set but no proof-soundness assumption to break, because it makes no cryptographic claim about which output was spent. FCMP++ would give it the whole chain -- closing the &lt;em&gt;set&lt;/em&gt; gap for good -- but only by adding a Generalized Bulletproof whose soundness must then hold, reopening a &lt;em&gt;soundness&lt;/em&gt; gap of exactly the kind that bit BCTV14. That is not a criticism; it is the thesis in action. You do not get anonymity for free; you choose which break condition you would rather carry [@fcmp-ccs] [@fcmp-blog].&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; As of 19 July 2026, FCMP++ is specified, funded, and under audit -- but not deployed. The current Monero release is 0.18.5.1 &quot;Fluorine Fermi,&quot; and consensus is still CLSAG ring signatures with a ring of sixteen; there is no FCMP hard fork [@monero-release-blog]. Any claim that Monero already has full-chain anonymity in production is false. Today&apos;s Monero user has a ring of sixteen, not the whole chain [@fcmp-ccs].&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;One of the quiet pleasures of this history is a revival. The inner-product argument &quot;famously used in Bulletproofs&quot; -- the ambiguity school&apos;s 2018 tool for shrinking Monero&apos;s range proofs -- is the same primitive Zcash built Halo 2 on in 2022 to eliminate trusted setup. The efficiency trick of one school became the other school&apos;s route to a cleaner proof [@ecc-halo2] [@bulletproofs-sp2018].&lt;/p&gt;
&lt;p&gt;Even a perfect proof over the entire chain, though, runs into one break condition that no cryptography can touch -- and by 2026 it is the sharpest of them all.&lt;/p&gt;
&lt;h2&gt;11. The Anonymity You Are Allowed to Prove&lt;/h2&gt;
&lt;p&gt;After a decade of tightening the cryptography, the sharpest live break condition for both coins is not a soundness flaw or a collapsible ring. It is a statute. Through 2024, 2025, and 2026, major exchanges operating in the European Union -- Binance, Kraken, OKX, and others -- delisted or restricted Monero, Zcash, and similar assets [@binance-delisting]. The delistings are real and current. What matters for anyone reasoning clearly is attributing them to the right instruments, because the popular shorthand &quot;MiCA banned privacy coins&quot; is wrong on every word.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Instrument&lt;/th&gt;
&lt;th&gt;What it actually does&lt;/th&gt;
&lt;th&gt;Key date&lt;/th&gt;
&lt;th&gt;Effect on privacy coins&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;MiCA, Regulation (EU) 2023/1114&lt;/td&gt;
&lt;td&gt;A market-conduct, transparency, and licensing regime for crypto-asset service providers&lt;/td&gt;
&lt;td&gt;CASP provisions apply from 30 December 2024&lt;/td&gt;
&lt;td&gt;Compliance pressure and cost; contains no privacy-coin ban [@mica-2023-1114]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AMLR Article 79, Regulation (EU) 2024/1624&lt;/td&gt;
&lt;td&gt;Prohibits obliged entities from keeping anonymous crypto-asset accounts and anonymity-enhancing coins&lt;/td&gt;
&lt;td&gt;Applies from 10 July 2027&lt;/td&gt;
&lt;td&gt;The actual prohibition, binding service providers, not individuals [@amlr-2024-1624]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FATF Recommendation 16 and EU TFR 2023/1113&lt;/td&gt;
&lt;td&gt;Requires originator and beneficiary information to travel with each transfer&lt;/td&gt;
&lt;td&gt;In force in the EU&lt;/td&gt;
&lt;td&gt;The global driver that makes traceless coins hard to service [@tfr-2023-1113] [@fatf-va]&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;

MiCA (Regulation (EU) 2023/1114) is a licensing and market-conduct framework. It raises the compliance cost of listing a hard-to-trace asset, and exchanges cite it when they delist, but it contains no prohibition on privacy coins [@mica-2023-1114]. The dated prohibition lives in the Anti-Money-Laundering Regulation: Article 79(1) of Regulation (EU) 2024/1624 bars credit institutions, financial institutions, and crypto-asset service providers from keeping &quot;anonymous ... crypto-asset accounts as well as any account otherwise allowing for the anonymisation ... or increased obfuscation of transactions, including through anonymity-enhancing coins,&quot; applying from 10 July 2027 [@amlr-2024-1624].&lt;p&gt;The driver underneath both is FATF Recommendation 16, the Travel Rule, implemented in the EU by TFR 2023/1113, which requires identifying information to accompany transfers -- the one thing a traceless coin structurally cannot provide [@tfr-2023-1113] [@fatf-va]. Say &quot;AMLR Article 79,&quot; not &quot;MiCA,&quot; when you mean the ban.
&lt;/p&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Zcash and Monero are the canonical constructions, but the same regime sweeps up their relatives, each a variant of the two schools here. Dash offers non-custodial CoinJoin mixing through masternodes -- ambiguity by shuffling, closer to the 2013 pre-history than to either coin [@dash-coinjoin]. Firo&apos;s Lelantus Spark is a no-trusted-setup membership-proof scheme, a descendant of the very Zerocoin that was later broken, rebuilt as a proof [@lelantus-spark]. Beldex is a Monero-lineage CryptoNote/RingCT coin [@beldex]; Pirate Chain uses always-shielded Zcash-Sapling zk-SNARKs [@pirate-chain]. Every one inherits a construction from this article and, from 10 July 2027, the same legal break [@amlr-2024-1624].&lt;/p&gt;
&lt;p&gt;The thesis reaches its final form here. A &lt;em&gt;provable&lt;/em&gt; anonymity and a &lt;em&gt;permitted&lt;/em&gt; anonymity are different properties, and the second is not something cryptography can supply. No cryptographic advance postpones the 10 July 2027 deadline [@amlr-2024-1624]. There is a genuine irony in the one technical escape hatch: both systems already support &lt;em&gt;selective disclosure&lt;/em&gt; -- Zcash and Monero view keys -- that let a holder voluntarily reveal a transaction to an auditor or regulator [@zcash-viewkeys] [@monero-viewkey].&lt;/p&gt;
&lt;p&gt;But that is precisely the disclosure these constructions spent a decade proving you need not make. Using it to satisfy a compliance adversary is not a break in the proof; it is a decision to stop relying on the proof. A fittingly honest place to land before turning the whole argument into something you can use.&lt;/p&gt;
&lt;p&gt;The delisting notices themselves are anchored here to the MiCA and AMLR primary texts rather than to individual exchange announcements, several of which are bot-gated or geo-restricted and could not be independently confirmed. The legal drivers are the load-bearing claim; any single exchange notice is illustrative [@mica-2023-1114] [@amlr-2024-1624] [@binance-delisting].&lt;/p&gt;
&lt;p&gt;So how should an engineer actually reason about any of this on a Monday morning?&lt;/p&gt;
&lt;h2&gt;12. Reasoning About On-Chain Anonymity&lt;/h2&gt;
&lt;p&gt;One question collapses almost all of the marketing around privacy coins: &lt;em&gt;anonymous against whom, within which set, under which proof?&lt;/em&gt; If a claim cannot answer those, it is not an engineering statement. The whole article reduces to a four-line checklist you can run against any construction, deployed or promised.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Ask&lt;/th&gt;
&lt;th&gt;What you are establishing&lt;/th&gt;
&lt;th&gt;Zcash answer&lt;/th&gt;
&lt;th&gt;Monero answer&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;What is proved?&lt;/td&gt;
&lt;td&gt;A cryptographic soundness proof, or a statistical claim of ambiguity&lt;/td&gt;
&lt;td&gt;A zk-SNARK: the spend is valid and hidden&lt;/td&gt;
&lt;td&gt;Ambiguity: the spend is one of N&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Over which set?&lt;/td&gt;
&lt;td&gt;The nominal set size, and the effective size after heuristics&lt;/td&gt;
&lt;td&gt;All shielded notes, but thin in practice [@kappos-usenix2018]&lt;/td&gt;
&lt;td&gt;A ring of 16, effective and often smaller [@moser-popets2018]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Against which adversary?&lt;/td&gt;
&lt;td&gt;Casual observer, chain-analysis firm, network watcher, or a compliance authority&lt;/td&gt;
&lt;td&gt;Sound vs a chain analyst if you shield; opt-in&lt;/td&gt;
&lt;td&gt;Always-on vs a chain analyst; realism-bounded&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What is the break condition?&lt;/td&gt;
&lt;td&gt;The single assumption whose failure collapses the guarantee&lt;/td&gt;
&lt;td&gt;Unsound proof, or a set too small&lt;/td&gt;
&lt;td&gt;A guessable real spend&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Run that checklist and the design choices sort themselves:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Want the smallest proof and cheapest verification, and can accept a trusted-setup lineage plus opt-in privacy? &lt;strong&gt;Zcash Sapling / Groth16.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Want strong hiding with no ceremony to trust, plus recursion for scale, still opt-in? &lt;strong&gt;Zcash Orchard / Halo 2&lt;/strong&gt; -- today&apos;s recommended pool [@ecc-halo2].&lt;/li&gt;
&lt;li&gt;Want always-on, no-setup, uniform privacy now, and can accept a bounded ring of sixteen with residual decoy risk? &lt;strong&gt;Monero RingCT / CLSAG / Bulletproofs+&lt;/strong&gt; -- deployed.&lt;/li&gt;
&lt;li&gt;Want a full-chain, transparent membership proof, the honest endgame of the ambiguity school? &lt;strong&gt;Monero FCMP++&lt;/strong&gt; -- once it is deployed and its Generalized Bulletproof soundness is proven, which is not today [@fcmp-ccs] [@curve-trees].&lt;/li&gt;
&lt;li&gt;Facing a compliance or legal adversary (an exchange or custodian)? None is &quot;permitted&quot;; the break is regulatory, not cryptographic -- AMLR Article 79 and the FATF Travel Rule, not a soundness bug [@amlr-2024-1624].&lt;/li&gt;
&lt;/ul&gt;

The single most common Zcash misconception is &quot;I hold ZEC, so I am private.&quot; Privacy in Zcash is a property of *transactions that entered the shielded pool*, not of the asset. To check, trace each receiving address on a block explorer: an address beginning with `t` is transparent, and every amount and counterparty is public; a `z` (Sapling) or unified address that received a *shielded* output is the only value the zk-SNARK actually hides.&lt;p&gt;If your coins arrived at a transparent address and never made a shielded (z-to-z) hop, the proof never ranged over them -- you have the pool&apos;s cryptography and none of its anonymity. The same logic is why Kappos et al. could measure the pool from outside: the transparent-to-shielded boundary is itself public [@kappos-usenix2018].
&lt;/p&gt;&lt;p&gt;&lt;/p&gt;


No. Zcash is transparent by default, and the shielded proof only ranges over value that has actually entered the shielded pool -- which most ZEC never does. Measurements found only about 15% of transactions ever touched the pool, and simple heuristics cut the effective anonymity set substantially [@kappos-usenix2018]. Privacy in Zcash is real but opt-in, and it is only as large as the set you actually join.


It is statistically ambiguous, not unconditionally untraceable. Early decoy selection made the real spend guessable -- roughly 62% of inputs with a mixin were deducible by chain-reaction, and the newest ring member was the real one about 80% of the time [@moser-popets2018]. Mandatory RingCT, larger rings, and realistic decoy sampling mitigated these attacks, but the guarantee remains statistical and the ring is bounded at sixteen.


No. MiCA (Regulation (EU) 2023/1114) is a market-conduct and licensing framework with no privacy-coin prohibition; it is pressure, not a ban [@mica-2023-1114]. The actual prohibition is AMLR Article 79 (Regulation (EU) 2024/1624), which bars service providers from keeping anonymity-enhancing coins and applies from 10 July 2027 [@amlr-2024-1624].


No. The trusted-setup secret (&quot;toxic waste&quot;) governs the proof system&apos;s soundness, not its privacy. A leaked setup would let an attacker counterfeit value, which is serious, but it is not a key that deanonymizes anyone&apos;s transactions. Transparent systems like Halo 2 and Bulletproofs remove the *trusted* part of the setup entirely [@ecc-halo2].


There is no evidence it was. In-pool counterfeiting would have been cryptographically undetectable, but the turnstile that meters net value crossing the pool boundary showed no aggregate anomaly, and Sapling superseded the flawed circuit before the vulnerability was disclosed on 5 February 2019 [@ecc-postmortem].


Only against the right adversary. A larger, uniform ring blunts size-fingerprinting and the guess-newest heuristic, but the guarantee stays statistical and bounded, and the realism of the decoys matters more than the raw number. Sixteen realistic decoys beat sixty-four obvious ones [@moser-popets2018].


Not as of 2026. The current release is 0.18.5.1 &quot;Fluorine Fermi,&quot; with ring signatures still in consensus [@monero-release-blog]. FCMP++ would raise each input&apos;s anonymity set to roughly 100,000,000, but it trades Monero&apos;s statistical assumption for the soundness of Generalized Bulletproofs, whose proofs were still being formalized at proposal time [@fcmp-ccs] [@fcmp-blog].

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; The Monday-morning version of the whole argument: before you trust any on-chain anonymity, name the proof. If it is a cryptographic proof, its break is unsound math or a set too small to hide in. If it is statistical ambiguity, its break is a guessable real spend. Then name the adversary, and check whether the effective set -- not the advertised one -- still hides you against that adversary. Name the proof, and you have named the break.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Two coins, two constructions, two break conditions that turned out to be the same statistical shape underneath -- and, waiting past every proof, a legal wall no cryptography can climb. The lesson is not that one coin is more private than the other. It is that &quot;private&quot; was never a single property. It is a proof with an assumption attached, and the assumption is always nameable. That is the discipline worth keeping: not &quot;is it anonymous?&quot; but &quot;what does it prove, and what breaks when that proof&apos;s one assumption fails?&quot;&lt;/p&gt;

Name the proof, and you have named the break.
&lt;p&gt;&amp;lt;StudyGuide slug=&quot;the-anonymity-that-actually-holds-zk-snarks-in-zcash-vs-ring&quot; keyTerms={[
  { term: &quot;Anonymity set&quot;, definition: &quot;The set of candidates an adversary cannot distinguish among; success probability is at best 1 over its effective size.&quot; },
  { term: &quot;zk-SNARK&quot;, definition: &quot;A succinct, non-interactive zero-knowledge argument that a statement is true, revealing nothing else.&quot; },
  { term: &quot;Note commitment&quot;, definition: &quot;A hiding, binding commitment to a shielded coin, appended to an append-only tree.&quot; },
  { term: &quot;Nullifier&quot;, definition: &quot;A deterministic per-note tag that voids a note to prevent double-spends without revealing which note.&quot; },
  { term: &quot;Trusted setup&quot;, definition: &quot;Public parameters generated from secret toxic waste; if the waste survives it enables counterfeiting, not deanonymization.&quot; },
  { term: &quot;Soundness&quot;, definition: &quot;The promise that a verified proof means a true statement; broken by BCTV14 in 2018.&quot; },
  { term: &quot;Ring signature&quot;, definition: &quot;A signature proving some member of a signer-chosen set authorized a message, without revealing which.&quot; },
  { term: &quot;Key image&quot;, definition: &quot;A unique tag of the real spending key that catches Monero double-spends without naming the output.&quot; },
  { term: &quot;Stealth address&quot;, definition: &quot;A one-time destination key derived per payment so payments to one person stay unlinkable.&quot; },
  { term: &quot;Pedersen commitment&quot;, definition: &quot;An additively homomorphic commitment C = vG + rH that hides an amount while letting a verifier check balance.&quot; }
]} questions={[
  { q: &quot;Why is a Zcash soundness break a counterfeiting risk rather than a privacy risk?&quot;, a: &quot;Soundness governs whether false statements can be proved; a broken proof lets an attacker mint unbalanced value, while hiding still comes from commitments and zero knowledge.&quot; },
  { q: &quot;What single assumption is Monero&apos;s ambiguity resting on?&quot;, a: &quot;That the real spend is statistically indistinguishable from its decoys; guessable decoys collapse the set.&quot; },
  { q: &quot;Why does FCMP++ inherit the proof school&apos;s break condition?&quot;, a: &quot;It replaces the ring with a full-chain membership proof, so its security now depends on the soundness of a Generalized Bulletproof rather than on decoy realism.&quot; }
]} /&amp;gt;&lt;/p&gt;
</content:encoded><category>zcash</category><category>monero</category><category>zk-snarks</category><category>ring-signatures</category><category>privacy-coins</category><category>zero-knowledge-proofs</category><category>on-chain-anonymity</category><category>cryptography</category><author>noreply@paragmali.com (Parag Mali)</author></item></channel></rss>