Repository navigation
fix: bind opening point in ZK evaluation proofs - #23
Merged
Merged
Conversation
ZK mode did not bind the evaluation opening point: a proof created for a point P verified at any point P'. The point reaches the verifier only through the folded scalars s1_acc/s2_acc, and the 0.3.0 ZK final check (a collapsed 1-pairing scalar-product argument over the pre-fold statement) never read them. Restore the original Dory construction (eprint 2020/1274): apply Fold-Scalars (§4.1) to the witness before the Scalar-Product argument (§3.1), so the argument opens the point-dependent folded statement (C', D1', D2'). The verifier reconstructs (C', D1', D2') from its own point-derived s1_acc/s2_acc and the E-accumulators, batched into a 3-pairing check. This pins the point coefficient to the committed witness while the folded coordinates v1[0]/v2[0] stay hidden. - Add DoryProverState::apply_fold_scalars (§4.1 witness update; used by both modes). - Absorb the scalar-product responses (e1,e2,r1,r2,r3) into the transcript before the batching challenge d, and drop the unconstrained r_final blinds (the degree of freedom a point-agnostic fix would leave exploitable, advisory §5). - DoryProof.final_message is now Option: Some in transparent mode (reveals the folded witness), None in ZK (the Sigma-proof replaces it). Breaking proof-format/API change; 0.3.0 ZK proofs must be regenerated. Regression tests: honest ZK proof rejected at a wrong point; a crafted wrong-point proof that shifts the Sigma-blinds by the public point-deltas is rejected; final message must be present iff transparent. zk_statistical (ZK/uniformity) still passes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
markosg04
approved these changes
Jul 3, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes a critical soundness bug in ZK mode (
zkfeature): the evaluation opening point was not bound by the verifier. A ZK proof created for a pointPverified at any pointP', letting a malicious prover open a committed polynomial to a forged evaluation at an attacker-chosen point. Transparent mode was unaffected.Special thanks to Veria Labs for finding and reporting this vulnerability!
Root cause
The evaluation point reaches the verifier only through the folded scalars
s1_acc/s2_acc(accumulated inDoryVerifierState::process_round). The 0.3.0 ZK final check was a collapsed 1-pairing scalar-product argument over the pre-fold statement(C, D₁, D₂)and never read them — it dropped the point-binding terms (the transparent check's Pairs 2 & 3 and thes1_acc·s2_acc·HTterm) with no zero-knowledge replacement.Fix — restore the original Dory construction
Per the Dory paper (eprint 2020/1274, §4.1 Fold-Scalars → §3.1 Scalar-Product):
v₁ += γ·s₁·H₁,v₂ += γ⁻¹·s₂·H₂,r_C += γ·r_E2 + γ⁻¹·r_E1) via the newDoryProverState::apply_fold_scalars, before running the scalar-product Σ-argument. The argument now opens the point-dependent folded statement(C′, D₁′, D₂′).(C′, D₁′, D₂′)from its own point-deriveds1_acc/s2_accand the E-accumulators, batched into a 3-pairing check (mirrors Pairs 1–3 of the transparent check). This pins the point coefficient to the committed witness whilev₁[0]/v₂[0]stay hidden.(E₁, E₂, r₁, r₂, r₃)are absorbed into the Fiat–Shamir transcript before the batching challengedis drawn, and the unconstrainedr_final1/r_final2blinds are removed — closing the degree of freedom a point-agnostic fix would leave exploitable (advisory §5).Breaking change
DoryProof::final_messageis nowOption<ScalarProductMessage>—Somein transparent mode (reveals the folded witness),Nonein ZK (the Σ-proof replaces it; revealing it would break hiding). ZK proofs generated by 0.3.0 no longer verify and must be regenerated. Documented inCHANGELOG.mdunder[Unreleased]; the version bump is left as a release decision.Tests
examples/zk_wrong_point.rs(advisory PoC): reportswrong_point_accepted=falseat every size after the fix (truebefore).test_zk_wrong_point_rejected: an honest ZK proof is rejected at a shifted and at a random point.test_zk_crafted_blind_shift_wrong_point_rejected: an adversarial wrong-point proof that shifts the Σ-blinds by the exact public point-deltas (§5's attack) is rejected.test_soundness_missing_final_message/test_zk_soundness_unexpected_final_message: the clear final message must be present iff the proof is transparent.zk, 67 transparent),clippy/fmtclean across all feature combos, both proof formats round-trip, andzk_statistical(ZK uniformity + witness-independence, 1000 trials) still passes.Downstream note
The Jolt-side items in the advisory (
jolt-verifierstage 8,jolt-dory, and flippingzk_wrong_point_binding_poctois_err()) live in a separate repo. When Jolt bumps this dependency, that PoC test should be flipped.🤖 Generated with Claude Code