A production Fiat–Shamir transcript, stepped one stage at a time. Not one challenge at a time — one constant added, one S-box evaluated, one matrix applied: the width-3 Poseidon2 permutation over BN254 running inside a persistent duplex challenger, with every field element on screen at every point. It answers the question you actually have when two implementations disagree on a challenge: which of the sixty-four rounds, or which of the six duplex rules, did we do differently?
Scope. The transcript and nothing else: no proving system, no arithmetization, no commitment scheme — only what goes into the sponge, what comes out, and in what order, because that is the part independent implementations most often disagree about. Everything on screen is computed in BigInt arithmetic modulo the BN254 scalar modulus, from the reference round constants.
The width-3 BN254 Poseidon2 instance driving a rate-2 / capacity-1 duplex challenger — the Plonky3 transcript framing on the Horizen Labs permutation:
| Parameter | Value |
|---|---|
| Field | BN254 scalar field Fr — not the curve base field, the most common way to get this suite wrong |
| State width | t = 3, as rate 2 + capacity 1 |
| S-box | x → x5 — three field multiplications each |
| Rounds | RF = 8 full and RP = 56 partial, split four–fifty-six–four |
| Constants | the exact RC3 table from the reference instance, never regenerated at runtime |
Poseidon2's whole economy is the partial round, so the two round types get two separate machines, both permanently on screen; the one matching the current round fills with values while the other stays dark.
- In an external round — four at the start, four at the end — all three lanes take a round constant and an S-box, and the layer is
ME. Nine multiplications. - In an internal round — the fifty-six in the middle — only lane 0 takes a constant and an S-box, and the layer is
MI. Three multiplications.
The two matrices differ in exactly one entry: the bottom-right is 2 in ME and 3 in MI. One permutation costs 80 S-box evaluations and 240 field multiplications, where sixty-four full rounds would need 192 S-boxes.
Four lines of the duplex change every challenge downstream if written differently, and the loaded transcript walks into each one:
| Rule | What breaks without it |
|---|---|
| Absorption overwrites | A sponge that adds into the rate lanes instead produces entirely different challenges from the same inputs. |
| Unused rate lanes are zeroed | Leaving lane 1 holding the previous output would silently bind the challenge to unrelated history. |
| The length is bound into the capacity | state[2] += n keeps absorbing one element distinct from absorbing two whose second is zero. |
| Challenges come off the end | pop() takes the last output, so the first challenge is s1 and the second is s0. Reversing it makes every challenge wrong. |
These are Plonky3's rules, not a variant of them, and the visualizer reproduces its DuplexChallenger challenges exactly. Length binding landed in Plonky3 #1769 in June 2026; an implementation pinned to anything earlier will produce different challenges from the same inputs.
Two more behaviours get their own moment: observing new material invalidates any challenge still in the output buffer, and a sample() with nothing buffered and nothing left to squeeze runs a permutation with no absorb at all.
Eight calls, four permutations, three challenges, on one continuous state — the shortest script that walks into every rule above:
- Five elements go in, one at a time, so the sponge permutes twice and the fifth is still buffered.
- A challenge is asked for on a partial buffer, exercising the zero-padded lane and the length tag together.
- A second challenge comes free off the same permutation, from the other rate lane.
- A third is asked for with nothing left, so the duplex permutes with no absorb at all.
The duplex sponge, drawn with the capacity off the input–output path; both round machines; a phase rail on which skipped phases are struck through; a sentence of narration per stage; all three lanes at their full 254 bits; and a spec drawer with both matrices, all 64 rounds of RC3, and the mandatory known-answer test on [0, 1, 2], run in the page. Stepping backwards restores every value exactly.
