Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
honk_optimized_common.hpp
Go to the documentation of this file.
1// === AUDIT STATUS ===
2// internal: { status: Planned, auditors: [], commit: }
3// external_1: { status: not started, auditors: [], commit: }
4// external_2: { status: not started, auditors: [], commit: }
5// =====================
6
7#pragma once
10#include <sstream>
11#include <string>
12#include <type_traits>
13#include <vector>
14
15// Shared helpers for honk_optimized_contract.hpp and honk_zk_optimized_contract.hpp.
16// Both optimized verifier code generators share the same field-to-hex conversion,
17// integer-to-hex conversion, unroll section generation, and template replacement logic.
18
19template <typename Field> std::string field_to_hex(const Field& f)
20{
21 std::ostringstream os;
22 os << f;
23 return os.str();
24}
25
26// Emit the (x, y) coordinates of a G1 commitment as canonical 32-byte hex strings,
27// routing through U256Codec so points at infinity collapse to the EIP-196 identity (0, 0).
28// Reading commitment.x / commitment.y directly is unsafe: when is_point_at_infinity is set,
29// the affine struct stores a sentinel (modulus) in x and leaves y untouched, so the raw
30// coordinates do not lie on the curve and the EVM ecMul/ecAdd precompiles reject them.
31template <typename Commitment> std::pair<std::string, std::string> g1_to_xy_hex(const Commitment& point)
32{
33 const auto coords = bb::U256Codec::template serialize_to_fields<std::remove_cvref_t<Commitment>>(point);
34 std::ostringstream x_os;
35 std::ostringstream y_os;
36 x_os << coords[0];
37 y_os << coords[1];
38 return { x_os.str(), y_os.str() };
39}
40
41inline std::string int_to_hex(size_t i)
42{
43 std::ostringstream os;
44 os << "0x" << std::hex << i;
45 return os.str();
46}
47
48// Configuration for unroll section generation that varies between ZK and non-ZK verifiers.
50 int batch_scalar_offset; // 37 (non-ZK) or 38 (ZK)
51};
52
53// Generate the Solidity assembly code for a given unroll section.
54// The section_name determines which code pattern to emit; the config parameterizes
55// the differences between ZK and non-ZK verifiers.
56inline std::string generate_unroll_section(const std::string& section_name, int log_n, const UnrollConfig& config)
57{
58 std::ostringstream code;
59
60 if (section_name == "POWERS_OF_EVALUATION_COMPUTATION") {
61 for (int i = 1; i < log_n; ++i) {
62 code << " cache := mulmod(cache, cache, p)\n";
63 code << " mstore(POWERS_OF_EVALUATION_CHALLENGE_" << i << "_LOC, cache)\n";
64 }
65 } else if (section_name == "ACCUMULATE_INVERSES") {
66 // Generate INVERTED_CHALLENGE_POW_MINUS_U accumulations
67 int temp_idx = 0;
68 for (int i = 0; i < log_n; ++i) {
69 code << " // INVERTED_CHALLENGE_POW_MINUS_U_" << i << "\n";
70 code << " {\n";
71 code << " let u := mload(SUM_U_CHALLENGE_" << i << ")\n";
72 code << " let challPow := mload(POWERS_OF_EVALUATION_CHALLENGE_" << i
73 << "_LOC)\n";
74 code << " let val := addmod(mulmod(challPow, addmod(1, sub(p, u), p), p), u, "
75 "p)\n";
76 code << " mstore(INVERTED_CHALLENGE_POW_MINUS_U_" << i << "_LOC, val)\n";
77 code << " mstore(TEMP_" << temp_idx << "_LOC, accumulator)\n";
78 code << " accumulator := mulmod(accumulator, val, p)\n";
79 code << " }\n";
80 temp_idx++;
81 }
82
83 code << "\n // Accumulate pos inverted denom\n";
84 code << " // Elements LOG_N+1..2*LOG_N: POS_INVERTED_DENOM\n";
85 code << " let eval_challenge := mload(SHPLONK_Z_CHALLENGE)\n";
86
87 for (int i = 0; i < log_n; ++i) {
88 code << " // POS_INVERTED_DENOM_" << i << "\n";
89 code << " {\n";
90 code << " let val := addmod(eval_challenge, sub(p, "
91 "mload(POWERS_OF_EVALUATION_CHALLENGE_"
92 << i << "_LOC)) , p)\n";
93 code << " mstore(POS_INVERTED_DENOM_" << i << "_LOC, val)\n";
94 code << " mstore(TEMP_" << temp_idx << "_LOC, accumulator)\n";
95 code << " accumulator := mulmod(accumulator, val, p)\n";
96 code << " }\n";
97 temp_idx++;
98 }
99
100 code << "\n // Accumulate neg inverted denom\n";
101 code << " // Elements 2*LOG_N+1..3*LOG_N: NEG_INVERTED_DENOM\n";
102 for (int i = 0; i < log_n; ++i) {
103 code << " {\n";
104 code << " let val := addmod(eval_challenge, "
105 "mload(POWERS_OF_EVALUATION_CHALLENGE_"
106 << i << "_LOC), p)\n";
107 code << " mstore(NEG_INVERTED_DENOM_" << i << "_LOC, val)\n";
108 code << " mstore(TEMP_" << temp_idx << "_LOC, accumulator)\n";
109 code << " accumulator := mulmod(accumulator, val, p)\n";
110 code << " }\n";
111 temp_idx++;
112 }
113 } else if (section_name == "COLLECT_INVERSES") {
114 int temp_idx = 3 * log_n - 1;
115
116 // Process NEG_INVERTED_DENOM in reverse order
117 code << " // i = " << log_n << "\n";
118 code << " // NEG_INVERTED_DENOM (LOG_N elements, reverse) -- last group appended\n";
119 for (int i = log_n - 1; i >= 0; --i) {
120 code << " {\n";
121 code << " let tmp := mulmod(accumulator, mload(TEMP_" << temp_idx
122 << "_LOC), p)\n";
123 code << " accumulator := mulmod(accumulator, mload(NEG_INVERTED_DENOM_" << i
124 << "_LOC), p)\n";
125 code << " mstore(NEG_INVERTED_DENOM_" << i << "_LOC, tmp)\n";
126 code << " }\n";
127 if (i > 0) {
128 code << " // i = " << i << "\n";
129 }
130 temp_idx--;
131 }
132
133 code << "\n // Unrolled for LOG_N = " << log_n << "\n";
134 code << " // i = " << log_n << "\n";
135
136 // Process POS_INVERTED_DENOM in reverse order
137 for (int i = log_n - 1; i >= 0; --i) {
138 code << " {\n";
139 code << " let tmp := mulmod(accumulator, mload(TEMP_" << temp_idx << "_LOC), p)\n";
140 code << " accumulator := mulmod(accumulator, mload(POS_INVERTED_DENOM_" << i
141 << "_LOC), p)\n";
142 code << " mstore(POS_INVERTED_DENOM_" << i << "_LOC, tmp)\n";
143 code << " }\n";
144 if (i > 0) {
145 code << " // i = " << i << "\n";
146 }
147 temp_idx--;
148 }
149
150 code << "\n // i = " << log_n << "\n";
151
152 // Process INVERTED_CHALLENGE_POW_MINUS_U in reverse order
153 for (int i = log_n - 1; i >= 0; --i) {
154 code << " {\n";
155 code << " let tmp := mulmod(accumulator, mload(TEMP_" << temp_idx << "_LOC), p)\n";
156 code << " accumulator := mulmod(accumulator, mload(INVERTED_CHALLENGE_POW_MINUS_U_" << i
157 << "_LOC), p)\n";
158 code << " mstore(INVERTED_CHALLENGE_POW_MINUS_U_" << i << "_LOC, tmp)\n";
159 code << " }\n";
160 if (i > 0) {
161 code << " // i = " << i << "\n";
162 }
163 temp_idx--;
164 }
165 } else if (section_name == "ACCUMULATE_GEMINI_FOLD_UNIVARIATE") {
166 // Generate GEMINI_FOLD_UNIVARIATE accumulations (log_n - 1 folding commitments)
167 for (int i = 0; i < log_n - 1; ++i) {
168 code << " mcopy(G1_LOCATION, GEMINI_FOLD_UNIVARIATE_" << i << "_X_LOC, 0x40)\n";
169 code << " mstore(SCALAR_LOCATION, mload(BATCH_SCALAR_"
170 << (config.batch_scalar_offset + i) << "_LOC))\n";
171 code << " precomp_success_flag :=\n";
172 code << " and(precomp_success_flag, staticcall(gas(), 7, G1_LOCATION, 0x60, "
173 "ACCUMULATOR_2, 0x40))\n";
174 code << " precomp_success_flag :=\n";
175 code << " and(precomp_success_flag, staticcall(gas(), 6, ACCUMULATOR, 0x80, "
176 "ACCUMULATOR, 0x40))\n";
177 if (i < log_n - 2) {
178 code << "\n";
179 }
180 }
181 }
182
183 return code.str();
184}
185
186// Replace a single UNROLL_SECTION block in the template string.
187// Finds the START/END markers for the given section_name, generates the code,
188// and splices it into the template.
189inline void replace_unroll_section(std::string& template_str,
190 const std::string& section_name,
191 int log_n,
192 const UnrollConfig& config)
193{
194 std::string start_marker = "/// {{ UNROLL_SECTION_START " + section_name + " }}";
195 std::string end_marker = "/// {{ UNROLL_SECTION_END " + section_name + " }}";
196 std::string::size_type start_pos = template_str.find(start_marker);
197 std::string::size_type end_pos = template_str.find(end_marker);
198
199 // Sanity check - much better to fail now if an expected template is missing - check whitespace matches exactly
200 if (start_pos == std::string::npos || end_pos == std::string::npos) {
201 info("Missing unroll markers for section: " + section_name);
202 std::abort();
203 }
204
205 if (start_pos != std::string::npos && end_pos != std::string::npos) {
206 std::string::size_type start_line_end = template_str.find("\n", start_pos);
207 std::string generated_code = generate_unroll_section(section_name, log_n, config);
208 template_str = template_str.substr(0, start_line_end + 1) + generated_code + template_str.substr(end_pos);
209 }
210}
211
212// Configuration for memory layout generation that varies between ZK and non-ZK verifiers.
214 int batched_relation_partial_length; // 8 (non-ZK) or 9 (ZK)
215 int barycentric_domain_size; // 8 (non-ZK) or 9 (ZK)
216 bool is_zk; // controls all ZK-specific conditional blocks
217};
218
219// Generate the Solidity memory layout constants for the optimized verifier.
220// This is shared between ZK and non-ZK verifiers; the config parameterizes
221// all differences (extra ZK proof elements, challenges, scratch space).
222inline std::string generate_memory_offsets(int log_n, const MemoryLayoutConfig& config)
223{
224 const int NUMBER_OF_SUBRELATIONS = 31;
225 const int NUMBER_OF_ALPHAS = NUMBER_OF_SUBRELATIONS - 1;
226 const int START_POINTER = 0x1000;
227
228 std::ostringstream out;
229
230 // Helper lambdas
231 auto print_header_centered = [&](const std::string& text) {
232 const std::string top = "/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/";
233 const std::string bottom = "/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/";
234 size_t width = static_cast<size_t>(top.length()) - 4; // exclude /* and */
235 std::string centered =
236 "/*" + std::string(static_cast<size_t>((width - text.length()) / 2), ' ') + text +
237 std::string(static_cast<size_t>(width - text.length() - (width - text.length()) / 2), ' ') + "*/";
238 out << "\n" << top << "\n" << centered << "\n" << bottom << "\n";
239 };
240
241 auto print_loc = [&](int pointer, const std::string& name) {
242 out << "uint256 internal constant " << name << " = " << std::showbase << std::hex << pointer << ";\n";
243 };
244
245 auto print_fr = print_loc;
246
247 auto print_g1 = [&](int pointer, const std::string& name) {
248 print_loc(pointer, name + "_X_LOC");
249 print_loc(pointer + 32, name + "_Y_LOC");
250 };
251
252 // Data arrays
253 const std::vector<std::string> vk_fr = { "VK_CIRCUIT_SIZE_LOC",
254 "VK_NUM_PUBLIC_INPUTS_LOC",
255 "VK_PUB_INPUTS_OFFSET_LOC" };
256
257 const std::vector<std::string> vk_g1 = { "Q_M",
258 "Q_L",
259 "Q_R",
260 "Q_O",
261 "Q_4",
262 "Q_C",
263 "Q_ARITH",
264 "SIGMA_1",
265 "SIGMA_2",
266 "SIGMA_3",
267 "SIGMA_4",
268 "ID_1",
269 "ID_2",
270 "ID_3",
271 "ID_4",
272 "LAGRANGE_FIRST",
273 "LAGRANGE_LAST",
274 "Q_LOOKUP",
275 "TABLE_1",
276 "TABLE_2",
277 "TABLE_3",
278 "TABLE_4",
279 "Q_DELTA_RANGE",
280 "Q_ELLIPTIC",
281 "Q_MEMORY",
282 "Q_NNF",
283 "Q_POSEIDON_2_EXTERNAL",
284 "Q_POSEIDON_2_INTERNAL" };
285
286 const std::vector<std::string> pairing_points = { "PAIRING_POINT_0_X_0_LOC", "PAIRING_POINT_0_X_1_LOC",
287 "PAIRING_POINT_0_Y_0_LOC", "PAIRING_POINT_0_Y_1_LOC",
288 "PAIRING_POINT_1_X_0_LOC", "PAIRING_POINT_1_X_1_LOC",
289 "PAIRING_POINT_1_Y_0_LOC", "PAIRING_POINT_1_Y_1_LOC" };
290
291 const std::vector<std::string> proof_g1 = {
292 "W_L", "W_R", "W_O", "LOOKUP_READ_COUNTS", "LOOKUP_READ_TAGS", "W_4", "LOOKUP_INVERSES", "Z_PERM"
293 };
294
295 const std::vector<std::string> entities = { "SIGMA1",
296 "SIGMA2",
297 "SIGMA3",
298 "SIGMA4",
299 "ID1",
300 "ID2",
301 "ID3",
302 "ID4",
303 "LAGRANGE_FIRST",
304 "LAGRANGE_LAST",
305 "QLOOKUP",
306 "TABLE1",
307 "TABLE2",
308 "TABLE3",
309 "TABLE4",
310 "QM",
311 "QR",
312 "QO",
313 "QC",
314 "QL",
315 "Q4",
316 "QARITH",
317 "QRANGE",
318 "QELLIPTIC",
319 "QMEMORY",
320 "QNNF",
321 "QPOSEIDON2_EXTERNAL",
322 "QPOSEIDON2_INTERNAL",
323 "W1",
324 "W2",
325 "W3",
326 "W4",
327 "Z_PERM",
328 "LOOKUP_INVERSES",
329 "LOOKUP_READ_COUNTS",
330 "LOOKUP_READ_TAGS",
331 "W1_SHIFT",
332 "W2_SHIFT",
333 "W3_SHIFT",
334 "W4_SHIFT",
335 "Z_PERM_SHIFT" };
336
337 const std::vector<std::string> challenges = { "ETA",
338 "ETA_TWO",
339 "ETA_THREE",
340 "ROM_LOGUP_GAMMA",
341 "BETA",
342 "GAMMA",
343 "RHO",
344 "GEMINI_R",
345 "SHPLONK_NU",
346 "SHPLONK_Z",
347 "PUBLIC_INPUTS_DELTA_NUMERATOR",
348 "PUBLIC_INPUTS_DELTA_DENOMINATOR" };
349
350 const std::vector<std::string> subrelation_intermediates = { "AUX_NON_NATIVE_FIELD_IDENTITY",
351 "AUX_LIMB_ACCUMULATOR_IDENTITY",
352 "AUX_RAM_CONSISTENCY_CHECK_IDENTITY",
353 "AUX_ROM_CONSISTENCY_CHECK_IDENTITY",
354 "AUX_MEMORY_CHECK_IDENTITY" };
355
356 const std::vector<std::string> general_intermediates = { "FINAL_ROUND_TARGET_LOC", "POW_PARTIAL_EVALUATION_LOC" };
357
358 int pointer = START_POINTER;
359
360 // VK INDICIES
361 print_header_centered("VK INDICIES");
362 for (const auto& item : vk_fr) {
363 print_fr(pointer, item);
364 pointer += 32;
365 }
366 for (const auto& item : vk_g1) {
367 print_g1(pointer, item);
368 pointer += 64;
369 }
370
371 // PROOF INDICIES
372 print_header_centered("PROOF INDICIES");
373 for (const auto& item : pairing_points) {
374 print_fr(pointer, item);
375 pointer += 32;
376 }
377
378 // ZK: GEMINI_MASKING_POLY before proof_g1
379 if (config.is_zk) {
380 print_g1(pointer, "GEMINI_MASKING_POLY");
381 pointer += 64;
382 }
383
384 for (const auto& item : proof_g1) {
385 print_g1(pointer, item);
386 pointer += 64;
387 }
388
389 // ZK: LIBRA_CONCAT after proof_g1, then LIBRA_SUM
390 if (config.is_zk) {
391 print_g1(pointer, "LIBRA_CONCAT");
392 pointer += 64;
393 print_fr(pointer, "LIBRA_SUM_LOC");
394 pointer += 32;
395 }
396
397 // SUMCHECK UNIVARIATES
398 print_header_centered("PROOF INDICIES - SUMCHECK UNIVARIATES");
399 for (int size = 0; size < log_n; ++size) {
400 for (int relation_len = 0; relation_len < config.batched_relation_partial_length; ++relation_len) {
401 std::string name =
402 "SUMCHECK_UNIVARIATE_" + std::to_string(size) + "_" + std::to_string(relation_len) + "_LOC";
403 print_fr(pointer, name);
404 pointer += 32;
405 }
406 }
407
408 // SUMCHECK EVALUATIONS
409 print_header_centered("PROOF INDICIES - SUMCHECK EVALUATIONS");
410
411 // ZK: GEMINI_MASKING_EVAL is entity index 0
412 if (config.is_zk) {
413 print_fr(pointer, "GEMINI_MASKING_EVAL_LOC");
414 pointer += 32;
415 }
416
417 for (const auto& entity : entities) {
418 print_fr(pointer, entity + "_EVAL_LOC");
419 pointer += 32;
420 }
421
422 // ZK: LIBRA_EVALUATION, LIBRA_GRAND_PRODUCT, LIBRA_QUOTIENT after entity evals
423 if (config.is_zk) {
424 print_fr(pointer, "LIBRA_EVALUATION_LOC");
425 pointer += 32;
426 print_g1(pointer, "LIBRA_GRAND_PRODUCT");
427 pointer += 64;
428 print_g1(pointer, "LIBRA_QUOTIENT");
429 pointer += 64;
430 }
431
432 // SHPLEMINI - GEMINI FOLDING COMMS
433 print_header_centered("PROOF INDICIES - GEMINI FOLDING COMMS");
434 for (int size = 0; size < log_n - 1; ++size) {
435 print_g1(pointer, "GEMINI_FOLD_UNIVARIATE_" + std::to_string(size));
436 pointer += 64;
437 }
438
439 // GEMINI FOLDING EVALUATIONS
440 print_header_centered("PROOF INDICIES - GEMINI FOLDING EVALUATIONS");
441 for (int size = 0; size < log_n; ++size) {
442 print_fr(pointer, "GEMINI_A_EVAL_" + std::to_string(size));
443 pointer += 32;
444 }
445
446 // ZK: LIBRA POLY EVALUATIONS
447 if (config.is_zk) {
448 print_header_centered("PROOF INDICIES - LIBRA POLY EVALUATIONS");
449 for (int i = 0; i < 4; ++i) {
450 print_fr(pointer, "LIBRA_POLY_EVAL_" + std::to_string(i) + "_LOC");
451 pointer += 32;
452 }
453 }
454
455 print_g1(pointer, "SHPLONK_Q");
456 pointer += 64;
457 print_g1(pointer, "KZG_QUOTIENT");
458 pointer += 64;
459
460 print_header_centered("PROOF INDICIES - COMPLETE");
461
462 // CHALLENGES
463 print_header_centered("CHALLENGES");
464 for (const auto& chall : challenges) {
465 print_fr(pointer, chall + "_CHALLENGE");
466 pointer += 32;
467 }
468 for (int alpha = 0; alpha < NUMBER_OF_ALPHAS; ++alpha) {
469 print_fr(pointer, "ALPHA_CHALLENGE_" + std::to_string(alpha));
470 pointer += 32;
471 }
472 for (int gate = 0; gate < log_n; ++gate) {
473 print_fr(pointer, "GATE_CHALLENGE_" + std::to_string(gate));
474 pointer += 32;
475 }
476
477 // ZK: LIBRA_CHALLENGE before SUM_U challenges
478 if (config.is_zk) {
479 print_fr(pointer, "LIBRA_CHALLENGE");
480 pointer += 32;
481 }
482
483 for (int sum_u = 0; sum_u < log_n; ++sum_u) {
484 print_fr(pointer, "SUM_U_CHALLENGE_" + std::to_string(sum_u));
485 pointer += 32;
486 }
487 print_header_centered("CHALLENGES - COMPLETE");
488
489 // RUNTIME MEMORY
490 print_header_centered("SUMCHECK - RUNTIME MEMORY");
491 print_header_centered("SUMCHECK - RUNTIME MEMORY - BARYCENTRIC");
492
493 // Barycentric domain
494 for (int i = 0; i < config.barycentric_domain_size; ++i) {
495 print_fr(pointer, "BARYCENTRIC_LAGRANGE_DENOMINATOR_" + std::to_string(i) + "_LOC");
496 pointer += 32;
497 }
498 for (int i = 0; i < log_n; ++i) {
499 for (int j = 0; j < config.barycentric_domain_size; ++j) {
500 print_fr(pointer,
501 "BARYCENTRIC_DENOMINATOR_INVERSES_" + std::to_string(i) + "_" + std::to_string(j) + "_LOC");
502 pointer += 32;
503 }
504 }
505 print_header_centered("SUMCHECK - RUNTIME MEMORY - BARYCENTRIC COMPLETE");
506
507 // SUBRELATION EVALUATIONS
508 print_header_centered("SUMCHECK - RUNTIME MEMORY - SUBRELATION EVALUATIONS");
509 for (int i = 0; i < NUMBER_OF_SUBRELATIONS; ++i) {
510 print_fr(pointer, "SUBRELATION_EVAL_" + std::to_string(i) + "_LOC");
511 pointer += 32;
512 }
513 print_header_centered("SUMCHECK - RUNTIME MEMORY - SUBRELATION EVALUATIONS COMPLETE");
514
515 // SUBRELATION INTERMEDIATES
516 print_header_centered("SUMCHECK - RUNTIME MEMORY - SUBRELATION INTERMEDIATES");
517 for (const auto& item : general_intermediates) {
518 print_fr(pointer, item);
519 pointer += 32;
520 }
521 for (const auto& item : subrelation_intermediates) {
522 print_fr(pointer, item);
523 pointer += 32;
524 }
525 print_header_centered("SUMCHECK - RUNTIME MEMORY - COMPLETE");
526
527 // SHPLEMINI RUNTIME MEMORY
528 print_header_centered("SHPLEMINI - RUNTIME MEMORY");
529 print_header_centered("SHPLEMINI - POWERS OF EVALUATION CHALLENGE");
530 out << "/// {{ UNROLL_SECTION_START POWERS_OF_EVALUATION_CHALLENGE }}\n";
531 for (int i = 0; i < log_n; ++i) {
532 print_fr(pointer, "POWERS_OF_EVALUATION_CHALLENGE_" + std::to_string(i) + "_LOC");
533 pointer += 32;
534 }
535 out << "/// {{ UNROLL_SECTION_END POWERS_OF_EVALUATION_CHALLENGE }}\n";
536 print_header_centered("SHPLEMINI - POWERS OF EVALUATION CHALLENGE COMPLETE");
537
538 // BATCH SCALARS
539 print_header_centered("SHPLEMINI - RUNTIME MEMORY - BATCH SCALARS");
540 const int BATCH_SIZE = 69;
541 for (int i = 1; i < BATCH_SIZE; ++i) {
542 print_fr(pointer, "BATCH_SCALAR_" + std::to_string(i) + "_LOC");
543 pointer += 32;
544 }
545 print_header_centered("SHPLEMINI - RUNTIME MEMORY - BATCH SCALARS COMPLETE");
546
547 // INVERSIONS
548 print_header_centered("SHPLEMINI - RUNTIME MEMORY - INVERSIONS");
549
550 print_fr(pointer, "GEMINI_R_INV_LOC");
551 pointer += 32;
552
553 // ZK: LIBRA_SUBGROUP_DENOM
554 if (config.is_zk) {
555 print_fr(pointer, "LIBRA_SUBGROUP_DENOM_LOC");
556 pointer += 32;
557 }
558
559 // Batched evaluation accumulator inversions
560 for (int i = 0; i < log_n; ++i) {
561 print_fr(pointer, "BATCH_EVALUATION_ACCUMULATOR_INVERSION_" + std::to_string(i) + "_LOC");
562 pointer += 32;
563 }
564
565 out << "\n";
566 print_fr(pointer, "CONSTANT_TERM_ACCUMULATOR_LOC");
567 pointer += 32;
568
569 out << "\n";
570 print_fr(pointer, "POS_INVERTED_DENOMINATOR");
571 pointer += 32;
572 print_fr(pointer, "NEG_INVERTED_DENOMINATOR");
573 pointer += 32;
574
575 out << "\n";
576 out << "// LOG_N challenge pow minus u\n";
577 for (int i = 0; i < log_n; ++i) {
578 print_fr(pointer, "INVERTED_CHALLENGE_POW_MINUS_U_" + std::to_string(i) + "_LOC");
579 pointer += 32;
580 }
581
582 out << "\n";
583 out << "// LOG_N pos_inverted_off\n";
584 for (int i = 0; i < log_n; ++i) {
585 print_fr(pointer, "POS_INVERTED_DENOM_" + std::to_string(i) + "_LOC");
586 pointer += 32;
587 }
588
589 out << "\n";
590 out << "// LOG_N neg_inverted_off\n";
591 for (int i = 0; i < log_n; ++i) {
592 print_fr(pointer, "NEG_INVERTED_DENOM_" + std::to_string(i) + "_LOC");
593 pointer += 32;
594 }
595
596 out << "\n";
597 for (int i = 0; i < log_n; ++i) {
598 print_fr(pointer, "FOLD_POS_EVALUATIONS_" + std::to_string(i) + "_LOC");
599 pointer += 32;
600 }
601
602 print_header_centered("SHPLEMINI RUNTIME MEMORY - INVERSIONS - COMPLETE");
603 print_header_centered("SHPLEMINI RUNTIME MEMORY - COMPLETE");
604
605 // Temporary space for batch inversions
606 out << "\n";
607 for (int i = 0; i < config.barycentric_domain_size * log_n; ++i) {
608 print_fr(pointer, "BARYCENTRIC_TEMP_" + std::to_string(i) + "_LOC");
609 pointer += 32;
610 }
611
612 print_fr(pointer, "PUBLIC_INPUTS_DENOM_TEMP_LOC");
613 pointer += 32;
614 print_fr(pointer, "GEMINI_R_INV_TEMP_LOC");
615 pointer += 32;
616 // ZK: LIBRA_SUBGROUP_DENOM_TEMP_LOC
617 if (config.is_zk) {
618 print_fr(pointer, "LIBRA_SUBGROUP_DENOM_TEMP_LOC");
619 pointer += 32;
620 }
621 print_fr(pointer, "BATCH_PRODUCT_TEMP_LOC");
622 pointer += 32;
623
624 // Temporary space
625 print_header_centered("Temporary space");
626 for (int i = 0; i < 3 * log_n; ++i) {
627 print_fr(pointer, "TEMP_" + std::to_string(i) + "_LOC");
628 pointer += 32;
629 }
630
631 // ZK: Consistency check scratch space
632 if (config.is_zk) {
633 const int active_challenge_poly_length = 1 + log_n * config.batched_relation_partial_length;
634 const int consistency_scratch_length = active_challenge_poly_length + 1;
635 print_header_centered("Small subgroup IPA");
636 out << "// Allocate only the active challenge-poly prefix and the extra denominator/product slot\n";
637 for (int i = 0; i < active_challenge_poly_length; ++i) {
638 print_fr(pointer, "CHALLENGE_POLY_LAGRANGE_BASE_" + std::to_string(i));
639 pointer += 32;
640 }
641
642 out << "\n";
643 for (int i = 0; i < consistency_scratch_length; ++i) {
644 print_fr(pointer, "CONSISTENCY_DENOMINATORS_BASE_" + std::to_string(i));
645 pointer += 32;
646 }
647
648 out << "\n";
649 for (int i = 0; i < consistency_scratch_length; ++i) {
650 print_fr(pointer, "CONSISTENCY_PRODUCTS_BASE_" + std::to_string(i));
651 pointer += 32;
652 }
653
654 out << "\n";
655 out << "// LIBRA_UNIVARIATES_LENGTH = BATCHED_RELATION_PARTIAL_LENGTH = " << std::dec
656 << config.batched_relation_partial_length << "\n";
657 out << "uint256 internal constant LIBRA_UNIVARIATES_LENGTH = " << std::showbase << std::hex
658 << config.batched_relation_partial_length << ";\n";
659 out << "uint256 internal constant LIBRA_UNIVARIATES_LENGTH_MINUS_ONE = " << std::showbase << std::hex
660 << config.batched_relation_partial_length - 1 << ";\n";
661 out << "// 1/SUBGROUP_SIZE mod p (precomputed constant)\n";
662
663 out << "// 1/256 mod p, computed as pow(256, p-2, p) where p = BN254 scalar field modulus\n";
664 out << "// 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001\n";
665 out << "uint256 internal constant INV_SUBGROUP_SIZE = "
666 "0x3033ea246e506e898e97f570caffd704cb0bb460313fb720b29e139e5c100001;\n";
667 }
668
669 print_fr(pointer, "LATER_SCRATCH_SPACE");
670 pointer += 32;
671 print_header_centered("Temporary space - COMPLETE");
672
673 // Scratch space aliases
674 out << "\n";
675 out << "// Aliases for scratch space\n";
676 out << "// Scratch space aliases at 0x00-0x40\n";
677 out << "// Phase 1 (sumcheck rounds): CHALL_POW_LOC, SUMCHECK_U_LOC, GEMINI_A_LOC\n";
678 out << "// Phase 2 (shplemini batch scalars): SS_POS_INV_DENOM_LOC, SS_NEG_INV_DENOM_LOC, SS_GEMINI_EVALS_LOC\n";
679 out << "// These phases do not overlap in execution time.\n";
680
681 print_fr(0x00, "CHALL_POW_LOC");
682 print_fr(0x20, "SUMCHECK_U_LOC");
683 print_fr(0x40, "GEMINI_A_LOC");
684 out << "\n";
685 print_fr(0x00, "SS_POS_INV_DENOM_LOC");
686 print_fr(0x20, "SS_NEG_INV_DENOM_LOC");
687 print_fr(0x40, "SS_GEMINI_EVALS_LOC");
688
689 // EC aliases
690 out << "\n\n";
691 print_header_centered("SUMCHECK - MEMORY ALIASES");
692
693 return out.str();
694}
695
697inline void replace_memory_layout(std::string& template_str, int log_n, const MemoryLayoutConfig& mem_config)
698{
699 std::string::size_type start_pos = template_str.find("// {{ SECTION_START MEMORY_LAYOUT }}");
700 std::string::size_type end_pos = template_str.find("// {{ SECTION_END MEMORY_LAYOUT }}");
701
702 if (start_pos != std::string::npos && end_pos != std::string::npos) {
703 std::string::size_type start_line_end = template_str.find("\n", start_pos);
704 std::string generated_code = generate_memory_offsets(log_n, mem_config);
705 template_str = template_str.substr(0, start_line_end + 1) + generated_code + template_str.substr(end_pos);
706 }
707}
708
709// Apply all template parameter substitutions for the optimized verifier.
710// This handles VK hash, circuit parameters, gemini fold lengths, and all 56 VK field substitutions.
711// The is_zk flag controls ZK-specific parameters (BATCHED_RELATION_PARTIAL_LENGTH_MINUS_ONE,
712// extra gemini eval terms).
713template <typename VK>
714inline void apply_template_params(std::string& template_str, VK const& verification_key, bool is_zk)
715{
716 auto set_template_param = [&template_str](const std::string& key, const std::string& value) {
717 std::string::size_type pos = 0;
718 std::string pattern = "{{ " + key + " }}";
719 while ((pos = template_str.find(pattern, pos)) != std::string::npos) {
720 template_str.replace(pos, pattern.length(), value);
721 pos += value.length();
722 }
723 };
724
725 auto log_circuit_size = verification_key->log_circuit_size;
726
727 // larger overflows int, sanity check not 0
728 if (log_circuit_size > 31 || log_circuit_size < 1) {
729 info("log_circuit_size out of bounds | 0 < x < 31 | x = " + std::to_string(log_circuit_size));
730 std::abort();
731 }
732
733 if (verification_key->num_public_inputs < bb::PAIRING_POINTS_SIZE) {
734 info("invariant broken: public input points are smaller than pairing points (they are usually appended)");
735 std::abort();
736 }
737
738 set_template_param("VK_HASH", field_to_hex(verification_key->hash()));
739 set_template_param("CIRCUIT_SIZE", std::to_string(1 << log_circuit_size));
740 set_template_param("LOG_CIRCUIT_SIZE", std::to_string(log_circuit_size));
741 set_template_param("NUM_PUBLIC_INPUTS", std::to_string(verification_key->num_public_inputs));
742 // REAL_NUM_PUBLIC_INPUTS excludes the 8 pairing point limbs that are part of the proof structure
743 set_template_param("REAL_NUM_PUBLIC_INPUTS",
744 std::to_string(verification_key->num_public_inputs - bb::PAIRING_POINTS_SIZE));
745 set_template_param("LOG_N_MINUS_ONE", std::to_string(log_circuit_size - 1));
746
747 // ZK: BATCHED_RELATION_PARTIAL_LENGTH - 1 = 8 (constant for ZK, domain size is always 9)
748 if (is_zk) {
749 set_template_param("BATCHED_RELATION_PARTIAL_LENGTH_MINUS_ONE", "8");
750 set_template_param("NUMBER_OF_LAGRANGE_BASES", std::to_string(log_circuit_size * 9));
751 set_template_param("NUMBER_OF_LAGRANGE_BASES_PLUS_ONE", std::to_string(log_circuit_size * 9 + 1));
752 // Libra batch scalar indices: placed after gemini fold scalars (38 + LOG_N-1 = 37 + LOG_N)
753 set_template_param("LIBRA_BATCH_SCALAR_0", std::to_string(37 + log_circuit_size));
754 set_template_param("LIBRA_BATCH_SCALAR_1", std::to_string(38 + log_circuit_size));
755 set_template_param("LIBRA_BATCH_SCALAR_2", std::to_string(39 + log_circuit_size));
756 }
757
758 uint32_t gemini_fold_univariate_length = static_cast<uint32_t>((log_circuit_size - 1) * 0x40);
759 uint32_t gemini_fold_univariate_hash_length = static_cast<uint32_t>(gemini_fold_univariate_length + 0x20);
760 // ZK: gemini evals include log_n evals + 4 libra poly evals
761 uint32_t gemini_evals_length =
762 is_zk ? static_cast<uint32_t>((log_circuit_size + 4) * 0x20) : static_cast<uint32_t>(log_circuit_size * 0x20);
763 uint32_t gemini_evals_hash_length = static_cast<uint32_t>(gemini_evals_length + 0x20);
764
765 set_template_param("GEMINI_FOLD_UNIVARIATE_LENGTH", int_to_hex(gemini_fold_univariate_length));
766 set_template_param("GEMINI_FOLD_UNIVARIATE_HASH_LENGTH", int_to_hex(gemini_fold_univariate_hash_length));
767 set_template_param("GEMINI_EVALS_LENGTH", int_to_hex(gemini_evals_length));
768 set_template_param("GEMINI_EVALS_HASH_LENGTH", int_to_hex(gemini_evals_hash_length));
769
770 // Verification Key — use g1_to_xy_hex so identity-commitment selectors
771 // (e.g. selectors that commit to identically-zero polynomials) emit the
772 // EIP-196 canonical (0, 0) instead of the raw affine sentinel, which is
773 // off-curve and rejected by the ecMul/ecAdd precompiles.
774 auto set_g1_template_param = [&](const std::string& name_prefix, const auto& point) {
775 const auto [x_hex, y_hex] = g1_to_xy_hex(point);
776 set_template_param(name_prefix + "_X_LOC", x_hex);
777 set_template_param(name_prefix + "_Y_LOC", y_hex);
778 };
779 set_g1_template_param("Q_L", verification_key->q_l());
780 set_g1_template_param("Q_R", verification_key->q_r());
781 set_g1_template_param("Q_O", verification_key->q_o());
782 set_g1_template_param("Q_4", verification_key->q_4());
783 set_g1_template_param("Q_M", verification_key->q_m());
784 set_g1_template_param("Q_C", verification_key->q_c());
785 set_g1_template_param("Q_LOOKUP", verification_key->q_lookup());
786 set_g1_template_param("Q_ARITH", verification_key->q_arith());
787 set_g1_template_param("Q_DELTA_RANGE", verification_key->q_delta_range());
788 set_g1_template_param("Q_ELLIPTIC", verification_key->q_elliptic());
789 set_g1_template_param("Q_MEMORY", verification_key->q_memory());
790 set_g1_template_param("Q_NNF", verification_key->q_nnf());
791 set_g1_template_param("Q_POSEIDON_2_EXTERNAL", verification_key->q_poseidon2_external());
792 set_g1_template_param("Q_POSEIDON_2_INTERNAL", verification_key->q_poseidon2_internal());
793 set_g1_template_param("SIGMA_1", verification_key->sigma_1());
794 set_g1_template_param("SIGMA_2", verification_key->sigma_2());
795 set_g1_template_param("SIGMA_3", verification_key->sigma_3());
796 set_g1_template_param("SIGMA_4", verification_key->sigma_4());
797 set_g1_template_param("TABLE_1", verification_key->table_1());
798 set_g1_template_param("TABLE_2", verification_key->table_2());
799 set_g1_template_param("TABLE_3", verification_key->table_3());
800 set_g1_template_param("TABLE_4", verification_key->table_4());
801 set_g1_template_param("ID_1", verification_key->id_1());
802 set_g1_template_param("ID_2", verification_key->id_2());
803 set_g1_template_param("ID_3", verification_key->id_3());
804 set_g1_template_param("ID_4", verification_key->id_4());
805 set_g1_template_param("LAGRANGE_FIRST", verification_key->lagrange_first());
806 set_g1_template_param("LAGRANGE_LAST", verification_key->lagrange_last());
807}
#define info(...)
Definition log.hpp:93
void apply_template_params(std::string &template_str, VK const &verification_key, bool is_zk)
void replace_unroll_section(std::string &template_str, const std::string &section_name, int log_n, const UnrollConfig &config)
std::pair< std::string, std::string > g1_to_xy_hex(const Commitment &point)
std::string generate_memory_offsets(int log_n, const MemoryLayoutConfig &config)
std::string field_to_hex(const Field &f)
void replace_memory_layout(std::string &template_str, int log_n, const MemoryLayoutConfig &mem_config)
Find the memory layout tags then insert generated layout into the offsets.
std::string generate_unroll_section(const std::string &section_name, int log_n, const UnrollConfig &config)
std::string int_to_hex(size_t i)
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
std::string to_string(bb::avm2::ValueTag tag)
std::string name
bb::VectorAffineElementPushSpan< BaseParams > out