Skip to content

refactor(math): verify Uint128 division with native arithmetic (11/13) - #1146

Draft
pepebndc wants to merge 4 commits into
refactor/math-10-uint128-maxfrom
refactor/math-11-native-div
Draft

pepebndc wants to merge 4 commits into
refactor/math-10-uint128-maxfrom
refactor/math-11-native-div

Conversation

@pepebndc

@pepebndc pepebndc commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Part 11 of 13 of the math library migration from midnight-apps contracts/src/math. Refs #213.

Stack: #1125 → #1126 → #1127 → #1128 → #1140 → #1141 → #1142 → #1143 → #1144 → #1145 → #1146 → #1147 → #1148. Merge in order.

Summary

The Uint<128> forms of div, rem, divRem and isMultiple check the result of one wit_divUint128(a, b) call with native arithmetic. Every valid input keeps its result and its failure message, and the circuits are about 4 times smaller.

Changes

  • Native checks of the division result, in the order of the U128 form (73f3901d). Rows (v2 / v3): div 3356 / 1017 to 845 / 287, divRem 3269 / 1026 to 886 / 328. The forms no longer call wit_divU128.
  • The guides and the CHANGELOG describe the change (44ba18fd), including every outcome for an incorrect witness result (28dd3eac).
  • The guides and the CHANGELOG state that these forms call wit_divUint128 with the divisor b, so a consumer witness must implement a general division, as the reference does; a witness that only splits at 2^64 makes them fail (ba8b17e1).
  • Outcomes for an incorrect witness result only. (1) The forms no longer split a and b, so a wrong split no longer fails them with Uint128: conversion invalid; they return the honest result. (2) They no longer call wit_divU128, so a replaced wit_divU128 result has no effect; they return the honest result. (3) The first wit_divUint128 call is now the division, so a replaced result of that call is a forged quotient and remainder, and it fails with the first failing message of the U128 form, for example Uint128: remainder error.

Validation

  • 1,010 math tests pass. The full suite passes: 3,206 passed, 48 skipped.
  • Only the four division circuits change (ZKIR v2 and v3).
  • Soundness: the witness quotient and remainder arrive as four 64-bit limbs with range checks, so every product and sum in the checks stays below 2^193, far below the field modulus, and nothing wraps. The checks then require b > 0, r < b and a = q * b + r as integers. Only one pair (q, r) satisfies these conditions, so an accepted result is the true quotient and remainder. A machine-checked proof confirms this.
  • Differential tests (98 circuits), against the source library: 26,094 equal results and 1,800 equal failures. Ten differences: the BUG-2 message fix of feat(math): add Pack, Bytes8 and Uint64 (2/13) #1126, and nine incorrect-witness scenarios. Seven replace the wit_divU128 result, which these forms no longer request, and now return the honest result. Two replace the first wit_divUint128 result, which is now the division, and now fail with Uint128: remainder error instead of Uint128: conversion invalid.
  • Real proofs: covered by the live run at the refactor(math): verify Uint128.mulChecked with native arithmetic (13/13) #1148 head, where the four division circuits are identical: 12 test files and 449 tests pass with real proofs.

Types of changes

  • Bugfix
  • New feature
  • Breaking change
  • Documentation update
  • Refactor (no API change)

PR Checklist

  • I have read the Contributing Guide
  • I have added tests that prove my fix is effective or that my feature works
  • I have added documentation for new methods or changes to existing method behavior
  • CI Workflows Are Passing

@coderabbitai

coderabbitai Bot commented Oct 15, 2026

Copy link
Copy Markdown
Contributor

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

The Uint<128> forms of div, rem, divRem and isMultiple call
wit_divUint128(a, b) once and check its result with native Uint
arithmetic: r < b, q_h * b <= 2^64 - 1, q * b <= 2^128 - 1 and
q * b + r == a. They no longer split a and b with toU128 and no longer
call wit_divU128 or _mul. The zero-divisor check stays before the
witness call. Rows (compiler 0.34.0, v2 / v3): div 3356 / 1017 to
845 / 287, rem 3356 / 1017 to 843 / 285, divRem 3269 / 1026 to
886 / 328, isMultiple 3233 / 992 to 850 / 290.

Every valid input keeps its result and its failure message. For each
witness quotient and remainder, the first failing check has the message
of the U128 form, also for the plan review MM-04 witness (10 / 2^65 with
quotient 2^64 - 1). Under D13, a wrong split can no longer make these
circuits fail with "Uint128: conversion invalid", and each makes one
witness call (10 before: 9 of wit_divUint128 and 1 of wit_divU128).

Tests: the unit and malicious-witness tests forge the division witness
of the new form; a dry spy shows one wit_divUint128(a, b) call; a
property test compares the failure message with the U128 form for
forged results; the relation tests forge every limb of the division
witness, and a constraint-removal test lists the five constraints that
other constraints imply.

MathRelationCounterexamples.test.ts keeps the _div preimages for the U128
forms. For the Uint<128> forms it has field preimages of their own
constraints: the range of a, the four limbs of the witness result, the
remainder check and the final equation (the audit A2 case 10 / 3 with
quotient 4 and remainder 1).

The coordinated split forgeries and the replay fallback tests of PR 5
apply to the U128 forms (divU128 and the others), where wit_divU128 runs
before the splits of _mul.
The Uint<128> forms of div, rem, divRem and isMultiple now make one
wit_divUint128 call and no wit_divU128 call. Update the witness table, the
rows table and the migration notes, and add the CHANGELOG line.
@pepebndc
pepebndc force-pushed the refactor/math-11-native-div branch from 999f24f to ba8b17e Compare October 15, 2026 09:25

This branch has not been deployed

No deployments
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.

1 participant