Skip to content

fix: RV64 completeness defects in DivRem/Weierstrass & revert RISCOF CI / chip tests (#108, #109) - #107

Merged
succinctli merged 9 commits into
mainfrom
fix-ci-and-tests
Jul 29, 2026
Merged

fix: RV64 completeness defects in DivRem/Weierstrass & revert RISCOF CI / chip tests (#108, #109)#107
succinctli merged 9 commits into
mainfrom
fix-ci-and-tests

Conversation

@kaiwei-0

@kaiwei-0 kaiwei-0 commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Closes #108. Closes #109.

Summary

This PR addresses RV64 completeness defects in DivRem and Weierstrass decompress chips, and reverts changes of RISCOF CI and chip tests.

What this fixes

Four completeness defects. Each one makes the prover reject an execution that is valid.

Affected When it triggers What you see
DivRem any RV64 word division (DIVW/DIVUW/REMW/REMUW) Regional cumulative sum is not zero
DivRem unsigned word division, certain operand values Constraint verification failed, chip: DivRem
DivRem word division where the operand's upper half is dirty Regional cumulative sum is not zero
Weierstrass decompress every decompress call Regional cumulative sum is not zero

@kaiwei-0
kaiwei-0 requested a review from succinctli July 24, 2026 13:33
kaiwei-0 added 6 commits July 24, 2026 21:47
Closes #108 and #109. Two one-sided completeness defects in the DivRem AIR:
each can only make the prover reject an honest execution, never accept an
invalid one. Together they are nine lines in one file and add no columns, so
the embedded vk map is unaffected by this commit.

Defect 1 (#108, DIVUW) -- the trace populates `quot_msb` and emits the
matching `U16Range` byte event, but the AIR never evaluated the MSB gadget on
the second limb of the quotient. Every word division therefore recorded one
unpaired byte event. No constraint is violated, so it surfaces only at the
very end as `verifier.rs:391 Regional cumulative sum is not zero`, with no
chip name attached.

Defect 2 (#109, REMUW) -- the `|remainder| == abs_remainder` check compared
the sign-extended `remainder` instead of the zero-extended `remainder_comp`,
so an unsigned word operation whose remainder has bit 31 set failed at
`verifier.rs:384`.

Defect 2 masks defect 1: `:384` is raised inside the per-chip loop while
`:391` is only reached after it, so until defect 2 is fixed the defect-1
failure is invisible.

Measured on the two issues' own reproducers, with nothing else applied:

    defect 1 only  -> #108 reproducer passes; #109 fails :384 chip: DivRem
    defects 1 + 2  -> both reproducers pass

Golden counts for the chip move 337/120 -> 339/121; the delta is entirely the
newly evaluated MSB gadget. Defect 2 changes no counts.
A third completeness defect, independent of #108 and #109 and not needed to
close them, but latent on any RV64 word division whose operand has bit 31 set.

RV64 word operations read only the low 32 bits of their operands, so the upper
32 bits of the source register are whatever happened to be there. That is not
a corner case: the RV64 ABI keeps 32-bit values sign-extended, so LLVM
materialises `0xFFFF_FFFFu32` as `li -1` and the upper half is all ones.

The DivRem chip stored only the *computational* operands -- the low 32 bits
zero- or sign-extended according to the opcode -- and put those on the ALU
bus. The CPU chip sends the raw register values. For an unsigned word
operation with bit 31 set the two disagree in the upper limbs, the send and
the receive never pair up, and the proof dies at `verifier.rs:391 Regional
cumulative sum is not zero` -- again with no chip name, because nothing is
actually violated.

Fix: keep both forms. `b_raw`/`c_raw` hold what the CPU read and are what the
bus now carries; `b`/`c` stay the computational form the arithmetic uses, and
new constraints tie the two together (equal on the low limbs; on the high
limbs either the raw value or the sign fill, selected by `is_word_operation`).
The padding row is a non-word op, so it must satisfy `*_raw == *` on every
limb.

This adds 8 columns, which changes every DivRem vk digest -- the embedded
vk map has to be rebuilt (done in a later commit on this branch).

Golden counts for the chip move 339 -> 347 constraints (four limb pairs, two
operands); lookups are unchanged.
A fourth completeness defect, in the Weierstrass decompress chips this time,
with the same signature as the other unpaired-lookup bugs: the proof dies at
`verifier.rs:391 Regional cumulative sum is not zero` with no chip named.

The trace populates `y_access.prev_value_u8` and, in doing so, emits a u8
range check for every byte. The AIR only ran the u16->u8 conversion on
`x_access`, so those `y_access` byte events had no matching evaluation and the
byte table was over-received.

Evaluating the conversion on `y_access` too pairs them up. The returned limbs
are deliberately unused -- `y_access`'s value is already constrained against
`sqrt_y`/`neg_y` further down -- so the call exists to balance the lookups and
to pin the byte decomposition. It is a write access, hence the `write` helper,
which reads `inner.prev_value` exactly as the trace did.

Pure AIR change: no new columns, no trace change, so the vk digest moves only
because the lookup set does.

Golden lookups for the secp256k1 decompress chip move 658 -> 674: four
`y_access` words, four u8 range checks each. Constraints are unchanged,
because the conversion gadget only emits lookups.
…tion

Two changes that make the vk map rebuild required by the previous commits
cheaper to produce and cheaper to debug.

(a) `program_heights` drops the 19 entry, cutting the enumerated shape count
by 25% (403,014 -> 302,355) and so cutting the `build_vk_map` run by the same
proportion. The old list is kept as a comment.

This is not a correctness change. `find_shape_from_allowed_heights` scans the
allowed heights in ascending order and takes the first one that fits, so a
program that used to land on 19 now lands on 20 -- it pads up, it does not
fail. Only removing the *largest* height can produce
`PreprocessedShapeError`. Measured across the whole corpus, nothing needed 19
in the first place: every example ELF is under 37k instructions and lands on
18, while the reth ELF is 548,975 instructions and already needed 20 (2^19 is
524,288). The band between 2^18 and 2^19 is empty.

The Merkle tree height is unaffected: it is
`total_num.next_power_of_two().ilog2()` over the *deduplicated* shape count,
and 302,355 still rounds up to 2^19.

(b) When a vk is rejected, print the preprocessed shape and the map size
instead of only the digest. The bare digest cannot distinguish the two causes
that actually occur -- a map that is stale with respect to the circuit, versus
a shape the config never enumerated -- and the message now names both, plus
the `riscv_proofshape_map_*.bin` cache, which is keyed only on chip names and
log heights and so will silently hand back pre-change digests if left in place
across a rebuild.
The added DivRem columns and the changed DivRem/decompress lookup sets move
every affected vk digest, so the maps shipped in-tree no longer match the
circuit and `VK_VERIFICATION=true` would reject valid proofs outright.

Rebuilt from scratch on clean machines, once per field, with no
`riscv_proofshape_map_*.bin` cache present -- that cache is keyed only on chip
names and log heights, so leaving it in place would have silently reproduced
the old digests while still reporting success.

    Total num of all shapes (after deduplication): 302355

for both fields, matching the shape count expected from the height-19 removal,
and still inside 2^19 so the Merkle tree height is unchanged.

Both maps were then exercised with `VK_VERIFICATION=true` end to end: the
eight DivRem corner ELFs and the decompress gate plus two controls on
KoalaBear, and a sample of the same on BabyBear -- the two maps are separate
build runs, so checking only one field would leave the other unverified.

NOTE for downstream consumers: vk indices come from a `BTreeSet` ordering over
the digests, so essentially every index shifts, not just the changed ones. Any
deployed on-chain verifier pinned to the previous `vk_root` needs to be
regenerated.
@kaiwei-0 kaiwei-0 changed the title fix: revert RISCOF CI and chip tests fix: RV64 completeness defects in DivRem/Weierstrass & revert RISCOF CI / chip tests (#108, #109) Jul 29, 2026

@succinctli succinctli left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@succinctli
succinctli merged commit 7067e6d into main Jul 29, 2026
7 checks passed
@succinctli
succinctli deleted the fix-ci-and-tests branch July 29, 2026 02:54
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.

Panic: Constraint verification failed, chip: DivRem (for REMUW) Panic: Regional cumulative sum is not zero (for DIVUW)

2 participants