Skip to content

chore: honk verifier opt + fix review#20547

Open
Maddiaa0 wants to merge 28 commits intonextfrom
md/honk-golf
Open

chore: honk verifier opt + fix review#20547
Maddiaa0 wants to merge 28 commits intonextfrom
md/honk-golf

Conversation

@Maddiaa0
Copy link
Member

@Maddiaa0 Maddiaa0 commented Feb 16, 2026

Overview

This has been submitted for audit already, so I've attempted to make the changes / optimizations as small as possible. I came across these while reading through it again for the zk verifier.

Commits are squashed to please CI - however ive pushed them to a mirror so they still exist within the repository

  • removing a scalar mul which was being multiplied by 1
  • LAGRANGE_FIRST is always (1,2), constant_term_acc is always multiplied by (1,2) so add the scalars together
  • the remaining are batching all inversions into the same modexp

chore: remove redundant scalar mul

chore: combine constant_term_acc and LAGRANGE_FIRST scalar mul

chore: batch public inputs inversion

chore: deduplicate gemini denominator and document memory layout

chore: merge all inversions into single modexp
@Maddiaa0
Copy link
Member Author

@copilot can you remove the commit of barretenberg/sol/src/honk/instance/other.sol

Copy link

Copilot AI commented Feb 17, 2026

@Maddiaa0 I've opened a new pull request, #20610, to work on those changes. Once the pull request is ready, I'll request review from you.

Maddiaa0 and others added 3 commits February 18, 2026 09:56
### 🧾 Audit Context

Minor optimizations identified during zk verifier review of
already-audited ultra honk verifier code.

### 🛠️ Changes Made

- Removed scalar multiplication by 1 (no-op elimination)
- Consolidated LAGRANGE_FIRST (1,2) constant term accumulation by adding
scalars directly
- Batched all inversions into single modexp operation
- Removed unintended `barretenberg/sol/src/honk/instance/other.sol` file

No functional changes. Purely optimization-focused refactoring. No
circuit changes.

### ✅ Checklist

- [x] Audited all methods of the relevant module/class
- [x] Audited the interface of the module/class with other (relevant)
components
- [ ] Documented existing functionality and any changes made (as per
Doxygen requirements)
- [x] Resolved and/or closed all issues/TODOs pertaining to the audited
files
- [x] Confirmed and documented any security or other issues found (if
applicable)
- [x] Verified that tests cover all critical paths (and added tests if
necessary)
- [ ] Updated audit tracking for the files audited (check the start of
each file you audited)

### 📌 Notes for Reviewers

Changes kept minimal as code already submitted for audit. See individual
commits for isolated changes:
- 699c7e2: scalar mul removal
- 6f2c350: LAGRANGE_FIRST consolidation  
- 05217b8, f1a5830: inversion batching

<!-- START COPILOT CODING AGENT TIPS -->
---

