Skip to content

fix getCutPoint lower bound - #630

Merged
OBrezhniev merged 3 commits into
iden3:feature/memory-scopingfrom
teddav:fix-cut-point
Jul 9, 2026
Merged

fix getCutPoint lower bound#630
OBrezhniev merged 3 commits into
iden3:feature/memory-scopingfrom
teddav:fix-cut-point

Conversation

@teddav

@teddav teddav commented Jul 8, 2026

Copy link
Copy Markdown

No description provided.

teddav and others added 2 commits July 8, 2026 15:10
Adds a fixture circuit (circom --O0) that alternates multiplicative
rows with purely-linear ones (l <== a + b). Linear constraints compile
to the 0*0 = C form, and section 4 of the zkey holds records only for
A-side/B-side terms -- so those rows have no coefficient records at
all, leaving gaps in the sorted c-sequence:

  c-values: 0,0,2,2,4,4,6,6,8,8,10,10,12,12,14,14,17,18

Pre-fix, a chunk boundary landing on a missing row made getCutPoint
return a cut point one coefficient too low; the dropped coefficient
corrupted the QAP evaluations and the proof failed to verify. The
test covers default prove options plus explicit nChunks=4/32 (a cut
point at every row), and includes a tamper-check to guard against a
degenerate ptau masking failures (a no-contribution tau=1 accumulator
accepts even corrupted proofs).

Verified: all three tests fail against the pre-fix bisection and pass
with it.
@OBrezhniev

Copy link
Copy Markdown
Member

I reviewed this fix, confirmed the bug is real and reachable with default prove options, and pushed a regression test to this branch (test/buildabc_gap.test.js + fixture, 2fe28c9) that fails deterministically pre-fix and passes with it.

The trigger condition: section 4 of the zkey only holds coefficient records for A-side (m=0) and B-side (m=1) terms — C-side terms produce no records. A constraint whose A and B are both empty (the 0*0 = C form circom emits for purely linear constraints like l <== a + b when optimization is off) therefore has no records at its row index, leaving a gap in the sorted c-sequence. The fixture (compiled with circom --O0) alternates multiplicative and pure-linear rows, giving:

c-values: 0,0,2,2,4,4,6,6,8,8,10,10,12,12,14,14,17,18

Rows 1,3,…,15,16 are missing. A chunk boundary landing on any of them made the old bisection return a cut point one coefficient short; I diffed the QAP buffers built with old-vs-new cut points and confirmed the dropped coefficient corrupts buffB_T/buffC_T, so the proof fails to verify.

Two notes for anyone reproducing this:

  1. Circuits compiled with circom's default optimization (--O1/--O2) fold away pure-linear constraints, leaving dense c-sequences where the old code happens to work. That's why standard benchmarks never caught this — but --O0 builds (common for debugging) hit it with no special options.
  2. Reproduction requires a ptau with at least one real contribution. A fresh accumulator (τ=1) degenerates the pairing check so badly that even a corrupted proof "verifies" — my first repro attempt was masked by exactly this, and the test includes a tamper-check guard against it.

Verified on this branch: the new test fails all three cases against the pre-fix bisection and passes with the fix; the full existing suite (52 tests) also passes. Leaving formal approval to a maintainer since I've now contributed to the branch.

src/groth16_prove.js merged cleanly (the lower_bound fix and the
base's buildABC option validation touch different lines). The
conflicted build/ bundles are derived artifacts: resolved by
rebuilding all five outputs from the merged source rather than
hand-merging. Full suite (66 tests, including the new gap-circuit
regression tests) passes on the merged tree.
@OBrezhniev
OBrezhniev merged commit c0f7622 into iden3:feature/memory-scoping Jul 9, 2026
15 of 19 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants