Skip to content

feat(contracts): optimize Uint64/Uint128 to 0.28.0, introduce Vector8, part 1/3 - #290

Merged
0xisk merged 13 commits into
mainfrom
refactor/upgrade-math-0.28.0-part1
Feb 11, 2026
Merged

feat(contracts): optimize Uint64/Uint128 to 0.28.0, introduce Vector8, part 1/3#290
0xisk merged 13 commits into
mainfrom
refactor/upgrade-math-0.28.0-part1

Conversation

@0xisk

@0xisk 0xisk commented Feb 7, 2026

Copy link
Copy Markdown
Member

Types of changes

What types of changes does your code introduce to OpenZeppelin Midnight Contracts?
Put an x in the boxes that apply

  • Bugfix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation Update (if none of the other choices apply)

Part of #279 (Part 1 of 2 of that main draft PR: #289 )

Note: This PR is part 1 of a larger upgrade that was split into two stacked PRs for easier review. Part 2 (coming soon) will add the 256-bit modules (Uint256, Bytes32, Vector32, Field255) that build on this foundation.

Upgrades Uint64/Uint128 to 0.28.0 API and introduces Vector8 as a foundational building block for efficient integer ↔ Vector conversions. Vector8 provides the core primitive (Vector<8, Uint<8>>Uint<64>) using pure arithmetic instead of expensive byte slicing, which will enable higher-level modules (Uint256, Field255) to split into 4x Uint64 limbs for efficient conversions in part 2.

Implements witness-optimized verification pattern: compute conversions off-chain, verify in circuit/on-chain (e.g., toVector(toUint64(input)) == input).

Changes:

  • Upgraded contracts: Uint64, Uint128 (to 0.28.0 API)
  • New contract: Vector8 (with arithmetic-based Vector ↔ Uint64 conversions)
  • New shared witness infrastructure: types, sqrt, div, and conversion helpers

Dependency Tree (Part 1 Scope)

graph TD
    subgraph "<b>Part 1: 8 / 64 / 128-bit Foundation</b>"
        Vector8
        Uint64
        Uint128
    end

    subgraph "<b>Part 2: Coming Soon</b>"
        Vector32[Vector32]
        Uint256[Uint256]
        Bytes32[Bytes32]
        Field255[Field255]
    end

    Vector8 --> Uint64
    Uint64 --> Uint128
    Uint64 -.-> Uint256
    Uint128 -.-> Uint256
    Uint128 -.-> Field255
    Uint256 -.-> Bytes32
    Uint256 -.-> Field255
    Bytes32 -.-> Field255

    style Vector32 fill:#f0f0f0,stroke:#999,stroke-dasharray: 5 5
    style Uint256 fill:#f0f0f0,stroke:#999,stroke-dasharray: 5 5
    style Bytes32 fill:#f0f0f0,stroke:#999,stroke-dasharray: 5 5
    style Field255 fill:#f0f0f0,stroke:#999,stroke-dasharray: 5 5
Loading

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 of new methods and any new behavior or changes to existing behavior
  • CI Workflows Are Passing

- Upgrade Uint64 and Uint128 compact modules to 0.28.0 API
- Add new Vector8 and Bytes8 compact modules with full test suites
- Extract shared witness infrastructure (types, sqrt, div, conversion helpers)
- Include mock contracts, simulators, and witnesses for all modules
@0xisk
0xisk requested review from a team as code owners February 7, 2026 15:29
@coderabbitai

coderabbitai Bot commented Feb 7, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Walkthrough

This PR introduces new byte and vector conversion modules (Bytes8, Vector8) for 8-byte little-endian operations, refactors witness naming convention across Uint128 and Uint64 from local suffixes to wit_ prefix, adds overflow-checked arithmetic operations (addChecked, mulChecked), and implements comprehensive test suites with mock simulators.

Changes

