2026-10-14
This monthFiat–Shamir done wrong: an auditor's map of transcript and challenge-derivation bugs
Produced by the ZK Security research pipeline. Published only after the automated quality gate. The gate is described in the editorial policy.
The Fiat–Shamir transformation replaces a verifier's random challenges with hashes of the transcript so far. Every value the prover can influence must be hashed in before any challenge that depends on…
Fiat–Shamir done wrong: an auditor's map of transcript and challenge-derivation bugs
The Fiat–Shamir transformation replaces a verifier's random challenges with hashes of the transcript so far. Every value the prover can influence must be hashed in before any challenge that depends on it is derived. If one is missing, the prover can fix the challenge first and solve for that value afterwards. Trail of Bits named this the "Frozen Heart" class in April 2022. Published advisories since then show the same failure turning up at several distinct layers of a proof system. This guide is organised by those layers, because an audit has to check each one separately.
Layer 1: bind the statement (weak vs. strong Fiat–Shamir)
Bernhard, Pereira and Warinschi (cited by ZKDocs as BPW12) separated two variants of the transform. The strong variant hashes the commitment and the statement; the weak variant hashes only the commitment. In settings where "malicious provers can select their statements adaptively", the weak variant "yields unsound/unextractable proofs" (eprint 2016/771). The paper showed the consequences in the Helios voting system, including tampering with election results under plausible circumstances.
Trail of Bits' ZKDocs gives the concrete Schnorr version. If the challenge is computed as H(g, q, u) without the public key h, a prover can pick u and z, compute c, and then solve for a public key h that makes g^z = u·h^c hold. The result is a forged proof of knowledge for a key nobody knows the secret for (ZKDocs). The same page adds a rule often skipped in application code: where parties have identities, include the prover's and verifier's IDs in the hash to prevent replay.
Bulletproofs. The original paper derived y = Hash(A,S), z = Hash(A,S,y) and x from later commitments. None of the statement's public values were included, "most importantly, V", the Pedersen commitment to the value being range-proved (Trail of Bits, 2022-04-15). The post shows how to forge a range proof for a value outside the range. The prover replaces t1, t2 with random values and then computes a matching V after x, y and z are known. The paper-level flaw is recorded as CVE-2022-29566 (GHSA-hx53-8mp7-3h3w). Trail of Bits listed ING Bank's zkrp, SECBIT Labs' ckb-zkp and Adjoint's bulletproofs as affected.
Layer 2: bind the public inputs into every round's challenge
PlonK has five prover rounds, each producing a challenge. In the variant affecting Dusk Network's plonk, Iden3's SnarkJS and ConsenSys' gnark, the public inputs were left out of every challenge, including the evaluation challenge zeta (Trail of Bits, 2022-04-18). The forgery uses random wire polynomials and a zeroed permutation polynomial. Once zeta is known, the attacker solves the verifier's step-8 equation for PI(zeta) and picks public inputs to match. The post states this is an implementation bug, "not a vulnerability in the PlonK paper itself." It notes the paper's "transcript" includes the preprocessed input, the public input and all proof values.
The coordinated disclosure went to maintainers on March 18, 2022. By April 12, ZenGo, Dusk Network, Iden3 and ConsenSys had patched, ING Bank had deleted its repository, and the Bulletproofs authors had revised their Fiat–Shamir section (Trail of Bits, part 1).
Layer 3: verifier-side randomness is a challenge too
Verifiers that batch pairing or opening checks draw a random combiner. That value needs the same treatment as a protocol challenge. gnark's PlonK Solidity verifier computed the multi-point batching scalar as keccak256(state, 0x20), hashing only the first 32 bytes of its state. This allowed "degrees of freedom in the transcript", and a third party could derive a new valid proof from an existing one for the same public inputs (GHSA-7p92-x423-vwj6, critical, published 2023-10-17, fixed in gnark 0.9.1). The fix hashes the folded digests, the opening proofs, the grand-product commitment, zeta and the KZG gamma together.
SP1 had the STARK equivalent. Before v4.0.0, the individual polynomial evaluation claims were "not observed into the challenger" before the random-linear-combination coefficient was sampled. The fix landed inside Plonky3, the dependency SP1 relied on (GHSA-c873-wfhp-wx5m, high, published 2025-01-15). An earlier SP1 advisory covers an omitted value in the main argument: before v3.0.0, the permutation argument's cumulative sum was not observed before sampling zeta. The advisory is rated low and calls exploitation practically infeasible; Succinct deprecated all earlier versions (GHSA-8m24-3cfx-9fjw).
A related case is gnark's Groth16 commitment extension (CVE-2024-45039, fixed in 0.11.0). The folding challenge r was derived from the commitments, but every proof of knowledge shared one σ. A prover could therefore fix all but one commitment before choosing the witness, and know the in-circuit challenge in advance. The advisory calls this a critical soundness problem for most uses of such challenges (GHSA-q3hw-3gm4-w5cr).
Layer 4: the encoding must be injective
Absorbing every value is not enough if the byte or field encoding lets two different transcripts collapse to the same sponge state. The Plonky3 advisory for MultiField32Challenger (CVE-2026-46654, published 2026-05-21) states the invariant directly: absorption and squeezing must be injective, and all bits of each absorbed element must influence the state (GHSA-vj64-rjf3-w3v7). It describes three breaks:
- Absorb. With no length marker, observing
[x]absorbs the same as[x, 0, …, 0]. - Squeeze. A mod-p reduction lets distinct values produce identical challenges.
- Truncation. For BN254 digests, a floor division keeps only 192 of 254 bits, silently discarding the top 62.
The fix is in p3-challenger 0.4.3 and 0.5.3.
Design that makes omissions harder
The halo2 book explains why halo2 has no Proof struct: it is "easy to accidentally put stuff into a Proof structure that isn't also placed in the transcript." Instead, proofs are opaque byte streams, and TranscriptWrite/TranscriptRead write and hash each element in the same call (halo2 book). For reviewers, this means that in halo2-style code any element read outside the transcript API deserves scrutiny.
Audit procedure
- Write the transcript from the paper. List every public parameter, statement element (public inputs, verifying key or circuit digest, commitments like
V), round message and challenge, in order. ZKDocs' rule: include "all public information and all transcript values." - Diff the code against that list, on both the prover and the verifier side, including Solidity and recursive verifiers.
- Treat verifier-side batching scalars as challenges. Check that each one hashes everything it combines.
- Check the encoding. Look for length prefixes or domain separation, full-width absorption of field elements and digests, and challenge reduction that does not collapse distinct inputs.
- Check composed and committed values. Where a challenge is derived from prover commitments, ask whether the prover can choose those commitments before the witness.
- Write the forgery test. For each challenge, try to fix it first and then solve for the omitted value, as in the Bulletproofs and PlonK walk-throughs. A forgery that succeeds means the challenge derivation is unsound.
Sources
- https://eprint.iacr.org/2016/771
- https://blog.trailofbits.com/2022/04/13/part-1-coordinated-disclosure-of-vulnerabilities-affecting-girault-bulletproofs-and-plonk/
- https://blog.trailofbits.com/2022/04/15/the-frozen-heart-vulnerability-in-bulletproofs/
- https://blog.trailofbits.com/2022/04/18/the-frozen-heart-vulnerability-in-plonk/
- https://www.zkdocs.com/docs/zkdocs/protocol-primitives/fiat-shamir/
- https://github.com/advisories/GHSA-hx53-8mp7-3h3w
- https://github.com/advisories/GHSA-7p92-x423-vwj6
- https://github.com/advisories/GHSA-q3hw-3gm4-w5cr
- https://github.com/advisories/GHSA-8m24-3cfx-9fjw
- https://github.com/advisories/GHSA-c873-wfhp-wx5m
- https://github.com/advisories/GHSA-vj64-rjf3-w3v7
- https://zcash.github.io/halo2/design/implementation/proofs.html
Related guides: under-constrained circuits, auditing circom, choosing a proving system, trusted setup risk, the checklist.