Skip to content
HN On Hacker News ↗

Unverified Evaluations in Dusk's PLONK

▲ 35 points 8 comments by deut-erium 2mo ago HN discussion ↗

Pangram verdict · v3.3

We believe that this document is primarily human-written, with some AI-generated content detected

25 %

AI likelihood · overall

Mixed
82% human-written 18% AI-generated
SEGMENTS · HUMAN 3 of 6
SEGMENTS · AI 0 of 6
WORD COUNT 1,462
PEAK AI % 50% · §4
Analyzed
May 3
backend: pangram/v3.3
Segments scanned
6 windows
avg 244 words each
Distribution
82 / 18%
human / AI fraction
Verdict
Mixed
Pangram v3.3

Article text · 1,462 words · 6 segments analyzed

Human AI-generated
§1 Human · 14%

Commitment Issues: Unverified Evaluations in Dusk's PLONKWe found a critical soundness vulnerability in dusk-plonk, the PLONK implementation powering Dusk Network's ~$60M market cap. By exploiting a gap in the verification step, a malicious prover could forge verifying proofs for arbitrary false statements, bypassing every constraint in the transaction circuit. On the live Rusk network, this would have enabled minting arbitrary amounts of DUSK and moving forged shielded funds through the normal Phoenix path.The root cause was that the prover slipped four public selector evaluations into the proof struct, and the verifier consumed them in its final equation without ever validating them against the trusted commitments in the verifier key. The prover can set them to whatever values make the equation pass.How PLONK works (briefly)For a rigorous treatment see the original paper; what follows covers only the parts needed to understand the bug.A prover wants to convince a verifier that it knows secret inputs satisfying some computation (an arithmetic circuit) without revealing those inputs, and the resulting proof should be short and quick to verify.Arithmetic circuits and constraintsAn arithmetic circuit is a series of addition and multiplication gates wired together. An example would be proving that we know of some point on an elliptic curve, by e.g proving that , here in . Arithmetic Circuit In F37\mathbb{F}_{37} Circuit computes y2−(x3+7)y^2-(x^3+7) over F37\mathbb{F}_{37} and checks whether it equals 00. 63631111abboaoababoaoboxy××7+×−0x = 6y = 1gate\text{gate}qMq_MqLq_LqRq_RqOq_OqCq_Caabboo× (x⋅x)\times\,(x\cdot x)100-106636× (x2⋅x)\times\,(x^2\cdot x)100-1036631+

§2 Mixed · 31%

(x3+7)+\,(x^3+7)010-173101× (y⋅y)\times\,(y\cdot y)100-10111− (y2−(x3+7))-\,(y^2-(x^3+7))01-1-10110=?0\overset{?}{=}001000000Each gate has a left input , right input , and output . The prover's job is to show it knows wire values that satisfy every gate.Each gate imposes a constraint, and PLONK unifies all gate types into one expression using selector values that act as switches: setting makes a row a multiplication gate, setting makes it contribute an addition term, and so on. The selector values define the circuit's shape and are public, known to both prover and verifier, while the wire values are the prover's secret witness. This per-row check does not ensure that wires between gates are consistent (that the output of one gate equals the input of the next); PLONK uses a separate permutation argument for that, which we will not cover here.From many checks to oneInstead of checking each gate individually, PLONK reads the execution trace column by column and uses FFT interpolation to convert each array of values to a single polynomial. The wire values become witness polynomials , , and the selectors become selector polynomials , , etc., all interpolated over a domain of -th roots of unity. Evaluating at the -th root recovers the left wire value at row .Interactive Polynomial Interpolation Toy circuit (x+2y)z=0(x+2y)z=02-5-83-24xyz+×0gate\text{gate}qMq_MqLq_LqRq_RqOq_OqCq_C+ (x+2y)+\,(x+2y)012-10× ((x+2y)⋅z)=?0\times\,((x+2y)\cdot z)\overset{?}{=}010000 This is the same row-to-polynomial step as above, but over the reals on {−1,1}\{-1, 1\}.

§3 Human · 26%

The selector rows interpolate to QM(x)=12x+12,  QL(x)=12−12x,  QR(x)=1−x,  QO(x)=12x−12,  QC(x)=0Q_M(x)=\tfrac{1}{2}x+\tfrac{1}{2},\;Q_L(x)=\tfrac{1}{2}-\tfrac{1}{2}x,\;Q_R(x)=1-x,\;Q_O(x)=\tfrac{1}{2}x-\tfrac{1}{2},\;Q_C(x)=0. Move x,  y,  zx,\;y,\;z. The point is to see when (x+2y)z=0(x+2y)z=0, we get F(−1)=0  and  F(1)=0F(-1)=0\;\text{and}\;F(1)=0, so Z(x)=x2−1Z(x)=x^2-1 divides F(x)F(x). Move x, y, zx=A(−1)x = A(-1)y=B(−1)y = B(-1)z=B(1)z = B(1)A(x)=−5x−3A(x) = -5x - 3B(x)=4x−1B(x) = 4x - 1O(x)=−8x−16O(x) = -8x - 16F(x)=−10x3−19x2−2x+7F(x) = -10x^{3} - 19x^{2} - 2x + 7Z(x)=x2−1Z(x) = x^2 - 1Z(x)∤F(x),  so F(x)/Z(x) is not a polynomialZ(x)\nmid F(x),\;\text{so }F(x)/Z(x)\text{ is not a polynomial}Because all columns are now polynomials, the entire circuit compresses into a single master constraint polynomial that combines selectors and witnesses. If the prover was honest, at every row index in the domain.

§4 Mixed · 50%

