Skip to content

perf: fold constant table column into denominator in logderivarg - #1804

Open
Fable95 wants to merge 1 commit into
Consensys:masterfrom
Fable95:logderiv-fold
Open

perf: fold constant table column into denominator in logderivarg#1804
Fable95 wants to merge 1 commit into
Consensys:masterfrom
Fable95:logderiv-fold

Conversation

@Fable95

@Fable95 Fable95 commented Aug 10, 2026

Copy link
Copy Markdown

Description

Optimizes PLONK (SCS) constraint count for constant multi-column lookup tables by folding
the row's random linear combination directly into the denominator's subtraction, on the table
side of the log-derivative argument.

In the two-column case, the LogUp verification equation sum count(f,S)/(x-f) == sum 1/(x-s), currently requires 4 PLONK gates per table entry:

  1. Add: acc = 1*i + rho*T_i (1 gate inside randLinearCombination)
  2. Sub: denom = challenge - acc (1 gate)
  3. DivUnchecked: quotient * denom = exps[i] (1 gate)
  4. Add: lp += quotient (1 gate)

The constant term 1*i can be folded into the subsequent subtraction by removing the call to randLinearCombination, we can get the following three constraints:

  1. Sub: denom = challenge - rho*T_i - i (1 gate: a constraint in two variables challenge and rho)
  2. DivUnchecked: quotient * denom = exps[i] (1 gate)
  3. Add: lp += quotient (1 gate)

This approach saves one gate for any column width (in case of a one-to-many LUT). For each row, we can first compute the randomized term_j = rho_j*T_{i,j} for free since T_{i,j} is public. A subsequent api.Sub(challenge, terms[0], terms[1:]...) will produce (nbRow - 1) gates

Saving: len(table) - 1 PLONK gates per circuit and R1CS is unaffected.

Security: The underlying argument is not affected by the fold. The constant i is moved from its own addition gate into the constant term of the subsequent subtraction gate.

Type of change

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

How has this been tested?

  • go test ./std/internal/logderivarg/ -count=1
  • go test ./std/lookup/logderivlookup/ -count=1: TestLookup, Example
  • go test ./std/rangecheck/ -count=1: unchanged path, guards against regression
  • go test ./std/compress/... -count=1: constant-table caller
  • SCS gate-count assertion for one-to-many tables, so the count cannot
    silently regress (invisible to correctness tests)

How has this been benchmarked?

Per table entry, table side (nbRow == 2, measured in Build)

field R1CS old R1CS new PLONK old PLONK new Δ PLONK
BN254 1 1 5 4 -1
BLS12-381 1 1 5 4 -1
KoalaBear 17 17 55 55 0

Optimization works for large fields, while small fields are not impacted.

Downstream circuit

Circuit table old new Δ %
Custom Skyscraper permutation, 16-bit-word Bar S-box (BLS12-381) 2^16 329,390 263,855 -2^{16}-1 = -65,535 -19.9%

The test suggests the claimed improvement holds for any classical (1-to-1) LUT use case.

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
  • golangci-lint does not output errors locally
  • New and existing unit tests pass locally with my changes
  • Any dependent changes have been merged and published in downstream modules

Note

Cursor Bugbot is generating a summary for commit bfdb931. Configure here.

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