💡 You can make Copilot smarter by setting up custom instructions,
customizing its development environment and configuring Model Context
Protocol (MCP) servers. Learn more [Copilot coding agent
tips](https://gh.io/copilot-coding-agent-tips) in the docs.

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com>
@Maddiaa0 Maddiaa0 changed the base branch from next to md/fix-verifier February 18, 2026 10:01
@Maddiaa0 Maddiaa0 marked this pull request as ready for review February 18, 2026 10:03
@Maddiaa0 Maddiaa0 requested a review from LHerskind February 18, 2026 10:03
for (int i = 0; i < BARYCENTRIC_DOMAIN_SIZE; ++i) {
print_fr(bary_pointer, "BARYCENTRIC_LAGRANGE_DENOMINATOR_" + std::to_string(i) + "_LOC");
bary_pointer += 32;
print_fr(pointer, "BARYCENTRIC_LAGRANGE_DENOMINATOR_" + std::to_string(i) + "_LOC");
Copy link
Member Author

@Maddiaa0 Maddiaa0 Feb 18, 2026

Choose a reason for hiding this comment

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

These were starting from "scratch space", but I've leaned towards not re-using allocations due to memory having a trivial gas overhead

// INVERSIONS
print_header_centered("SHPLEMINI - RUNTIME MEMORY - INVERSIONS");

print_fr(pointer, "GEMINI_R_INV_LOC");
Copy link
Member Author

Choose a reason for hiding this comment

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

stored in memory now as the inversion is performed before usage

out << "// LOG_N challenge pow minus u\n";
for (int i = 0; i < log_n; ++i) {
print_fr(pointer, "INVERTED_CHALLENEGE_POW_MINUS_U_" + std::to_string(i) + "_LOC");
print_fr(pointer, "INVERTED_CHALLENGE_POW_MINUS_U_" + std::to_string(i) + "_LOC");
Copy link
Member Author

Choose a reason for hiding this comment

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

typo fix


out << "\n";
print_fr(pointer, "LATER_SCRATCH_SPACE");
for (int i = 0; i < 8 * log_n; ++i) {
Copy link
Member Author

Choose a reason for hiding this comment

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

Previously these were located in LATER_SCRATCH_SPACE, i thought it would be better to explicitly provide their memory locations in the layout at the top of the file.

Hidden allocations could be a source of footguns when refactoring

…ract.hpp

previously we were writing to an offset of 0x20 which was not expected - how this lines up correctly
Base automatically changed from md/fix-verifier to next February 19, 2026 15:22
Copy link
Contributor

@LHerskind LHerskind left a comment

Choose a reason for hiding this comment

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

Using my 🥜 brain, but think there are a few unused bits than can be cut off. And then I am just curious about the first_lagrange really.

mulmod(mload(PUBLIC_INPUTS_DELTA_NUMERATOR_CHALLENGE), pi_delta_inv, p)
)
}

// Normalise as last loop will have incremented the offset
bary_centric_inverses_off := sub(bary_centric_inverses_off, 0x20)
for {} gt(bary_centric_inverses_off, SUM_U_CHALLENGE_14) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Don't this run for too long?

SUM_U_CHALLENGE_14 = 0x3cc0 and bary_centric_inversses_off = 0x4ce0, but the BARYCENTRIC_DENOMINATOR_INVERSES_0_0_LOC = 0x3de0 so it unds up looping more than planned and overwriting BARYCENTRIC_LAGRANGE_DENOMINATOR_7..0 (8 excess runs of the body). The accumulator and other values don't seem to be used afterwards, so it does not break currently, but just writing more than needed?

Copy link
Member Author

Choose a reason for hiding this comment

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

unbelievable catch, before this pr ( or maybe the last one ) barycentric_lagrange_denominator was in scratch space ( starting at 0x100 ). so this was correct, this pr updates such that the lagrange_denominators are stored in the slab with everything else. But does not update this pointer

Copy link
Member Author

Choose a reason for hiding this comment

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

fix is replacing SUM_U_CHALLENGE_14 with BARYCENTRIC_LAGRANGE_DENOMINATOR_7

Copy link
Member Author

Choose a reason for hiding this comment

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

@@ -4203,8 +4553,11 @@ contract BlakeOptHonkVerifier is IVerifier {
)

// Accumulator = accumulator + scalar[27] * vk[26]
// optimization - Lagrange first is always G - (1,2)
Copy link
Contributor

Choose a reason for hiding this comment

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

Are we absolutely sure that LAGRANGE_FIRST is always (1, 2)? Because then we should do that in both the optimized and base, to ensure that it is not different between the two variants and that will break together if it changes.

Copy link
Member Author

@Maddiaa0 Maddiaa0 Feb 20, 2026

Choose a reason for hiding this comment

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

yeah it is - its enforced in the vk

let neg_inverted_denominator := mload(NEG_INVERTED_DENOM_0_LOC)
let shplonk_nu := mload(SHPLONK_NU_CHALLENGE)

unshifted_scalar := addmod(pos_inverted_denominator, mulmod(shplonk_nu, neg_inverted_denominator, p), p)
Copy link
Contributor

Choose a reason for hiding this comment

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

If we are onlysetting it here, we mightn ot need the separate let unshifted_scaler := 0 and then almost instantly setting it?

Copy link
Member Author

Choose a reason for hiding this comment

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

ah this was getting around a stack too deep

}
/// {{ UNROLL_SECTION_END ACCUMULATE_INVERSES }}

// Invert all elements (barycentric + PI delta + shplemini) as a single batch
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we apply similar checks as in Fr.sol to avoid trying to invert a 0 value? If there is one 0 we would end up fully making all the accumulator 0. It should be caught by other parts of the verification, but 🤷

Copy link
Member Author

Choose a reason for hiding this comment

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

we could, but if we invert a zero everything will blow up after

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not sure you read this value, seems to start at BATCH_SCALAR_1_LOC onwards.

Copy link
Member Author

Choose a reason for hiding this comment

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

yep its now unused

uint256 internal constant BATCH_EVALUATION_ACCUMULATOR_INVERSION_13_LOC = 0x5f80;
uint256 internal constant BATCH_EVALUATION_ACCUMULATOR_INVERSION_14_LOC = 0x5fa0;

uint256 internal constant BATCHED_EVALUATION_LOC = 0x5fc0;
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think you actually use this one. It looks like you store it in memory, but then you use the stack item batched_evaluation right after and not the memory variant.

@Maddiaa0 Maddiaa0 changed the title chore: lil bit of golfing on the ultra honk verifier chore: honk verifier opt + fix review Mar 10, 2026
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