CVE-2025-24802 Overview
CVE-2025-24802 is a soundness vulnerability in Plonky2, a SNARK (Succinct Non-interactive Argument of Knowledge) implementation that combines techniques from PLONK and FRI (Fast Reed-Solomon Interactive Oracle Proof). The flaw exists in how LookupTableGate instances handle padding when a lookup table's length is not divisible by 26, which equals floor(num_routed_wires / 3). Padding slots are filled with zero values, which causes the gate to always accept the pair (0, 0) as a valid lookup entry. A malicious prover can use this to generate proofs asserting f(0) = 0 for any lookup table f, breaking the integrity guarantees of the zero-knowledge proof system [CWE-1240].
Critical Impact
A malicious prover can forge valid SNARK proofs for false statements, undermining the integrity of any application that relies on Plonky2 lookup tables for verification.
Affected Products
- Plonky2 SNARK library (0xPolygonZero/plonky2) versions prior to 1.0.1
- Applications using Plonky2 lookup tables whose length is not divisible by 26
- Downstream zero-knowledge circuits and rollup systems depending on Plonky2 proof soundness
Discovery Timeline
- 2025-01-30 - CVE-2025-24802 published to NVD
- 2026-04-15 - Last updated in NVD database
Technical Details for CVE-2025-24802
Vulnerability Analysis
Plonky2 uses LookupTableGate to enforce lookup constraints inside a circuit. Each gate slot encodes an (input, output) pair from a lookup table (LUT). When the LUT length is not a multiple of 26, the prover must fill remaining slots with padding to complete the gate. The original implementation padded these unused slots with zero values for both input and output targets. The verifier then treats these zero pairs as valid entries in the lookup table. Consequently, the constraint system accepts f(0) = 0 as a valid claim regardless of whether the actual table contains such a mapping. This violates the cryptographic soundness property the proof system is designed to provide. The issue is classified under [CWE-1240], improper use of a cryptographic primitive.
Root Cause
The defect lives in the witness generation logic for LookupTableGate in plonky2/src/gates/lookup_table.rs. Padding slots wrote F::ZERO to both the input and output wire targets without recording that these were synthetic entries. The companion LookupGate constraint in plonky2/src/gates/lookup.rs did not bound-check input_val against self.lut.len(), so any input matching a padded zero entry satisfied the verification predicate.
Attack Vector
The attack is purely cryptographic and does not require network or local access to the proving system. A malicious prover constructs a circuit that asserts f(0) = 0 for a lookup table where this relation does not hold, then generates a Plonky2 proof. Verifiers running unpatched code accept the proof as valid. In rollup or bridge contexts, this can be used to authorize state transitions or assertions that the circuit author never intended.
// Patch in plonky2/src/gates/lookup.rs - bounds check added before LUT access
let get_wire = |wire: usize| -> F { witness.get_target(Target::wire(self.row, wire)) };
let input_val = get_wire(LookupGate::wire_ith_looking_inp(self.slot_nb));
-let (input, output) = self.lut[input_val.to_canonical_u64() as usize];
-if input_val == F::from_canonical_u16(input) {
+if (input_val.to_canonical_u64() as usize) < self.lut.len()
+ && input_val == F::from_canonical_u16(self.lut[input_val.to_canonical_u64() as usize].0)
+{
+ let (_, output) = self.lut[input_val.to_canonical_u64() as usize];
let output_val = F::from_canonical_u16(output);
let out_wire = Target::wire(self.row, LookupGate::wire_ith_looking_out(self.slot_nb));
Source: GitHub Commit 091047f
Detection Methods for CVE-2025-24802
Indicators of Compromise
- Plonky2 proofs that successfully verify a statement involving f(0) = 0 for lookup tables where the underlying table does not map 0 to 0
- Use of Plonky2 dependencies pinned to versions below 1.0.1 in Cargo.toml or Cargo.lock
- Circuits constructed with lookup tables whose entry count is not divisible by 26
Detection Strategies
- Audit dependency manifests across repositories and build pipelines for plonky2 entries below 1.0.1
- Inspect circuit code for LookupTableGate usage and verify that LUT lengths are either patched or padded to a multiple of 26
- Replay historical proofs against a patched verifier and flag any that fail re-verification
Monitoring Recommendations
- Track newly published proofs in production rollups for anomalous claims involving zero-valued lookup inputs and outputs
- Monitor source code repositories for reintroduction of vulnerable Plonky2 versions through transitive dependencies
- Add CI checks that fail builds pulling Plonky2 versions earlier than 1.0.1
How to Mitigate CVE-2025-24802
Immediate Actions Required
- Upgrade Plonky2 to version 1.0.1 or later across all proving and verification components
- Re-deploy verifier contracts and services so that any in-flight proofs are evaluated under the fixed constraint logic
- Inventory production circuits and identify those using lookup tables with lengths not divisible by 26
Patch Information
The fix is committed in GitHub Commit 091047f and released in Plonky2 1.0.1. The patch changes padding behavior in LookupTableGate to replicate the first LUT entry instead of writing zeros, and adds bounds checks in LookupGate to reject inputs that fall outside the table. Full advisory details are in the GitHub Security Advisory GHSA-hj49-h7fq-px5h.
// Patch in plonky2/src/gates/lookup_table.rs - pad with first LUT entry
} else {
- // Pad with zeros.
- out_buffer.set_target(slot_input_target, F::ZERO)?;
- out_buffer.set_target(slot_output_target, F::ZERO)
+ // Pad with first element in the LUT.
+ assert!(!self.lut.is_empty(), "Empty LUTs are not supported.");
+ let (input, output) = self.lut[0];
+ out_buffer.set_target(slot_input_target, F::from_canonical_usize(input as usize))?;
+ out_buffer.set_target(slot_output_target, F::from_canonical_usize(output as usize))
}
Source: GitHub Commit 091047f
Workarounds
- Extend the lookup table by repeating existing entries so that its length becomes a multiple of 26, eliminating the zero-padded slots that trigger the flaw
- Reject any proof at the application layer that claims f(0) = 0 unless that mapping exists in the canonical lookup table
- Pin Plonky2 to 1.0.1 in dependency manifests to prevent downgrades by transitive resolution
# Update Plonky2 to the fixed release
cargo update -p plonky2 --precise 1.0.1
cargo tree -p plonky2 | grep plonky2
Disclaimer: This content was generated using AI. While we strive for accuracy, please verify critical information with official sources.