Cohort / File(s) Summary
New Bytes8 Module
contracts/src/math/Bytes8.compact, contracts/src/math/test/Bytes8.test.ts, contracts/src/math/test/mocks/Bytes8Simulator.ts, contracts/src/math/test/mocks/contracts/Bytes8.mock.compact, contracts/src/math/test/mocks/witnesses/Bytes8.ts
Adds new module for 8-byte little-endian conversions with circuits toUint64 and toVector. Includes complete test coverage and mock simulator with witness definitions.
New Vector8 Module
contracts/src/math/Vector8.compact, contracts/src/math/test/Vector8.test.ts, contracts/src/math/test/mocks/Vector8Simulator.ts, contracts/src/math/test/mocks/contracts/Vector8.mock.compact, contracts/src/math/test/mocks/witnesses/Vector8.ts
Introduces new module for vector-to-uint64/bytes conversions with corresponding tests and mock simulator. Provides bidirectional conversions with little-endian semantics.
Uint128 Refactoring
contracts/src/math/Uint128.compact, contracts/src/math/test/Uint128.test.ts, contracts/src/math/test/mocks/Uint128Simulator.ts, contracts/src/math/test/mocks/witnesses/Uint128.ts
Renames witness functions from `Locally` to `wit_` prefix convention (divU128Locally→wit_divU128, sqrtU128Locally→wit_sqrtU128). Updates internal arithmetic implementations and converts pure to impure circuits in mock simulator. Test updates reflect new naming.
Uint64 Enhancement
contracts/src/math/Uint64.compact, contracts/src/math/test/Uint64.test.ts, contracts/src/math/test/mocks/Uint64Simulator.ts, contracts/src/math/test/mocks/contracts/Uint64.mock.compact, contracts/src/math/test/mocks/witnesses/Uint64.ts
Adds overflow-checked operations (addChecked, mulChecked), renames witnesses to wit_* prefix, introduces toVector and toBytes conversions, updates mock circuits from pure to impure variants. Expands test coverage for new functionality.
Witness Function Implementations
contracts/src/math/witnesses/types.ts, contracts/src/math/witnesses/wit_divU128.ts, contracts/src/math/witnesses/wit_divUint128.ts, contracts/src/math/witnesses/wit_divUint64.ts, contracts/src/math/witnesses/wit_sqrtU128.ts, contracts/src/math/witnesses/wit_sqrtUint64.ts, contracts/src/math/witnesses/wit_uint64ToVector.ts
Centralizes witness implementations with new types.ts module defining U128, U256, DivResult types and conversion utilities. Implements individual witness functions for division, square root, and vector conversion operations.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested labels

enhancement, math-library, refactoring

Suggested reviewers

  • andrew-fleming
  • emnul

Poem

🐰 Hops through bytes and vectors bright,
Little-endian conversions done just right,
Witnesses renamed with wisdom's care,
Overflow checks prove we're fair!
Eight bytes dance in harmony's light.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title is partially related to the changeset—it mentions the main objective of upgrading Uint64/Uint128 to 0.28.0 and introducing Vector8, but it omits Bytes8, which is a significant addition in this PR. The title accurately reflects core upgrade goals but is incomplete.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor/upgrade-math-0.28.0-part1

Tip

Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord.


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

