perf: fold constant byte operations in std/math/uints - #1796
Open
colll78 wants to merge 1 commit into
Open
Conversation
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
When both operands of a
Bytes.And/Or/Xorbyte 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 sharedtwoArgFn.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 auint8. The fold additionally guards onBitLen() > 8and 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/sha2gadget see the numbers below.Type of change
How has this been tested?
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 viaassert.CheckCircuit(Groth16 + PLONK, bn254 + bls12-381).TestByteOpConstantFoldAddsNoConstraints: compiles a constant-only And/Or/Xor circuit and asserts its R1CS constraint count equals a baseline circuit that only instantiates the tables.go test ./std/math/uints/ ./std/hash/sha2/... ./std/hash/sha3/...How has this been benchmarked?
std/hash/sha2gadget over a 64-byte message on bn254 (Linux x86-64): R1CS 184,199 → 183,992 constraints; SCS 560,641 → 559,628 constraints.Checklist:
golangci-lintdoes not output errors locally (not installed locally;go vetandgofmtare clean)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/Xornow route each pairwise step throughqueryOrFold: 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 totbl.Query.BinaryField(U32/U64) picks this up automatically via the sharedtwoArgFn.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.