Conversation
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
f1a5830 to
965d231
Compare
|
@copilot can you remove the commit of barretenberg/sol/src/honk/instance/other.sol |
### 🧾 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>
| 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"); |
There was a problem hiding this comment.
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"); |
There was a problem hiding this comment.
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"); |
|
|
||
| out << "\n"; | ||
| print_fr(pointer, "LATER_SCRATCH_SPACE"); | ||
| for (int i = 0; i < 8 * log_n; ++i) { |
There was a problem hiding this comment.
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
19451c7 to
ed81ff5
Compare
…ract.hpp previously we were writing to an offset of 0x20 which was not expected - how this lines up correctly
LHerskind
left a comment
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
fix is replacing SUM_U_CHALLENGE_14 with BARYCENTRIC_LAGRANGE_DENOMINATOR_7
There was a problem hiding this comment.
| @@ -4203,8 +4553,11 @@ contract BlakeOptHonkVerifier is IVerifier { | |||
| ) | |||
|
|
|||
| // Accumulator = accumulator + scalar[27] * vk[26] | |||
| // optimization - Lagrange first is always G - (1,2) | |||
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
If we are onlysetting it here, we mightn ot need the separate let unshifted_scaler := 0 and then almost instantly setting it?
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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 🤷
There was a problem hiding this comment.
we could, but if we invert a zero everything will blow up after
There was a problem hiding this comment.
I'm not sure you read this value, seems to start at BATCH_SCALAR_1_LOC onwards.
| 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; |
There was a problem hiding this comment.
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.
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