Skip to content

perf: fold constant byte operations in std/math/uints - #1796

Merged
ivokub merged 2 commits into
Consensys-Incorporated:masterfrom
colll78:feat/uints-fold-constant-byte-ops
Sep 30, 2026
Merged

ivokub merged 2 commits into
Consensys-Incorporated:masterfrom
colll78:feat/uints-fold-constant-byte-ops

Conversation

@colll78

@colll78 colll78 commented Jul 15, 2026 •

Copy link
Copy Markdown
Contributor

Description

When both operands of a Bytes.And/Or/Xor byte operation are compile-time constants, evaluate the operation natively at compile time instead of emitting a lookup query. The fold is applied per query in the variadic chain, so constant prefixes and constant–constant pairs fold while any witness operand keeps the exact existing lookup path; BinaryField (U32/U64) operations benefit through the shared twoArgFn.

Soundness: both operands of a folded query are already width-checked — circuit inputs by enforceWidth (which panics on constants wider than 8 bits), intermediates because they come either from a lookup response or from a previous fold that produced a uint8. The fold additionally guards on BitLen() > 8 and falls back to the lookup for anything wider, so a constant can never be silently truncated. An unrecognized table also falls back to the lookup path.

Motivation: byte-oriented circuits routinely mix constants (padding bytes, IVs, domain separators, masks) with witness bytes, and each such constant pair currently costs a lookup query plus its share of the log-derivative table commitment. In a production circuit of ours dominated by SHA-512/HMAC this fold removed ~38,000 R1CS constraints; on gnark's own std/hash/sha2 gadget see the numbers below.

Type of change

  • New feature (non-breaking change which adds functionality)

How has this been tested?

  • New TestByteOpConstantFoldMixed: for each of And/Or/Xor, a chain mixing a folded constant pair, a witness operand, and a trailing constant queried against a non-constant intermediate; valid and invalid assignments via assert.CheckCircuit (Groth16 + PLONK, bn254 + bls12-381).
  • New TestByteOpConstantFoldAddsNoConstraints: compiles a constant-only And/Or/Xor circuit and asserts its R1CS constraint count equals a baseline circuit that only instantiates the tables.
  • Full existing suites pass: go test ./std/math/uints/ ./std/hash/sha2/... ./std/hash/sha3/...

How has this been benchmarked?

  • Compiling the std/hash/sha2 gadget over a 64-byte message on bn254 (Linux x86-64): R1CS 184,199 → 183,992 constraints; SCS 560,641 → 559,628 constraints.

Checklist:

  • I have performed a self-review of my code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • I have added tests that prove my fix is effective or that my feature works
  • I did not modify files generated from templates
  • New and existing unit tests pass locally with my changes
  • Any dependent changes have been merged and published in downstream modules

Note

Medium Risk
Touches core bitwise byte logic in circuits; behavior is guarded by width checks and lookup fallback, with new tests, but incorrect folding would affect proof soundness for hash/crypto gadgets.

Overview
Bytes.And / Or / Xor now route each pairwise step through queryOrFold: when both operands are compile-time constants within 8 bits, the op is evaluated natively instead of emitting a log-derivative lookup. Variadic chains still use lookups wherever a witness or wide constant appears; unknown tables fall back to tbl.Query.

BinaryField (U32/U64) picks this up automatically via the shared twoArgFn.

New tests cover mixed constant/witness chains (valid and invalid circuits) and assert constant-only byte ops add no R1CS constraints beyond instantiating the lookup tables.

Reviewed by Cursor Bugbot for commit 926e18b. Bugbot is set up for automated code reviews on this repo. Configure here.

colll78 and others added 2 commits July 14, 2026 21:45
When both operands of an And/Or/Xor byte operation are compile-time
constants, evaluate the operation natively at compile time instead of
emitting a lookup query. The fold applies per query in the variadic
chain, so constant prefixes and constant-constant pairs fold while any
witness operand keeps the lookup path. Both operands are already
width-checked, and the fold additionally guards on BitLen so an
out-of-range constant falls back to the lookup instead of being
truncated.

Circuits over byte-oriented gadgets commonly mix constants (padding,
IVs, domain separators) with witness bytes; those queries and their
log-derivative table costs disappear. Compiling the sha2 gadget over a
64-byte message: r1cs 184,199 -> 183,992 constraints, scs 560,641 ->
559,628 constraints.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@ivokub
ivokub requested a review from a team as a code owner September 30, 2026 08:54
@cursor

cursor Bot commented Sep 30, 2026

Copy link
Copy Markdown

Bugbot needs on-demand usage enabled

Bugbot uses usage-based billing for this team and requires on-demand usage to be enabled.

A team admin can enable on-demand usage in the Cursor dashboard.

@ivokub ivokub 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.

Thanks for the contribution! I merged master as we had a security fix incoming there. There were some conflicts, but I also cleaned it up.

I performed independent review and it is sound and complete.

@ivokub
ivokub merged commit d089cef into Consensys-Incorporated:master Sep 30, 2026
15 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