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 = 29;
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_C",
259 "Q_L",
260 "Q_R",
261 "Q_O",
262 "Q_4",
263 "Q_LOOKUP",
264 "Q_ARITH",
265 "Q_DELTA_RANGE",
266 "Q_ELLIPTIC",
267 "Q_MEMORY",
268 "Q_NNF",
269 "Q_POSEIDON_2_EXTERNAL",
270 "Q_POSEIDON_2_INTERNAL",
271 "SIGMA_1",
272 "SIGMA_2",
273 "SIGMA_3",
274 "SIGMA_4",
275 "ID_1",
276 "ID_2",
277 "ID_3",
278 "ID_4",
279 "TABLE_1",
280 "TABLE_2",
281 "TABLE_3",
282 "TABLE_4",
283 "LAGRANGE_FIRST",
284 "LAGRANGE_LAST" };
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 = { "QM",
296 "QC",
297 "QL",
298 "QR",
299 "QO",
300 "Q4",
301 "QLOOKUP",
302 "QARITH",
303 "QRANGE",
304 "QELLIPTIC",
305 "QMEMORY",
306 "QNNF",
307 "QPOSEIDON2_EXTERNAL",
308 "QPOSEIDON2_INTERNAL",
309 "SIGMA1",
310 "SIGMA2",
311 "SIGMA3",
312 "SIGMA4",
313 "ID1",
314 "ID2",
315 "ID3",
316 "ID4",
317 "TABLE1",
318 "TABLE2",
319 "TABLE3",
320 "TABLE4",
321 "LAGRANGE_FIRST",
322 "LAGRANGE_LAST",
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 "BETA",
341 "GAMMA",
342 "RHO",
343 "GEMINI_R",
344 "SHPLONK_NU",
345 "SHPLONK_Z",
346 "PUBLIC_INPUTS_DELTA_NUMERATOR",
347 "PUBLIC_INPUTS_DELTA_DENOMINATOR" };
348
349 const std::vector<std::string> subrelation_intermediates = { "AUX_NON_NATIVE_FIELD_IDENTITY",
350 "AUX_LIMB_ACCUMULATOR_IDENTITY",
351 "AUX_RAM_CONSISTENCY_CHECK_IDENTITY",
352 "AUX_ROM_CONSISTENCY_CHECK_IDENTITY",
353 "AUX_MEMORY_CHECK_IDENTITY" };
354
355 const std::vector<std::string> general_intermediates = { "FINAL_ROUND_TARGET_LOC", "POW_PARTIAL_EVALUATION_LOC" };
356
357 int pointer = START_POINTER;
358
359 // VK INDICIES
360 print_header_centered("VK INDICIES");
361 for (const auto& item : vk_fr) {
362 print_fr(pointer, item);
363 pointer += 32;
364 }
365 for (const auto& item : vk_g1) {
366 print_g1(pointer, item);
367 pointer += 64;
368 }
369
370 // PROOF INDICIES
371 print_header_centered("PROOF INDICIES");
372 for (const auto& item : pairing_points) {
373 print_fr(pointer, item);
374 pointer += 32;
375 }
376
377 // ZK: GEMINI_MASKING_POLY before proof_g1
378 if (config.is_zk) {
379 print_g1(pointer, "GEMINI_MASKING_POLY");
380 pointer += 64;
381 }
382
383 for (const auto& item : proof_g1) {
384 print_g1(pointer, item);
385 pointer += 64;
386 }
387
388 // ZK: LIBRA_CONCAT after proof_g1, then LIBRA_SUM
389 if (config.is_zk) {
390 print_g1(pointer, "LIBRA_CONCAT");
391 pointer += 64;
392 print_fr(pointer, "LIBRA_SUM_LOC");
393 pointer += 32;
394 }
395
396 // SUMCHECK UNIVARIATES
397 print_header_centered("PROOF INDICIES - SUMCHECK UNIVARIATES");
398 for (int size = 0; size < log_n; ++size) {
399 for (int relation_len = 0; relation_len < config.batched_relation_partial_length; ++relation_len) {
400 std::string name =
401 "SUMCHECK_UNIVARIATE_" + std::to_string(size) + "_" + std::to_string(relation_len) + "_LOC";
402 print_fr(pointer, name);
403 pointer += 32;
404 }
405 }
406
407 // SUMCHECK EVALUATIONS
408 print_header_centered("PROOF INDICIES - SUMCHECK EVALUATIONS");
409
410 // ZK: GEMINI_MASKING_EVAL is entity index 0
411 if (config.is_zk) {
412 print_fr(pointer, "GEMINI_MASKING_EVAL_LOC");
413 pointer += 32;
414 }
415
416 for (const auto& entity : entities) {
417 print_fr(pointer, entity + "_EVAL_LOC");
418 pointer += 32;
419 }
420
421 // ZK: LIBRA_EVALUATION, LIBRA_GRAND_PRODUCT, LIBRA_QUOTIENT after entity evals
422 if (config.is_zk) {
423 print_fr(pointer, "LIBRA_EVALUATION_LOC");
424 pointer += 32;
425 print_g1(pointer, "LIBRA_GRAND_PRODUCT");
426 pointer += 64;
427 print_g1(pointer, "LIBRA_QUOTIENT");
428 pointer += 64;
429 }
430
431 // SHPLEMINI - GEMINI FOLDING COMMS
432 print_header_centered("PROOF INDICIES - GEMINI FOLDING COMMS");
433 for (int size = 0; size < log_n - 1; ++size) {
434 print_g1(pointer, "GEMINI_FOLD_UNIVARIATE_" + std::to_string(size));
435 pointer += 64;
436 }
437
438 // GEMINI FOLDING EVALUATIONS
439 print_header_centered("PROOF INDICIES - GEMINI FOLDING EVALUATIONS");
440 for (int size = 0; size < log_n; ++size) {
441 print_fr(pointer, "GEMINI_A_EVAL_" + std::to_string(size));
442 pointer += 32;
443 }
444
445 // ZK: LIBRA POLY EVALUATIONS
446 if (config.is_zk) {
447 print_header_centered("PROOF INDICIES - LIBRA POLY EVALUATIONS");
448 for (int i = 0; i < 4; ++i) {
449 print_fr(pointer, "LIBRA_POLY_EVAL_" + std::to_string(i) + "_LOC");
450 pointer += 32;
451 }
452 }
453
454 print_g1(pointer, "SHPLONK_Q");
455 pointer += 64;
456 print_g1(pointer, "KZG_QUOTIENT");
457 pointer += 64;
458
459 print_header_centered("PROOF INDICIES - COMPLETE");
460
461 // CHALLENGES
462 print_header_centered("CHALLENGES");
463 for (const auto& chall : challenges) {
464 print_fr(pointer, chall + "_CHALLENGE");
465 pointer += 32;
466 }
467 for (int alpha = 0; alpha < NUMBER_OF_ALPHAS; ++alpha) {
468 print_fr(pointer, "ALPHA_CHALLENGE_" + std::to_string(alpha));
469 pointer += 32;
470 }
471 for (int gate = 0; gate < log_n; ++gate) {
472 print_fr(pointer, "GATE_CHALLENGE_" + std::to_string(gate));
473 pointer += 32;
474 }
475
476 // ZK: LIBRA_CHALLENGE before SUM_U challenges
477 if (config.is_zk) {
478 print_fr(pointer, "LIBRA_CHALLENGE");
479 pointer += 32;
480 }
481
482 for (int sum_u = 0; sum_u < log_n; ++sum_u) {
483 print_fr(pointer, "SUM_U_CHALLENGE_" + std::to_string(sum_u));
484 pointer += 32;
485 }
486 print_header_centered("CHALLENGES - COMPLETE");
487
488 // RUNTIME MEMORY
489 print_header_centered("SUMCHECK - RUNTIME MEMORY");
490 print_header_centered("SUMCHECK - RUNTIME MEMORY - BARYCENTRIC");
491
492 // Barycentric domain
493 for (int i = 0; i < config.barycentric_domain_size; ++i) {
494 print_fr(pointer, "BARYCENTRIC_LAGRANGE_DENOMINATOR_" + std::to_string(i) + "_LOC");
495 pointer += 32;
496 }
497 for (int i = 0; i < log_n; ++i) {
498 for (int j = 0; j < config.barycentric_domain_size; ++j) {
499 print_fr(pointer,
500 "BARYCENTRIC_DENOMINATOR_INVERSES_" + std::to_string(i) + "_" + std::to_string(j) + "_LOC");
501 pointer += 32;
502 }
503 }
504 print_header_centered("SUMCHECK - RUNTIME MEMORY - BARYCENTRIC COMPLETE");
505
506 // SUBRELATION EVALUATIONS
507 print_header_centered("SUMCHECK - RUNTIME MEMORY - SUBRELATION EVALUATIONS");
508 for (int i = 0; i < NUMBER_OF_SUBRELATIONS; ++i) {
509 print_fr(pointer, "SUBRELATION_EVAL_" + std::to_string(i) + "_LOC");
510 pointer += 32;
511 }
512 print_header_centered("SUMCHECK - RUNTIME MEMORY - SUBRELATION EVALUATIONS COMPLETE");
513
514 // SUBRELATION INTERMEDIATES
515 print_header_centered("SUMCHECK - RUNTIME MEMORY - SUBRELATION INTERMEDIATES");
516 for (const auto& item : general_intermediates) {
517 print_fr(pointer, item);
518 pointer += 32;
519 }
520 for (const auto& item : subrelation_intermediates) {
521 print_fr(pointer, item);
522 pointer += 32;
523 }
524 print_header_centered("SUMCHECK - RUNTIME MEMORY - COMPLETE");
525
526 // SHPLEMINI RUNTIME MEMORY
527 print_header_centered("SHPLEMINI - RUNTIME MEMORY");
528 print_header_centered("SHPLEMINI - POWERS OF EVALUATION CHALLENGE");
529 out << "/// {{ UNROLL_SECTION_START POWERS_OF_EVALUATION_CHALLENGE }}\n";
530 for (int i = 0; i < log_n; ++i) {
531 print_fr(pointer, "POWERS_OF_EVALUATION_CHALLENGE_" + std::to_string(i) + "_LOC");
532 pointer += 32;
533 }
534 out << "/// {{ UNROLL_SECTION_END POWERS_OF_EVALUATION_CHALLENGE }}\n";
535 print_header_centered("SHPLEMINI - POWERS OF EVALUATION CHALLENGE COMPLETE");
536
537 // BATCH SCALARS
538 print_header_centered("SHPLEMINI - RUNTIME MEMORY - BATCH SCALARS");
539 const int BATCH_SIZE = 69;
540 for (int i = 1; i < BATCH_SIZE; ++i) {
541 print_fr(pointer, "BATCH_SCALAR_" + std::to_string(i) + "_LOC");
542 pointer += 32;
543 }
544 print_header_centered("SHPLEMINI - RUNTIME MEMORY - BATCH SCALARS COMPLETE");
545
546 // INVERSIONS
547 print_header_centered("SHPLEMINI - RUNTIME MEMORY - INVERSIONS");
548
549 print_fr(pointer, "GEMINI_R_INV_LOC");
550 pointer += 32;
551
552 // ZK: LIBRA_SUBGROUP_DENOM
553 if (config.is_zk) {
554 print_fr(pointer, "LIBRA_SUBGROUP_DENOM_LOC");
555 pointer += 32;
556 }
557
558 // Batched evaluation accumulator inversions
559 for (int i = 0; i < log_n; ++i) {
560 print_fr(pointer, "BATCH_EVALUATION_ACCUMULATOR_INVERSION_" + std::to_string(i) + "_LOC");
561 pointer += 32;
562 }
563
564 out << "\n";
565 print_fr(pointer, "CONSTANT_TERM_ACCUMULATOR_LOC");
566 pointer += 32;
567
568 out << "\n";
569 print_fr(pointer, "POS_INVERTED_DENOMINATOR");
570 pointer += 32;
571 print_fr(pointer, "NEG_INVERTED_DENOMINATOR");
572 pointer += 32;
573
574 out << "\n";
575 out << "// LOG_N challenge pow minus u\n";
576 for (int i = 0; i < log_n; ++i) {
577 print_fr(pointer, "INVERTED_CHALLENGE_POW_MINUS_U_" + std::to_string(i) + "_LOC");
578 pointer += 32;
579 }
580
581 out << "\n";
582 out << "// LOG_N pos_inverted_off\n";
583 for (int i = 0; i < log_n; ++i) {
584 print_fr(pointer, "POS_INVERTED_DENOM_" + std::to_string(i) + "_LOC");
585 pointer += 32;
586 }
587
588 out << "\n";
589 out << "// LOG_N neg_inverted_off\n";
590 for (int i = 0; i < log_n; ++i) {
591 print_fr(pointer, "NEG_INVERTED_DENOM_" + std::to_string(i) + "_LOC");
592 pointer += 32;
593 }
594
595 out << "\n";
596 for (int i = 0; i < log_n; ++i) {
597 print_fr(pointer, "FOLD_POS_EVALUATIONS_" + std::to_string(i) + "_LOC");
598 pointer += 32;
599 }
600
601 print_header_centered("SHPLEMINI RUNTIME MEMORY - INVERSIONS - COMPLETE");
602 print_header_centered("SHPLEMINI RUNTIME MEMORY - COMPLETE");
603
604 // Temporary space for batch inversions
605 out << "\n";
606 for (int i = 0; i < config.barycentric_domain_size * log_n; ++i) {
607 print_fr(pointer, "BARYCENTRIC_TEMP_" + std::to_string(i) + "_LOC");
608 pointer += 32;
609 }
610
611 print_fr(pointer, "PUBLIC_INPUTS_DENOM_TEMP_LOC");
612 pointer += 32;
613 print_fr(pointer, "GEMINI_R_INV_TEMP_LOC");
614 pointer += 32;
615 // ZK: LIBRA_SUBGROUP_DENOM_TEMP_LOC
616 if (config.is_zk) {
617 print_fr(pointer, "LIBRA_SUBGROUP_DENOM_TEMP_LOC");
618 pointer += 32;
619 }
620 print_fr(pointer, "BATCH_PRODUCT_TEMP_LOC");
621 pointer += 32;
622
623 // Temporary space
624 print_header_centered("Temporary space");
625 for (int i = 0; i < 3 * log_n; ++i) {
626 print_fr(pointer, "TEMP_" + std::to_string(i) + "_LOC");
627 pointer += 32;
628 }
629
630 // ZK: Consistency check scratch space
631 if (config.is_zk) {
632 const int active_challenge_poly_length = 1 + log_n * config.batched_relation_partial_length;
633 const int consistency_scratch_length = active_challenge_poly_length + 1;
634 print_header_centered("Small subgroup IPA");
635 out << "// Allocate only the active challenge-poly prefix and the extra denominator/product slot\n";
636 for (int i = 0; i < active_challenge_poly_length; ++i) {
637 print_fr(pointer, "CHALLENGE_POLY_LAGRANGE_BASE_" + std::to_string(i));
638 pointer += 32;
639 }
640
641 out << "\n";
642 for (int i = 0; i < consistency_scratch_length; ++i) {
643 print_fr(pointer, "CONSISTENCY_DENOMINATORS_BASE_" + std::to_string(i));
644 pointer += 32;
645 }
646
647 out << "\n";
648 for (int i = 0; i < consistency_scratch_length; ++i) {
649 print_fr(pointer, "CONSISTENCY_PRODUCTS_BASE_" + std::to_string(i));
650 pointer += 32;
651 }
652
653 out << "\n";
654 out << "// LIBRA_UNIVARIATES_LENGTH = BATCHED_RELATION_PARTIAL_LENGTH = " << std::dec
655 << config.batched_relation_partial_length << "\n";
656 out << "uint256 internal constant LIBRA_UNIVARIATES_LENGTH = " << std::showbase << std::hex
657 << config.batched_relation_partial_length << ";\n";
658 out << "uint256 internal constant LIBRA_UNIVARIATES_LENGTH_MINUS_ONE = " << std::showbase << std::hex
659 << config.batched_relation_partial_length - 1 << ";\n";
660 out << "// 1/SUBGROUP_SIZE mod p (precomputed constant)\n";
661
662 out << "// 1/256 mod p, computed as pow(256, p-2, p) where p = BN254 scalar field modulus\n";
663 out << "// 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001\n";
664 out << "uint256 internal constant INV_SUBGROUP_SIZE = "
665 "0x3033ea246e506e898e97f570caffd704cb0bb460313fb720b29e139e5c100001;\n";
666 }
667
668 print_fr(pointer, "LATER_SCRATCH_SPACE");
669 pointer += 32;
670 print_header_centered("Temporary space - COMPLETE");
671
672 // Scratch space aliases
673 out << "\n";
674 out << "// Aliases for scratch space\n";
675 out << "// Scratch space aliases at 0x00-0x40\n";
676 out << "// Phase 1 (sumcheck rounds): CHALL_POW_LOC, SUMCHECK_U_LOC, GEMINI_A_LOC\n";
677 out << "// Phase 2 (shplemini batch scalars): SS_POS_INV_DENOM_LOC, SS_NEG_INV_DENOM_LOC, SS_GEMINI_EVALS_LOC\n";
678 out << "// These phases do not overlap in execution time.\n";
679
680 print_fr(0x00, "CHALL_POW_LOC");
681 print_fr(0x20, "SUMCHECK_U_LOC");
682 print_fr(0x40, "GEMINI_A_LOC");
683 out << "\n";
684 print_fr(0x00, "SS_POS_INV_DENOM_LOC");
685 print_fr(0x20, "SS_NEG_INV_DENOM_LOC");
686 print_fr(0x40, "SS_GEMINI_EVALS_LOC");
687
688 // EC aliases
689 out << "\n\n";
690 print_header_centered("SUMCHECK - MEMORY ALIASES");
691
692 return out.str();
693}
694
696inline void replace_memory_layout(std::string& template_str, int log_n, const MemoryLayoutConfig& mem_config)
697{
698 std::string::size_type start_pos = template_str.find("// {{ SECTION_START MEMORY_LAYOUT }}");
699 std::string::size_type end_pos = template_str.find("// {{ SECTION_END MEMORY_LAYOUT }}");
700
701 if (start_pos != std::string::npos && end_pos != std::string::npos) {
702 std::string::size_type start_line_end = template_str.find("\n", start_pos);
703 std::string generated_code = generate_memory_offsets(log_n, mem_config);
704 template_str = template_str.substr(0, start_line_end + 1) + generated_code + template_str.substr(end_pos);
705 }
706}
707
708// Apply all template parameter substitutions for the optimized verifier.
709// This handles VK hash, circuit parameters, gemini fold lengths, and all 56 VK field substitutions.
710// The is_zk flag controls ZK-specific parameters (BATCHED_RELATION_PARTIAL_LENGTH_MINUS_ONE,
711// extra gemini eval terms).
712template <typename VK>
713inline void apply_template_params(std::string& template_str, VK const& verification_key, bool is_zk)
714{
715 auto set_template_param = [&template_str](const std::string& key, const std::string& value) {
716 std::string::size_type pos = 0;
717 std::string pattern = "{{ " + key + " }}";
718 while ((pos = template_str.find(pattern, pos)) != std::string::npos) {
719 template_str.replace(pos, pattern.length(), value);
720 pos += value.length();
721 }
722 };
723
724 auto log_circuit_size = verification_key->log_circuit_size;
725
726 // larger overflows int, sanity check not 0
727 if (log_circuit_size > 31 || log_circuit_size < 1) {
728 info("log_circuit_size out of bounds | 0 < x < 31 | x = " + std::to_string(log_circuit_size));
729 std::abort();
730 }
731
732 if (verification_key->num_public_inputs < bb::PAIRING_POINTS_SIZE) {
733 info("invariant broken: public input points are smaller than pairing points (they are usually appended)");
734 std::abort();
735 }
736
737 set_template_param("VK_HASH", field_to_hex(verification_key->hash()));
738 set_template_param("CIRCUIT_SIZE", std::to_string(1 << log_circuit_size));
739 set_template_param("LOG_CIRCUIT_SIZE", std::to_string(log_circuit_size));
740 set_template_param("NUM_PUBLIC_INPUTS", std::to_string(verification_key->num_public_inputs));
741 // REAL_NUM_PUBLIC_INPUTS excludes the 8 pairing point limbs that are part of the proof structure
742 set_template_param("REAL_NUM_PUBLIC_INPUTS",
743 std::to_string(verification_key->num_public_inputs - bb::PAIRING_POINTS_SIZE));
744 set_template_param("LOG_N_MINUS_ONE", std::to_string(log_circuit_size - 1));
745
746 // ZK: BATCHED_RELATION_PARTIAL_LENGTH - 1 = 8 (constant for ZK, domain size is always 9)
747 if (is_zk) {
748 set_template_param("BATCHED_RELATION_PARTIAL_LENGTH_MINUS_ONE", "8");
749 set_template_param("NUMBER_OF_LAGRANGE_BASES", std::to_string(log_circuit_size * 9));
750 set_template_param("NUMBER_OF_LAGRANGE_BASES_PLUS_ONE", std::to_string(log_circuit_size * 9 + 1));
751 // Libra batch scalar indices: placed after gemini fold scalars (38 + LOG_N-1 = 37 + LOG_N)
752 set_template_param("LIBRA_BATCH_SCALAR_0", std::to_string(37 + log_circuit_size));
753 set_template_param("LIBRA_BATCH_SCALAR_1", std::to_string(38 + log_circuit_size));
754 set_template_param("LIBRA_BATCH_SCALAR_2", std::to_string(39 + log_circuit_size));
755 }
756
757 uint32_t gemini_fold_univariate_length = static_cast<uint32_t>((log_circuit_size - 1) * 0x40);
758 uint32_t gemini_fold_univariate_hash_length = static_cast<uint32_t>(gemini_fold_univariate_length + 0x20);
759 // ZK: gemini evals include log_n evals + 4 libra poly evals
760 uint32_t gemini_evals_length =
761 is_zk ? static_cast<uint32_t>((log_circuit_size + 4) * 0x20) : static_cast<uint32_t>(log_circuit_size * 0x20);
762 uint32_t gemini_evals_hash_length = static_cast<uint32_t>(gemini_evals_length + 0x20);
763
764 set_template_param("GEMINI_FOLD_UNIVARIATE_LENGTH", int_to_hex(gemini_fold_univariate_length));
765 set_template_param("GEMINI_FOLD_UNIVARIATE_HASH_LENGTH", int_to_hex(gemini_fold_univariate_hash_length));
766 set_template_param("GEMINI_EVALS_LENGTH", int_to_hex(gemini_evals_length));
767 set_template_param("GEMINI_EVALS_HASH_LENGTH", int_to_hex(gemini_evals_hash_length));
768
769 // Verification Key — use g1_to_xy_hex so identity-commitment selectors
770 // (e.g. selectors that commit to identically-zero polynomials) emit the
771 // EIP-196 canonical (0, 0) instead of the raw affine sentinel, which is
772 // off-curve and rejected by the ecMul/ecAdd precompiles.
773 auto set_g1_template_param = [&](const std::string& name_prefix, const auto& point) {
774 const auto [x_hex, y_hex] = g1_to_xy_hex(point);
775 set_template_param(name_prefix + "_X_LOC", x_hex);
776 set_template_param(name_prefix + "_Y_LOC", y_hex);
777 };
778 set_g1_template_param("Q_L", verification_key->q_l);
779 set_g1_template_param("Q_R", verification_key->q_r);
780 set_g1_template_param("Q_O", verification_key->q_o);
781 set_g1_template_param("Q_4", verification_key->q_4);
782 set_g1_template_param("Q_M", verification_key->q_m);
783 set_g1_template_param("Q_C", verification_key->q_c);
784 set_g1_template_param("Q_LOOKUP", verification_key->q_lookup);
785 set_g1_template_param("Q_ARITH", verification_key->q_arith);
786 set_g1_template_param("Q_DELTA_RANGE", verification_key->q_delta_range);
787 set_g1_template_param("Q_ELLIPTIC", verification_key->q_elliptic);
788 set_g1_template_param("Q_MEMORY", verification_key->q_memory);
789 set_g1_template_param("Q_NNF", verification_key->q_nnf);
790 set_g1_template_param("Q_POSEIDON_2_EXTERNAL", verification_key->q_poseidon2_external);
791 set_g1_template_param("Q_POSEIDON_2_INTERNAL", verification_key->q_poseidon2_internal);
792 set_g1_template_param("SIGMA_1", verification_key->sigma_1);
793 set_g1_template_param("SIGMA_2", verification_key->sigma_2);
794 set_g1_template_param("SIGMA_3", verification_key->sigma_3);
795 set_g1_template_param("SIGMA_4", verification_key->sigma_4);
796 set_g1_template_param("TABLE_1", verification_key->table_1);
797 set_g1_template_param("TABLE_2", verification_key->table_2);
798 set_g1_template_param("TABLE_3", verification_key->table_3);
799 set_g1_template_param("TABLE_4", verification_key->table_4);
800 set_g1_template_param("ID_1", verification_key->id_1);
801 set_g1_template_param("ID_2", verification_key->id_2);
802 set_g1_template_param("ID_3", verification_key->id_3);
803 set_g1_template_param("ID_4", verification_key->id_4);
804 set_g1_template_param("LAGRANGE_FIRST", verification_key->lagrange_first);
805 set_g1_template_param("LAGRANGE_LAST", verification_key->lagrange_last);
806}
#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)