Consolidate scattered "Theoretical Description:" and "Mathematical Steps:"
sections into unified @remarks tags for cleaner documentation structure in
Uint64 and Uint128 modules.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@contracts/src/math/Bytes8.compact`:
- Line 21: The import { toUint64 } from "./Vector8" is unused in Bytes8.compact;
either remove that unused import line or change the Bytes8.compact circuit named
toUint64 to call the Vector8-provided conversion (e.g., use Vector8.toUint64 /
Vector8_toUint64 on the bytes cast to a Vector8) so the implementation matches
the documentation; update the import usage or delete the import accordingly and
ensure the toUint64 circuit in this file references the Vector8 symbol if you
choose the second option.
🧹 Nitpick comments (6)
contracts/src/math/witnesses/types.ts (2)

46-49: Consider masking high bits for defensive consistency.

If value exceeds 128 bits, the high field will contain more than 64 bits. While witnesses should receive valid inputs from circuits, masking both components would ensure well-formed U128 values.

♻️ Suggested defensive masking
 export const toU128 = (value: bigint): U128 => ({
   low: value & UINT64_MASK,
-  high: value >> 64n,
+  high: (value >> 64n) & UINT64_MASK,
 });

51-52: Minor: Redundant BigInt() calls.

Since value.high and value.low are already typed as bigint, the BigInt() wrappers are unnecessary.

♻️ Simplified version
 export const toBigint = (value: U128): bigint =>
-  (BigInt(value.high) << 64n) + BigInt(value.low);
+  (value.high << 64n) + value.low;
contracts/src/math/witnesses/wit_divUint64.ts (1)

9-16: Division by zero will throw an unhandled RangeError.

If divisor is 0n, the native bigint division throws RangeError: Division by zero. While the circuit should validate the divisor before calling this witness, an explicit check would provide a clearer error message for debugging.

🛡️ Suggested defensive check
 export const wit_divUint64 = (
   dividend: bigint,
   divisor: bigint,
 ): DivResultU64 => {
+  if (divisor === 0n) {
+    throw new Error('wit_divUint64: division by zero');
+  }
   const quotient = dividend / divisor;
   const remainder = dividend % divisor;
   return { quotient, remainder };
 };
contracts/src/math/witnesses/wit_divU128.ts (1)

9-18: Division by zero will throw if b is zero.

Same concern as wit_divUint64: if toBigint(b) returns 0n, division throws RangeError. Consider adding an explicit check for clearer error messages.

🛡️ Suggested defensive check
 export const wit_divU128 = (a: U128, b: U128): DivResultU128 => {
   const aValue = toBigint(a);
   const bValue = toBigint(b);
+  if (bValue === 0n) {
+    throw new Error('wit_divU128: division by zero');
+  }
   const quotient = aValue / bValue;
   const remainder = aValue - quotient * bValue;
   return {
     quotient: toU128(quotient),
     remainder: toU128(remainder),
   };
 };
contracts/src/math/witnesses/wit_divUint128.ts (1)

12-19: Division by zero will throw if b is 0n.

Consistent with the other division witnesses, consider adding an explicit zero check for clearer debugging.

🛡️ Suggested defensive check
 export const wit_divUint128 = (a: bigint, b: bigint): DivResultU128 => {
+  if (b === 0n) {
+    throw new Error('wit_divUint128: division by zero');
+  }
   const quotient = a / b;
   const remainder = a - quotient * b;
   return {
     quotient: toU128(quotient),
     remainder: toU128(remainder),
   };
 };
contracts/src/math/test/Uint64.test.ts (1)

333-341: Remove duplicate divRem remainder test.

Line 333-341 repeats the remainder ≥ divisor case already covered at Line 313-321; consider dropping to reduce redundancy.

🧹 Suggested cleanup
-    test('should fail when remainder >= divisor (duplicate)', () => {
-      uint64Simulator.overrideWitness('wit_divUint64', (context) => [
-        context.privateState,
-        { quotient: 1n, remainder: 10n },
-      ]);
-      expect(() => uint64Simulator.divRem(10n, 5n)).toThrow(
-        'failed assert: Math: remainder error',
-      );
-    });

Comment thread contracts/src/math/Bytes8.compact Outdated
0xisk added 3 commits February 7, 2026 16:46
Update @circuitInfo annotations across all Part 1 math modules with actual
k and rows values from compiled mock contracts:

- Uint64: 18 circuits updated (MAX constants, arithmetic, division, sqrt, etc.)
- Uint128: 42 circuits updated (comparisons, arithmetic, division, sqrt, etc.)
- Vector8: 2 circuits updated (toUint64, toBytes)
- Bytes8: 2 circuits updated (toUint64, toVector)

All values verified by compiling corresponding .mock.compact test contracts.
Bytes8 is a leaf node with no downstream consumers — its two circuits
are trivial casts (bytes as Uint<64>, bytes as Vector<8, Uint<8>>)
that any consumer can do inline. Removing to keep the PR focused.
@0xisk 0xisk changed the title feat: upgrade Uint64/Uint128 to 0.28.0, introduce Vector8 and Bytes8 feat(contracts): upgrade Uint64/Uint128 to 0.28.0, introduce Vector8 and Bytes8 Feb 7, 2026
@0xisk 0xisk changed the title feat(contracts): upgrade Uint64/Uint128 to 0.28.0, introduce Vector8 and Bytes8 feat(contracts): optimize Uint64/Uint128 to 0.28.0, introduce Vector8 and Bytes8 Feb 7, 2026
@0xisk

0xisk commented Feb 7, 2026

Copy link
Copy Markdown
Member Author

🐰 Hops through bytes and vectors bright,
Little-endian conversions done just right,
Witnesses renamed with wisdom's care,
Overflow checks prove we're fair!
Eight bytes dance in harmony's light. ✨

That's funny actually 😅

@0xisk 0xisk changed the title feat(contracts): optimize Uint64/Uint128 to 0.28.0, introduce Vector8 and Bytes8 feat(contracts): optimize Uint64/Uint128 to 0.28.0, introduce Vector8 Feb 9, 2026
@0xisk

0xisk commented Feb 9, 2026

Copy link
Copy Markdown
Member Author

UPDATE: as Part2 PR was also big (~3K lines of changes) I split it into two parts, so we have part2 #291, then part3 after wards #292

@0xisk 0xisk changed the title feat(contracts): optimize Uint64/Uint128 to 0.28.0, introduce Vector8 feat(contracts): optimize Uint64/Uint128 to 0.28.0, introduce Vector8, part 1 Feb 9, 2026
@0xisk 0xisk changed the title feat(contracts): optimize Uint64/Uint128 to 0.28.0, introduce Vector8, part 1 feat(contracts): optimize Uint64/Uint128 to 0.28.0, introduce Vector8, part 1/3 Feb 9, 2026

@andrew-fleming andrew-fleming left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Looking good, @0xisk! Left some comments

Comment thread .github/workflows/config/typos.toml
Comment thread contracts/src/math/test/mocks/contracts/Uint128.mock.compact Outdated
Comment thread contracts/src/math/test/mocks/witnesses/Uint128.ts Outdated
Comment thread contracts/src/math/test/mocks/Uint64Simulator.ts Outdated
Comment thread contracts/src/math/test/Uint64.test.ts Outdated
Comment thread contracts/src/math/Uint64.compact Outdated
Comment thread contracts/src/math/Uint128.compact Outdated
Comment thread contracts/src/math/Uint128.compact Outdated
Comment thread contracts/src/math/Uint128.compact Outdated
Comment thread contracts/src/math/test/Uint128.test.ts Outdated
0xisk and others added 8 commits February 10, 2026 11:21
Co-authored-by: Andrew Fleming <fleming.andrew@protonmail.com>
Signed-off-by: 0xisk <0xisk@proton.me>
Co-authored-by: Andrew Fleming <fleming.andrew@protonmail.com>
Signed-off-by: 0xisk <0xisk@proton.me>
Remove 4 unnecessary assertions from Uint128 module that add no value:
- Removed 3 mathematically unreachable defensive checks in _mul and
  _sqrt circuits that can never fail with valid inputs
- Removed 1 redundant division-by-zero check in _isMultiple already
  performed by _div

Updated corresponding @throws documentation and circuit info annotations.
Co-authored-by: Andrew Fleming <fleming.andrew@protonmail.com>
Signed-off-by: 0xisk <0xisk@proton.me>

@andrew-fleming andrew-fleming left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nice work on the improvements 🚀 LGTM!

Comment on lines +38 to +53
/**
* @description Factory function creating witness implementations for Uint128 module operations.
*/
export const Uint128Witnesses = (): Witnesses<Uint128PrivateState> => ({
wit_sqrtU128(_context, radicand) {
return [{}, wit_sqrtU128(radicand)];
},

wit_divU128(_context, a, b) {
return [{}, wit_divU128(a, b)];
},

wit_divUint128(_context, a, b) {
return [{}, wit_divUint128(a, b)];
},
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

👍

Comment thread contracts/src/math/Uint128.compact Outdated
@0xisk
0xisk merged commit 258a941 into main Feb 11, 2026
12 checks passed
@0xisk
0xisk deleted the refactor/upgrade-math-0.28.0-part1 branch February 11, 2026 13:52
@emnul
emnul restored the refactor/upgrade-math-0.28.0-part1 branch February 17, 2026 17:56

@emnul emnul left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

GG

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.

3 participants