The vanishing polynomial is zero on exactly those points, so if all constraints hold then divides , yielding a quotient polynomial with .Polynomial commitments and opening proofsTo keep the proof short, the prover doesn't send polynomials directly. Instead, it sends commitments, short cryptographic fingerprints of each polynomial (using e.g. KZG commitments). When the verifier needs the value of a committed polynomial at a specific point, the prover provides the value along with an opening proof that the claimed value is consistent with the earlier commitment.A committed polynomial evaluation is therefore cryptographically bound, and the prover cannot lie about the value without being caught.Reducing to a single random pointAfter the prover commits to all polynomials, including , the verifier picks a random challenge point (derived via the Fiat-Shamir heuristic from the transcript) and checks at that single point. By the Schwartz-Zippel lemma, if this holds at a random then the full polynomial identity holds with overwhelming probability, so the verifier checks the entire multi-million-row circuit in constant time.In textbook PLONK the selector polynomials are part of the fixed circuit description, but in practice implementations commit to them during preprocessing and place those commitments in the verifier key. When the verifier later needs their values at , the prover supplies evaluation claims that must be checked against those commitments with opening proofs.The security argument depends on a chain: commitments lock the prover into polynomials before challenges are derived, and opening proofs ensure the evaluations are consistent with those commitments. Breaking any single link in this chain collapses soundness entirely.What the verifier is actually allowed to trustFor this bug, one invariant matters more than the rest: every scalar that enters the final verifier equation must be either locally computed by the verifier, or cryptographically tied to an earlier commitment.In practice, values entering the verifier equation fall into three buckets. The verifier computes some values locally from public data (, , the public-input polynomial at ), which are safe because the prover never chooses them. Other values are prover-supplied evaluations accompanied by KZG opening proofs (, , , ), which are safe because the opening binds them to previously committed polynomials. A third category consists of verifier-key commitments used directly in the linearization multiscalar multiplication (, , ), which are safe because the verifier never trusts a bare field element for these; it uses the commitment itself.

§5 Mixed · 34%

Any term that falls outside those three categories is attacker-controlled by construction.Where dusk-plonk differs from textbook PLONKdusk-plonk is not a literal transcription of the 2019 PLONK paper. It extends the arithmetic gate with a fourth wire d, adds custom widgets for range, logic, and elliptic-curve operations, uses shifted evaluations at , and heavily batches KZG openings. None of that is exotic by modern PLONK standards, but it does make the verifier harder to reason about than the minimal paper presentation.The important part for this bug is the boundary between public circuit data and prover claims about that data at the random challenge point. Parallel implementations avoid this ambiguity by keeping selector polynomials strictly out of the prover's hands. For example, Consensys' gnark (one of the most widely deployed PLONK implementations) never asks the prover for selector evaluations at all. Instead, the verifier incorporates the selector commitments Ql, Qr, Qm, Qo, Qk directly into the linearization multi-scalar multiplication, ensuring their values are cryptographically bound by construction.Dusk's custom widgets were more complex (multiplying selectors with other evaluated terms), so they could not just use a simple linear combination of commitments. Their architecture required evaluating the selectors at and using those scalars. But while they serialized those four selector evaluations into the proof struct, they never actually verified them against the verifier key's commitments through an opening proof.The shortest way to see the bug is the graph below: safe values flow through the opening path toward the final pairing check, while the red selector flow enters verifier logic without ever touching an opening proof.Verifier Dependence GraphWhat actually flows into the final check? Click a proof value or commitment to trace its verifier slice. Safe values feed the opening accumulator on the way to the pairing check; the red selector flow shows values the verifier consumes without ever opening. commit 82c08e8f11f2red = consumed but not openedSelected variableNeighborhoodFilter kindsProof evalProof commVK commTranscriptChallengeDerived scalarVerifier checkAggregationPairingFinal checkZoomProof EvaluationsVK CommitmentsTranscriptChallenges / ScalarsChecks / IdentitiesAggregation /

§6 Human · 11%

Pairings_sigma_1_evals_evalsigma1s_sigma_2_evals_evalsigma2z_evalz_evals_sigma_3_evals_evalsigma3a_w_evala_w_evalb_w_evalb_w_evala_evala_evalb_evalb_evalc_evalc_evald_evald_evald_w_evald_w_evalq_l_evalq_l_evalq_r_evalq_r_evalq_c_evalq_c_evalq_arith_evalqarithevalq_mq_mq_lq_lq_rq_rq_oq_oq_fq_fq_cq_cabsorb evaluation claimsabsorbevaluationclaimsv_challengev_challengeE_evalsE_evalsarithmetic identityarithmeticidentityE_scalaruE_scalarDZ_H(z)zDHow Dusk uses PLONKDusk Network is a privacy-focused L1 blockchain. Its transaction model has two modes:Phoenix (shielded): amounts and participants are hidden using ZK proofs, and every Phoenix transaction carries a PLONK proof that the transaction is valid.Moonlight (transparent): standard account-based transactions verified by BLS signatures, with no PLONK involvement.At node level, every ProtocolTransaction::Phoenix goes through verify_proof_with_version() during preverification. If that PLONK proof verifies, the transaction is admitted to the mempool and can later be mined into a block. Moonlight-path transactions instead go through BLS signature verification.That same Phoenix proof path covers more than simple shielded transfers. Phoenix-path staking, reward withdrawals, unstaking, and Phoenix-to-Moonlight conversion all build a Phoenix transaction via phoenix(), for example in phoenix_stake(), phoenix_stake_reward(), phoenix_unstake(), and phoenix_to_moonlight(). So if Phoenix proof verification is unsound, the entire shielded transaction path is exposed.The PLONK implementation, dusk-plonk, is a standalone library by the Dusk team. It was among the first PLONK implementations written, with development starting the same year the original paper was released.The Phoenix transaction PLONK circuit is defined here.