Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
graph.cpp
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#include "./graph.hpp"
11#include <algorithm>
12#include <array>
13#include <iomanip>
14#include <optional>
15#include <stack>
16
17using namespace bb::plookup;
18using namespace bb;
19
20namespace cdg {
21
29template <typename FF, typename CircuitBuilder>
31{
32 auto blocks_data = circuit_builder.blocks.get();
33 for (size_t i = 0; i < blocks_data.size(); i++) {
34 if (std::addressof(blocks_data[i]) == std::addressof(block)) {
35 return i;
36 }
37 }
38 return std::nullopt;
39}
40
55template <typename FF, typename CircuitBuilder>
56inline void StaticAnalyzer_<FF, CircuitBuilder>::process_gate_variables(std::vector<uint32_t>& gate_variables,
57 size_t gate_index,
58 size_t block_idx)
59{
60 auto unique_variables = std::unique(gate_variables.begin(), gate_variables.end());
61 gate_variables.erase(unique_variables, gate_variables.end());
62 if (gate_variables.empty()) {
63 return;
64 }
65 for (auto& var_idx : gate_variables) {
66 KeyPair key = std::make_pair(var_idx, block_idx);
67 variable_gates[key].emplace_back(gate_index);
68 }
69 for (const auto& variable_index : gate_variables) {
70 variables_gate_counts[variable_index] += 1;
71 }
72}
73
85template <typename FF, typename CircuitBuilder>
87 size_t index, size_t block_idx, auto& blk)
88{
89 auto q_arith = blk.q_arith()[index];
90 std::vector<uint32_t> all_variables;
91 std::vector<uint32_t> gate_variables;
92 std::vector<uint32_t> minigate_variables;
93 std::vector<std::vector<uint32_t>> all_gates_variables;
94 if (q_arith.is_zero()) {
95 return {};
96 }
97 auto q_m = blk.q_m()[index];
98 auto q_1 = blk.q_1()[index];
99 auto q_2 = blk.q_2()[index];
100 auto q_3 = blk.q_3()[index];
101 auto q_4 = blk.q_4()[index];
102
103 uint32_t left_idx = blk.w_l()[index];
104 uint32_t right_idx = blk.w_r()[index];
105 uint32_t out_idx = blk.w_o()[index];
106 uint32_t fourth_idx = blk.w_4()[index];
107 if (q_m.is_zero() && q_1 == 1 && q_2.is_zero() && q_3.is_zero() && q_4.is_zero() && q_arith == FF::one()) {
108 // this is fixed_witness gate. So, variable index contains in left wire. So, we have to take only it.
109 fixed_variables.insert(this->to_real(left_idx));
110 } else if (!q_m.is_zero() || q_1 != FF::one() || !q_2.is_zero() || !q_3.is_zero() || !q_4.is_zero()) {
111 // this is not the gate for fix_witness, so we have to process this gate
112 if (!q_m.is_zero()) {
113 gate_variables.emplace_back(left_idx);
114 gate_variables.emplace_back(right_idx);
115 } else {
116 if (!q_1.is_zero()) {
117 gate_variables.emplace_back(left_idx);
118 }
119 if (!q_2.is_zero()) {
120 gate_variables.emplace_back(right_idx);
121 }
122 }
123
124 if (!q_3.is_zero()) {
125 gate_variables.emplace_back(out_idx);
126 }
127 if (!q_4.is_zero()) {
128 gate_variables.emplace_back(fourth_idx);
129 }
130 if (q_arith == FF(2)) {
131 // We have to use w_4_shift from the next gate
132 // if and only if the current gate isn't last, cause we can't
133 // look into the next gate
134 if (index != blk.size() - 1) {
135 gate_variables.emplace_back(blk.w_4()[index + 1]);
136 }
137 }
138 if (q_arith == FF(3)) {
139 // In this gate mini gate is enabled, we have 2 equations:
140 // q_1 * w_1 + q_2 * w_2 + q_3 * w_3 + q_4 * w_4 + q_c + 2 * w_4_omega = 0
141 // w_1 + w_4 - w_1_omega + q_m = 0
142 minigate_variables.emplace_back(left_idx);
143 minigate_variables.emplace_back(fourth_idx);
144 if (index != blk.size() - 1) {
145 gate_variables.emplace_back(blk.w_4()[index + 1]);
146 minigate_variables.emplace_back(blk.w_l()[index + 1]);
147 }
148 }
149 }
150 gate_variables = to_real(gate_variables);
151 minigate_variables = to_real(minigate_variables);
152 all_variables.reserve(gate_variables.size() + minigate_variables.size());
153 all_variables.insert(all_variables.end(), gate_variables.begin(), gate_variables.end());
154 all_variables.insert(all_variables.end(), minigate_variables.begin(), minigate_variables.end());
155 process_gate_variables(all_variables, index, block_idx);
156 return all_variables;
157}
158
170template <typename FF, typename CircuitBuilder>
172 size_t index, size_t block_idx, auto& blk)
173{
174 std::vector<uint32_t> gate_variables;
175 if (!blk.q_elliptic()[index].is_zero()) {
176 std::vector<uint32_t> first_row_variables;
177 std::vector<uint32_t> second_row_variables;
178 gate_variables.reserve(6);
179 bool is_elliptic_add_gate = !blk.q_1()[index].is_zero() && blk.q_m()[index].is_zero();
180 bool is_elliptic_dbl_gate = blk.q_1()[index].is_zero() && blk.q_m()[index] == FF::one();
181 first_row_variables.emplace_back(blk.w_r()[index]);
182 first_row_variables.emplace_back(blk.w_o()[index]);
183 if (index != blk.size() - 1) {
184 if (is_elliptic_add_gate) {
185 // if this gate is ecc_add_gate, we have to get indices x2, x3, y3, y2 from the next gate
186 second_row_variables.emplace_back(blk.w_l()[index + 1]);
187 second_row_variables.emplace_back(blk.w_r()[index + 1]);
188 second_row_variables.emplace_back(blk.w_o()[index + 1]);
189 second_row_variables.emplace_back(blk.w_4()[index + 1]);
190 }
191 if (is_elliptic_dbl_gate) {
192 // if this gate is ecc_dbl_gate, we have to indices x3, y3 from right and output wires
193 second_row_variables.emplace_back(blk.w_r()[index + 1]);
194 second_row_variables.emplace_back(blk.w_o()[index + 1]);
195 }
196 }
197 if (!first_row_variables.empty()) {
198 first_row_variables = to_real(first_row_variables);
199 process_gate_variables(first_row_variables, index, block_idx);
200 gate_variables.insert(gate_variables.end(), first_row_variables.cbegin(), first_row_variables.cend());
201 }
202 if (!second_row_variables.empty()) {
203 second_row_variables = to_real(second_row_variables);
204 process_gate_variables(second_row_variables, index, block_idx);
205 gate_variables.insert(gate_variables.end(), second_row_variables.cbegin(), second_row_variables.cend());
206 }
207 }
208 return gate_variables;
209}
210
222template <typename FF, typename CircuitBuilder>
224 size_t index, size_t blk_idx, auto& block)
225{
226 std::vector<uint32_t> gate_variables = {};
227 if (!block.q_delta_range()[index].is_zero()) {
228 std::vector<uint32_t> row_variables = {
229 block.w_l()[index], block.w_r()[index], block.w_o()[index], block.w_4()[index]
230 };
231 /*
232 sometimes process_range_list function adds variables with zero_idx in beginning of vector with indices
233 in order to pad a size of indices to gate width. But tool has to ignore these additional variables
234 */
235 for (const auto& var_idx : row_variables) {
236 if (var_idx != circuit_builder.zero_idx()) {
237 gate_variables.emplace_back(var_idx);
238 }
239 }
240 if (index != block.size() - 1 && block.w_l()[index + 1] != circuit_builder.zero_idx()) {
241 gate_variables.emplace_back(block.w_l()[index + 1]);
242 }
243 }
244 gate_variables = to_real(gate_variables);
245 process_gate_variables(gate_variables, index, blk_idx);
246 return gate_variables;
247}
248
260template <typename FF, typename CircuitBuilder>
262 size_t blk_idx,
263 auto& block)
264{
265 std::vector<uint32_t> gate_variables;
266 auto q_lookup = block.q_lookup()[index];
267 if (!q_lookup.is_zero()) {
268 gate_variables.reserve(6);
269 auto q_2 = block.q_2()[index];
270 auto q_m = block.q_m()[index];
271 auto q_c = block.q_c()[index];
272 gate_variables.emplace_back(block.w_l()[index]);
273 gate_variables.emplace_back(block.w_r()[index]);
274 gate_variables.emplace_back(block.w_o()[index]);
275 if (index < block.size() - 1) {
276 if (!q_2.is_zero()) {
277 gate_variables.emplace_back(block.w_l()[index + 1]);
278 }
279 if (!q_m.is_zero()) {
280 gate_variables.emplace_back(block.w_r()[index + 1]);
281 }
282 if (!q_c.is_zero()) {
283 gate_variables.emplace_back(block.w_o()[index + 1]);
284 }
285 }
286 gate_variables = to_real(gate_variables);
287 process_gate_variables(gate_variables, index, blk_idx);
288 }
289 return gate_variables;
290}
291
301template <typename FF, typename CircuitBuilder>
303 size_t blk_idx,
304 auto& block)
305{
306 std::vector<uint32_t> gate_variables;
307 auto internal_selector = block.q_poseidon2_internal()[index];
308 auto external_selector = block.q_poseidon2_external()[index];
309 if (!internal_selector.is_zero() || !external_selector.is_zero()) {
310 gate_variables.reserve(8);
311 gate_variables.emplace_back(block.w_l()[index]);
312 gate_variables.emplace_back(block.w_r()[index]);
313 gate_variables.emplace_back(block.w_o()[index]);
314 gate_variables.emplace_back(block.w_4()[index]);
315 if (index != block.size() - 1) {
316 gate_variables.emplace_back(block.w_l()[index + 1]);
317 gate_variables.emplace_back(block.w_r()[index + 1]);
318 gate_variables.emplace_back(block.w_o()[index + 1]);
319 gate_variables.emplace_back(block.w_4()[index + 1]);
320 }
321 gate_variables = to_real(gate_variables);
322 process_gate_variables(gate_variables, index, blk_idx);
323 }
324 return gate_variables;
325}
326
336template <typename FF, typename CircuitBuilder>
338 size_t blk_idx,
339 auto& block)
340{
341 std::vector<uint32_t> gate_variables;
342 if (!block.q_memory()[index].is_zero()) {
343 gate_variables.reserve(8);
344 auto q_1 = block.q_1()[index];
345 auto q_2 = block.q_2()[index];
346 auto q_3 = block.q_3()[index];
347 auto q_4 = block.q_4()[index];
348 if (q_1 == FF::one() && q_4 == FF::one()) {
349 BB_ASSERT(q_3.is_zero());
350 // ram timestamp check
351 if (index < block.size() - 1) {
352 gate_variables.insert(gate_variables.end(),
353 { block.w_r()[index + 1],
354 block.w_r()[index],
355 block.w_l()[index],
356 block.w_l()[index + 1],
357 block.w_o()[index] });
358 }
359 } else if (q_1 == FF::one() && q_2 == FF::one()) {
360 BB_ASSERT(q_3.is_zero());
361 // rom constitency check
362 if (index < block.size() - 1) {
363 gate_variables.insert(
364 gate_variables.end(),
365 { block.w_l()[index], block.w_l()[index + 1], block.w_4()[index], block.w_4()[index + 1] });
366 }
367 } else {
368 // ram constitency check
369 if (!q_3.is_zero()) {
370 if (index < block.size() - 1) {
371 gate_variables.insert(gate_variables.end(),
372 { block.w_o()[index],
373 block.w_4()[index],
374 block.w_l()[index + 1],
375 block.w_r()[index + 1],
376 block.w_o()[index + 1],
377 block.w_4()[index + 1] });
378 }
379 }
380 }
381 }
382 gate_variables = to_real(gate_variables);
383 process_gate_variables(gate_variables, index, blk_idx);
384 return gate_variables;
385}
386
396template <typename FF, typename CircuitBuilder>
398 size_t index, size_t blk_idx, auto& block)
399{
400 std::vector<uint32_t> gate_variables;
401 if (!block.q_nnf()[index].is_zero()) {
402 gate_variables.reserve(8);
403 [[maybe_unused]] auto q_1 = block.q_1()[index];
404 auto q_2 = block.q_2()[index];
405 auto q_3 = block.q_3()[index];
406 auto q_4 = block.q_4()[index];
407 auto q_m = block.q_m()[index];
408
409 auto w_l = block.w_l()[index];
410 auto w_r = block.w_r()[index];
411 auto w_o = block.w_o()[index];
412 auto w_4 = block.w_4()[index];
413 if (q_3 == FF::one() && q_4 == FF::one()) {
414 // bigfield limb accumulation 1
415 if (index < block.size() - 1) {
416 gate_variables.insert(gate_variables.end(),
417 { w_l, w_r, w_o, w_4, block.w_l()[index + 1], block.w_r()[index + 1] }); // 6
418 }
419 } else if (q_3 == FF::one() && q_m == FF::one()) {
420 // bigfield limb accumulation 2
421 if (index < block.size() - 1) {
422 gate_variables.insert(gate_variables.end(),
423 { w_o,
424 w_4,
425 block.w_l()[index + 1],
426 block.w_r()[index + 1],
427 block.w_o()[index + 1],
428 block.w_4()[index + 1] });
429 }
430 } else if (q_2 == FF::one() && (q_3 == FF::one() || q_4 == FF::one() || q_m == FF::one())) {
431 // bigfield product cases
432 if (index < block.size() - 1) {
433 std::vector<uint32_t> limb_subproduct_vars = {
434 w_l, w_r, block.w_l()[index + 1], block.w_r()[index + 1]
435 };
436 if (q_3 == FF::one()) {
437 // bigfield product 1
438 BB_ASSERT(q_4.is_zero() && q_m.is_zero());
439 gate_variables.insert(
440 gate_variables.end(), limb_subproduct_vars.begin(), limb_subproduct_vars.end());
441 gate_variables.insert(gate_variables.end(), { w_o, w_4 });
442 }
443 if (q_4 == FF::one()) {
444 // bigfield product 2
445 BB_ASSERT(q_3.is_zero() && q_m.is_zero());
446 std::vector<uint32_t> non_native_field_gate_2 = { w_l, w_4, w_r, w_o, block.w_o()[index + 1] };
447 gate_variables.insert(
448 gate_variables.end(), non_native_field_gate_2.begin(), non_native_field_gate_2.end());
449 gate_variables.emplace_back(block.w_4()[index + 1]);
450 gate_variables.insert(
451 gate_variables.end(), limb_subproduct_vars.begin(), limb_subproduct_vars.end());
452 }
453 if (q_m == FF::one()) {
454 // bigfield product 3
455 BB_ASSERT(q_4.is_zero() && q_3.is_zero());
456 gate_variables.insert(
457 gate_variables.end(), limb_subproduct_vars.begin(), limb_subproduct_vars.end());
458 gate_variables.insert(gate_variables.end(),
459 { w_4, block.w_o()[index + 1], block.w_4()[index + 1] });
460 }
461 }
462 }
463 }
464 gate_variables = to_real(gate_variables);
465 process_gate_variables(gate_variables, index, blk_idx);
466 return gate_variables;
467}
468
476template <typename FF, typename CircuitBuilder>
478 const bb::RomTranscript& rom_array)
479{
480 // Every RomTranscript data structure has 2 main components that are interested for static analyzer:
481 // 1) records contains values that were put in the gate, we can use them to create connections between variables
482 // 2) states contains values witness indexes that we can find in the ROM record in the RomTrascript, so we can
483 // ignore state of the ROM transcript, because we still can connect all variables using variables from records.
484 std::vector<uint32_t> rom_table_variables;
485 if (std::optional<size_t> blk_idx = find_block_index(circuit_builder.blocks.memory); blk_idx) {
486 // Every RomTranscript data structure has 2 main components that are interested for static analyzer:
487 // 1) records contains values that were put in the gate, we can use them to create connections between variables
488 // 2) states contains values witness indexes that we can find in the ROM record in the RomTrascript, so we can
489 // ignore state of the ROM transcript, because we still can connect all variables using variables from records.
490 for (const auto& record : rom_array.records) {
491 std::vector<uint32_t> gate_variables;
492 size_t gate_index = record.gate_index;
493
494 auto q_1 = circuit_builder.blocks.memory.q_1()[gate_index];
495 auto q_2 = circuit_builder.blocks.memory.q_2()[gate_index];
496 auto q_3 = circuit_builder.blocks.memory.q_3()[gate_index];
497 auto q_4 = circuit_builder.blocks.memory.q_4()[gate_index];
498 auto q_m = circuit_builder.blocks.memory.q_m()[gate_index];
499 auto q_c = circuit_builder.blocks.memory.q_c()[gate_index];
500
501 auto index_witness = record.index_witness;
502 auto vc1_witness = record.value_column1_witness; // state[0] from RomTranscript
503 auto vc2_witness = record.value_column2_witness; // state[1] from RomTranscript
504 auto record_witness = record.record_witness;
505
506 if (q_1 == FF::one() && q_m == FF::one() && q_2.is_zero() && q_3.is_zero() && q_4.is_zero() &&
507 q_c.is_zero()) {
508 // By default ROM read gate uses variables (w_1, w_2, w_3, w_4) = (index_witness, vc1_witness,
509 // vc2_witness, record_witness) So we can update all of them
510 gate_variables.emplace_back(index_witness);
511 if (vc1_witness != circuit_builder.zero_idx()) {
512 gate_variables.emplace_back(vc1_witness);
513 }
514 if (vc2_witness != circuit_builder.zero_idx()) {
515 gate_variables.emplace_back(vc2_witness);
516 }
517 gate_variables.emplace_back(record_witness);
518 }
519 gate_variables = to_real(gate_variables);
520 process_gate_variables(gate_variables, gate_index, *blk_idx);
521 // after process_gate_variables function gate_variables constists of real variables indexes, so we can
522 // add all this variables in the final vector to connect all of them
523 if (!gate_variables.empty()) {
524 rom_table_variables.insert(rom_table_variables.end(), gate_variables.begin(), gate_variables.end());
525 }
526 }
527 }
528 return rom_table_variables;
529}
530
539template <typename FF, typename CircuitBuilder>
541 const bb::RamTranscript& ram_array)
542{
543 std::vector<uint32_t> ram_table_variables;
544 if (std::optional<size_t> blk_idx = find_block_index(circuit_builder.blocks.memory); blk_idx) {
545 for (const auto& record : ram_array.records) {
546 std::vector<uint32_t> gate_variables;
547 size_t gate_index = record.gate_index;
548
549 auto q_1 = circuit_builder.blocks.memory.q_1()[gate_index];
550 auto q_2 = circuit_builder.blocks.memory.q_2()[gate_index];
551 auto q_3 = circuit_builder.blocks.memory.q_3()[gate_index];
552 auto q_4 = circuit_builder.blocks.memory.q_4()[gate_index];
553 auto q_m = circuit_builder.blocks.memory.q_m()[gate_index];
554 auto q_c = circuit_builder.blocks.memory.q_c()[gate_index];
555
556 auto index_witness = record.index_witness;
557 auto timestamp_witness = record.timestamp_witness;
558 auto value_witness = record.value_witness;
559 auto record_witness = record.record_witness;
560
561 if (q_1 == FF::one() && q_m == FF::one() && q_2.is_zero() && q_3.is_zero() && q_4.is_zero() &&
562 (q_c.is_zero() || q_c == FF::one())) {
563 // By default RAM read/write gate uses variables (w_1, w_2, w_3, w_4) = (index_witness,
564 // timestamp_witness, value_witness, record_witness) So we can update all of them
565 gate_variables.emplace_back(index_witness);
566 if (timestamp_witness != circuit_builder.zero_idx()) {
567 gate_variables.emplace_back(timestamp_witness);
568 }
569 if (value_witness != circuit_builder.zero_idx()) {
570 gate_variables.emplace_back(value_witness);
571 }
572 gate_variables.emplace_back(record_witness);
573 }
574 gate_variables = to_real(gate_variables);
575 process_gate_variables(gate_variables, gate_index, *blk_idx);
576 // after process_gate_variables function gate_variables constists of real variables indexes, so we can add
577 // all these variables in the final vector to connect all of them
578 ram_table_variables.insert(ram_table_variables.end(), gate_variables.begin(), gate_variables.end());
579 }
580 }
581 return ram_table_variables;
582}
583
594template <typename FF, typename CircuitBuilder>
596 size_t block_idx,
597 auto& blk)
598{
599 std::vector<uint32_t> gate_variables;
600 if (!blk.q_busread()[index].is_zero()) {
601 gate_variables.insert(gate_variables.end(), { blk.w_l()[index], blk.w_r()[index] });
602 gate_variables = to_real(gate_variables);
603 process_gate_variables(gate_variables, index, block_idx);
604 }
605 return gate_variables;
606}
607
619template <typename FF, typename CircuitBuilder>
621 size_t block_idx,
622 auto& blk)
623{
624 std::vector<uint32_t> gate_variables;
625 std::vector<uint32_t> first_row_variables;
626 std::vector<uint32_t> second_row_variables;
627 auto w1 = blk.w_l()[index]; // get opcode of operation, because function get_ecc_op_idx returns type
628 // uint32_t and it adds as w1
629 if (w1 != circuit_builder.zero_idx()) {
630 // this is opcode and start of the UltraOp element
631 first_row_variables.insert(
632 first_row_variables.end(),
633 { w1, blk.w_r()[index], blk.w_o()[index], blk.w_4()[index] }); // add op, x_lo, x_hi, y_lo
634 if (index < blk.size() - 1) {
635 second_row_variables.insert(
636 second_row_variables.end(),
637 { blk.w_r()[index + 1], blk.w_o()[index + 1], blk.w_4()[index + 1] }); // add y_hi, z1, z2
638 }
639 first_row_variables = to_real(first_row_variables);
640 second_row_variables = to_real(second_row_variables);
641 process_gate_variables(first_row_variables, index, block_idx);
642 process_gate_variables(second_row_variables, index, block_idx);
643 }
644 if (!first_row_variables.empty()) {
645 gate_variables.insert(gate_variables.end(), first_row_variables.cbegin(), first_row_variables.cend());
646 }
647 if (!second_row_variables.empty()) {
648 gate_variables.insert(gate_variables.end(), second_row_variables.cbegin(), second_row_variables.cend());
649 }
650 return gate_variables;
651}
652
653template <typename FF, typename CircuitBuilder> void StaticAnalyzer_<FF, CircuitBuilder>::process_execution_trace()
654{
655 auto block_data = circuit_builder.blocks.get();
656
657 // We have to determine pub_inputs block index based on circuit builder type, because we have to skip it.
658 // If type of CircuitBuilder is UltraCircuitBuilder, the pub_inputs block is the first block so we can set
659 // pub_inputs_block_idx
660 size_t pub_inputs_block_idx = 0;
661
662 // For MegaCircuitBuilder, pub_inputs block has index 3
663 if constexpr (IsMegaBuilder<CircuitBuilder>) {
664 pub_inputs_block_idx = 3;
665 }
666
667 for (size_t blk_idx = 0; blk_idx < block_data.size(); blk_idx++) {
668 if (block_data[blk_idx].size() == 0 || blk_idx == pub_inputs_block_idx) {
669 continue;
670 }
671 std::vector<uint32_t> sorted_variables;
672 std::vector<uint32_t> eccop_variables;
673 for (size_t gate_idx = 0; gate_idx < block_data[blk_idx].size(); gate_idx++) {
675 get_arithmetic_gate_connected_component(gate_idx, blk_idx, block_data[blk_idx]),
676 get_elliptic_gate_connected_component(gate_idx, blk_idx, block_data[blk_idx]),
677 get_plookup_gate_connected_component(gate_idx, blk_idx, block_data[blk_idx]),
678 get_poseido2s_gate_connected_component(gate_idx, blk_idx, block_data[blk_idx]),
679 get_non_native_field_gate_connected_component(gate_idx, blk_idx, block_data[blk_idx]),
680 get_memory_gate_connected_component(gate_idx, blk_idx, block_data[blk_idx]),
681 get_sort_constraint_connected_component(gate_idx, blk_idx, block_data[blk_idx])
682 };
683 auto non_empty_count =
684 std::count_if(all_cc.begin(), all_cc.end(), [](const auto& vec) { return !vec.empty(); });
685 BB_ASSERT(non_empty_count < 2U);
686 auto not_empty_cc_it =
687 std::find_if(all_cc.begin(), all_cc.end(), [](const auto& vec) { return !vec.empty(); });
688 if (not_empty_cc_it != all_cc.end() && connect_variables) {
689 connect_all_variables_in_vector(*not_empty_cc_it);
690 }
691 if constexpr (IsMegaBuilder<CircuitBuilder>) {
692 // If type of CircuitBuilder is MegaCircuitBuilder, we'll try to process blocks like they can be
693 // databus or eccop
694 auto databus_variables = get_databus_connected_component(gate_idx, blk_idx, block_data[blk_idx]);
695 if (connect_variables) {
696 connect_all_variables_in_vector(databus_variables);
697 }
698 auto eccop_gate_variables = get_eccop_part_connected_component(gate_idx, blk_idx, block_data[blk_idx]);
699 if (connect_variables) {
700 if (!eccop_gate_variables.empty()) {
701 // The gotten vector of variables contains all variables from UltraOp element of the table
702 eccop_variables.insert(
703 eccop_variables.end(), eccop_gate_variables.begin(), eccop_gate_variables.end());
704 // if a current opcode is responsible for equality and reset, we have to connect all
705 // variables in global vector and clear it for the next parts
706 if (eccop_gate_variables[0] == circuit_builder.equality_op_idx) {
707 connect_all_variables_in_vector(eccop_variables);
708 eccop_variables.clear();
709 }
710 }
711 }
712 }
713 }
714 }
715
716 const auto& rom_arrays = circuit_builder.rom_ram_logic.rom_arrays;
717 if (!rom_arrays.empty()) {
718 for (const auto& rom_array : rom_arrays) {
719 std::vector<uint32_t> variable_indices = get_rom_table_connected_component(rom_array);
720 if (connect_variables) {
721 connect_all_variables_in_vector(variable_indices);
722 }
723 }
724 }
725
726 const auto& ram_arrays = circuit_builder.rom_ram_logic.ram_arrays;
727 if (!ram_arrays.empty()) {
728 for (const auto& ram_array : ram_arrays) {
729 std::vector<uint32_t> variable_indices = get_ram_table_connected_component(ram_array);
730 if (connect_variables) {
731 connect_all_variables_in_vector(variable_indices);
732 }
733 }
734 }
735}
736
759template <typename FF, typename CircuitBuilder>
761 : circuit_builder(circuit_builder)
762 , connect_variables(connect_variables)
763{
764 variables_gate_counts = std::unordered_map<uint32_t, size_t>(circuit_builder.real_variable_index.size());
767 variables_degree = std::unordered_map<uint32_t, size_t>(circuit_builder.real_variable_index.size());
768 for (const auto& variable_index : circuit_builder.real_variable_index) {
769 variables_gate_counts[variable_index] = 0;
770 variables_degree[variable_index] = 0;
771 variable_adjacency_lists[variable_index] = {};
772 }
775}
776
785template <typename FF, typename CircuitBuilder>
787{
788 constant_variable_indices_set.clear();
789 const auto& constant_variable_indices = circuit_builder.constant_variable_indices;
790 for (const auto& pair : constant_variable_indices) {
791 constant_variable_indices_set.insert(pair.second);
792 }
793}
794
802template <typename FF, typename CircuitBuilder>
804{
805 uint32_t real_variable_index = circuit_builder.real_variable_index[variable_index];
806 return constant_variable_indices_set.find(real_variable_index) == constant_variable_indices_set.end();
807}
808
819template <typename FF, typename CircuitBuilder>
820void StaticAnalyzer_<FF, CircuitBuilder>::connect_all_variables_in_vector(const std::vector<uint32_t>& variables_vector)
821{
822 if (variables_vector.empty()) {
823 return;
824 }
825 std::vector<uint32_t> filtered_variables_vector;
826 filtered_variables_vector.reserve(variables_vector.size());
827 // Only copy non-zero and non-constant variables
828 std::copy_if(variables_vector.begin(),
829 variables_vector.end(),
830 std::back_inserter(filtered_variables_vector),
831 [&](uint32_t variable_index) {
832 return variable_index != circuit_builder.zero_idx() &&
833 this->check_is_not_constant_variable(variable_index);
834 });
835 // Remove duplicates
836 auto unique_pointer = std::unique(filtered_variables_vector.begin(), filtered_variables_vector.end());
837 filtered_variables_vector.erase(unique_pointer, filtered_variables_vector.end());
838 if (filtered_variables_vector.size() < 2) {
839 return;
840 }
841 for (size_t i = 0; i < filtered_variables_vector.size() - 1; i++) {
842 add_new_edge(filtered_variables_vector[i], filtered_variables_vector[i + 1]);
843 }
844}
845
854template <typename FF, typename CircuitBuilder>
855void StaticAnalyzer_<FF, CircuitBuilder>::add_new_edge(const uint32_t& first_variable_index,
856 const uint32_t& second_variable_index)
857{
858 variable_adjacency_lists[first_variable_index].emplace_back(second_variable_index);
859 variable_adjacency_lists[second_variable_index].emplace_back(first_variable_index);
860 variables_degree[first_variable_index] += 1;
861 variables_degree[second_variable_index] += 1;
862}
863
873template <typename FF, typename CircuitBuilder>
875 std::unordered_set<uint32_t>& is_used,
876 std::vector<uint32_t>& connected_component)
877{
878 std::stack<uint32_t> variable_stack;
879 variable_stack.push(variable_index);
880 while (!variable_stack.empty()) {
881 uint32_t current_index = variable_stack.top();
882 variable_stack.pop();
883 if (!is_used.contains(current_index)) {
884 is_used.insert(current_index);
885 connected_component.emplace_back(current_index);
886 for (const auto& it : variable_adjacency_lists[current_index]) {
887 variable_stack.push(it);
888 }
889 }
890 }
891}
892
902template <typename FF, typename CircuitBuilder>
904{
905 if (!connect_variables) {
906 throw std::runtime_error("find_connected_components() can only be called when connect_variables is true");
907 }
908 connected_components.clear();
909 std::unordered_set<uint32_t> visited;
910 for (const auto& pair : variable_adjacency_lists) {
911 if (pair.first != 0 && variables_degree[pair.first] > 0) {
912 if (!visited.contains(pair.first)) {
913 std::vector<uint32_t> variable_indices;
914 depth_first_search(pair.first, visited, variable_indices);
915 std::sort(variable_indices.begin(), variable_indices.end());
916 connected_components.emplace_back(ConnectedComponent(variable_indices));
917 }
918 }
919 }
920 mark_range_list_connected_components();
921 mark_finalize_connected_components();
922 mark_process_rom_connected_component();
923 return connected_components;
924}
925
934template <typename FF, typename CircuitBuilder>
935bool StaticAnalyzer_<FF, CircuitBuilder>::is_gate_sorted_rom(size_t memory_block_idx, size_t gate_idx) const
936{
937
938 auto& memory_block = circuit_builder.blocks.get()[memory_block_idx];
939 return memory_block.q_memory()[gate_idx] == FF::one() && memory_block.q_1()[gate_idx] == FF::one() &&
940 memory_block.q_2()[gate_idx] == FF::one();
941}
942
951template <typename FF, typename CircuitBuilder>
953{
954 bool result = false;
955 KeyPair key = { var_idx, blk_idx };
956 auto it = variable_gates.find(key);
957 if (it != variable_gates.end()) {
958 const auto& gates = it->second;
959 result = std::all_of(gates.begin(), gates.end(), [this, blk_idx](size_t gate_idx) {
960 return is_gate_sorted_rom(blk_idx, gate_idx);
961 });
962 }
963 return result;
964}
965
975template <typename FF, typename CircuitBuilder>
977{
978 std::optional<size_t> block_idx_opt = find_block_index(circuit_builder.blocks.memory);
979 if (!block_idx_opt.has_value()) {
980 return;
981 }
982 size_t block_idx = block_idx_opt.value();
983 for (auto& cc : connected_components) {
984 const std::vector<uint32_t>& variables = cc.vars();
985 cc.is_process_rom_cc =
986 std::all_of(variables.begin(), variables.end(), [this, block_idx](uint32_t real_var_idx) {
987 return variable_only_in_sorted_rom_gates(real_var_idx, block_idx);
988 });
989 }
990}
991
1001template <typename FF, typename CircuitBuilder>
1003{
1004 const auto& tags = circuit_builder.real_variable_tags;
1005 std::unordered_set<uint32_t> tau_tags;
1006 for (const auto& pair : circuit_builder.range_lists) {
1007 tau_tags.insert(pair.second.tau_tag);
1008 }
1009 for (auto& cc : connected_components) {
1010 const auto& variables = cc.variable_indices;
1011 const uint32_t first_tag = tags[variables[0]];
1012 if (tau_tags.contains(first_tag)) {
1013 cc.is_range_list_cc =
1014 std::all_of(variables.begin() + 1, variables.end(), [&tags, first_tag](uint32_t var_idx) {
1015 return tags[var_idx] == first_tag;
1016 });
1017 }
1018 }
1019}
1020
1029template <typename FF, typename CircuitBuilder>
1031{
1032 const auto& finalize_witnesses = circuit_builder.get_finalize_witnesses();
1033 for (auto& cc : connected_components) {
1034 const auto& vars = cc.vars();
1035 cc.is_finalize_cc = std::all_of(vars.begin(), vars.end(), [&finalize_witnesses](uint32_t var_idx) {
1036 return finalize_witnesses.contains(var_idx);
1037 });
1038 }
1039}
1040
1057template <typename FF, typename CircuitBuilder>
1059{
1060 auto& arithmetic_block = circuit_builder.blocks.arithmetic;
1061 auto zero_idx = circuit_builder.zero_idx();
1062 size_t current_index = index;
1063 std::vector<uint32_t> accumulators_indices;
1064 while (true) {
1065 // we have to remove left, right and output wires of the current gate, cause they'are new_limbs, and they
1066 // are useless for the analyzer
1067 auto fourth_idx = arithmetic_block.w_4()[current_index];
1068 accumulators_indices.emplace_back(this->to_real(fourth_idx));
1069 auto left_idx = arithmetic_block.w_l()[current_index];
1070 if (left_idx != zero_idx) {
1071 variables_in_one_gate.erase(this->to_real(left_idx));
1072 }
1073 auto right_idx = arithmetic_block.w_r()[current_index];
1074 if (right_idx != zero_idx) {
1075 variables_in_one_gate.erase(this->to_real(right_idx));
1076 }
1077 auto out_idx = arithmetic_block.w_o()[current_index];
1078 if (out_idx != zero_idx) {
1079 variables_in_one_gate.erase(this->to_real(out_idx));
1080 }
1081 auto q_arith = arithmetic_block.q_arith()[current_index];
1082 if (q_arith == 1 || current_index == arithmetic_block.size() - 1) {
1083 // this is the last gate in this chain, or we can't go next, so we have to stop a loop
1084 break;
1085 }
1086 current_index++;
1087 }
1088 for (size_t i = 0; i < accumulators_indices.size(); i++) {
1089 if (i == 0) {
1090 // the first variable in accumulators is the variable which decompose was created. So, we have to
1091 // decrement variable_gate_counts for this variable
1092 variables_gate_counts[accumulators_indices[i]] -= 1;
1093 } else {
1094 // next accumulators are useless variables that are not interested for the analyzer. So, for these
1095 // variables we can nullify variables_gate_counts
1096 variables_gate_counts[accumulators_indices[i]] = 0;
1097 }
1098 }
1099 // we don't want to make variables_gate_counts for intermediate variables negative, so, can go to the next gates
1100 return current_index;
1101}
1102
1110template <typename FF, typename CircuitBuilder>
1112 const std::unordered_set<uint32_t>& decompose_variables)
1113{
1114 auto is_power_two = [&](const uint256_t& number) { return number > 0 && ((number & (number - 1)) == 0); };
1115 auto find_position = [&](uint32_t variable_index) {
1116 return decompose_variables.contains(this->to_real(variable_index));
1117 };
1118 auto& arithmetic_block = circuit_builder.blocks.arithmetic;
1119 if (arithmetic_block.size() > 0) {
1120 for (size_t i = 0; i < arithmetic_block.size(); i++) {
1121 auto q_1 = arithmetic_block.q_1()[i];
1122 auto q_2 = arithmetic_block.q_2()[i];
1123 auto q_3 = arithmetic_block.q_3()[i];
1124 // big addition gate from decompose has selectors, which have the next property:
1125 // q_1 = (1) << shifts[0], target_range_bitnum * (3 * i),
1126 // q_2 = (1) << shifts[1], target_range_bitnum * (3 * i + 1),
1127 // q_3 = (1) << shifts[2], target_range_bitnum * (3 * i + 2)
1128 // so, they are power of two and satisfying the following equality: q_2 * q_2 = q_1 * q_3
1129 // this way we can differ them from other arithmetic gates
1130 bool q_1_is_power_two = is_power_two(q_1);
1131 bool q_2_is_power_two = is_power_two(q_2);
1132 bool q_3_is_power_two = is_power_two(q_3);
1133 if (q_2 * q_2 == q_1 * q_3 && q_1_is_power_two && q_2_is_power_two && q_3_is_power_two) {
1134 uint32_t left_idx = arithmetic_block.w_l()[i];
1135 uint32_t right_idx = arithmetic_block.w_r()[i];
1136 uint32_t out_idx = arithmetic_block.w_o()[i];
1137 uint32_t fourth_idx = arithmetic_block.w_4()[i];
1138 bool find_left = find_position(left_idx);
1139 bool find_right = find_position(right_idx);
1140 bool find_out = find_position(out_idx);
1141 bool find_fourth = find_position(fourth_idx);
1142 if (((find_left && find_right && find_out) || (find_left && find_right && !find_out) ||
1143 (find_left && find_right && !find_out) || (find_left && !find_right && !find_out)) &&
1144 !find_fourth) {
1145 i = this->process_current_decompose_chain(i);
1146 }
1147 }
1148 }
1149 }
1150}
1151
1160template <typename FF, typename CircuitBuilder>
1162{
1163 const auto& range_lists = circuit_builder.range_lists;
1164 std::unordered_set<uint32_t> range_lists_tau_tags;
1165 std::unordered_set<uint32_t> range_lists_range_tags;
1166 const auto& real_variable_tags = circuit_builder.real_variable_tags;
1167 for (const auto& pair : range_lists) {
1168 typename CircuitBuilder::RangeList list = pair.second;
1169 range_lists_tau_tags.insert(list.tau_tag);
1170 range_lists_range_tags.insert(list.range_tag);
1171 }
1172 for (uint32_t real_index = 0; real_index < real_variable_tags.size(); real_index++) {
1173 if (variables_in_one_gate.contains(real_index)) {
1174 // this if helps us to remove variables from delta_range_constraints when finalize_circuit() function
1175 // was called
1176 if (range_lists_tau_tags.contains(real_variable_tags[real_index])) {
1177 variables_in_one_gate.erase(real_index);
1178 }
1179 // this if helps us to remove variables from range_constraints when range_constraint_into_two_limbs
1180 // function was called
1181 if (range_lists_range_tags.contains(real_variable_tags[real_index])) {
1182 variables_in_one_gate.erase(real_index);
1183 }
1184 }
1185 }
1186}
1187
1198template <typename FF, typename CircuitBuilder>
1200 size_t gate_index)
1201{
1202
1203 auto find_position = [&](uint32_t real_variable_index) {
1204 return variables_in_one_gate.contains(real_variable_index);
1205 };
1206 std::unordered_set<BasicTableId> aes_plookup_tables{ BasicTableId::AES_SBOX_MAP,
1207 BasicTableId::AES_SPARSE_MAP,
1208 BasicTableId::AES_SPARSE_NORMALIZE };
1209 auto& lookup_block = circuit_builder.blocks.lookup;
1210 if (aes_plookup_tables.contains(table_id)) {
1211 uint32_t real_out_idx = this->to_real(lookup_block.w_o()[gate_index]);
1212 uint32_t real_right_idx = this->to_real(lookup_block.w_r()[gate_index]);
1213 if (variables_gate_counts[real_out_idx] != 1 || variables_gate_counts[real_right_idx] != 1) {
1214 bool find_out = find_position(real_out_idx);
1215 auto q_c = lookup_block.q_c()[gate_index];
1216 if (q_c.is_zero()) {
1217 if (find_out) {
1218 variables_in_one_gate.erase(real_out_idx);
1219 }
1220 }
1221 }
1222 }
1223}
1224
1236template <typename FF, typename CircuitBuilder>
1238 size_t gate_index)
1239{
1240 auto find_position = [&](uint32_t real_variable_index) {
1241 return variables_in_one_gate.contains(real_variable_index);
1242 };
1243 auto& lookup_block = circuit_builder.blocks.lookup;
1244 std::unordered_set<BasicTableId> sha256_plookup_tables{ BasicTableId::SHA256_WITNESS_SLICE_3,
1245 BasicTableId::SHA256_WITNESS_SLICE_7_ROTATE_4,
1246 BasicTableId::SHA256_WITNESS_SLICE_8_ROTATE_7,
1247 BasicTableId::SHA256_WITNESS_SLICE_14_ROTATE_1,
1248 BasicTableId::SHA256_BASE16,
1249 BasicTableId::SHA256_BASE16_ROTATE2,
1250 BasicTableId::SHA256_BASE28,
1251 BasicTableId::SHA256_BASE28_ROTATE3,
1252 BasicTableId::SHA256_BASE28_ROTATE6 };
1253 if (sha256_plookup_tables.contains(table_id)) {
1254 uint32_t real_right_idx = this->to_real(lookup_block.w_r()[gate_index]);
1255 uint32_t real_out_idx = this->to_real(lookup_block.w_o()[gate_index]);
1256 if (variables_gate_counts[real_out_idx] != 1 || variables_gate_counts[real_right_idx] != 1) {
1257 // auto q_m = lookup_block.q_m()[gate_index];
1258 auto q_c = lookup_block.q_c()[gate_index];
1259 bool find_out = find_position(real_out_idx);
1260 // bool find_right = find_position(real_right_idx);
1261 if (q_c.is_zero()) {
1262 if (find_out) {
1263 variables_in_one_gate.erase(real_out_idx);
1264 }
1265 }
1266 if (table_id == SHA256_BASE16_ROTATE2 || table_id == SHA256_BASE28_ROTATE6) {
1267 // we want to remove false cases for special tables even though their selectors != 0
1268 // because they are used in read_from_1_to_2_table function, and they aren't dangerous
1269 variables_in_one_gate.erase(real_out_idx);
1270 }
1271 }
1272 }
1273}
1274
1284template <typename FF, typename CircuitBuilder>
1286 size_t gate_index)
1287{
1288 auto find_position = [&](uint32_t real_variable_index) {
1289 return variables_in_one_gate.contains(real_variable_index);
1290 };
1291
1292 std::unordered_set<BasicTableId> keccak_plookup_tables{
1293 BasicTableId::KECCAK_INPUT, BasicTableId::KECCAK_OUTPUT, BasicTableId::KECCAK_CHI, BasicTableId::KECCAK_THETA,
1294 BasicTableId::KECCAK_RHO, BasicTableId::KECCAK_RHO_1, BasicTableId::KECCAK_RHO_2, BasicTableId::KECCAK_RHO_3,
1295 BasicTableId::KECCAK_RHO_4, BasicTableId::KECCAK_RHO_5, BasicTableId::KECCAK_RHO_6, BasicTableId::KECCAK_RHO_7,
1296 BasicTableId::KECCAK_RHO_8, BasicTableId::KECCAK_RHO_9
1297 };
1298
1299 auto& lookup_block = circuit_builder.blocks.lookup;
1300
1301 if (keccak_plookup_tables.contains(table_id)) {
1302 uint32_t real_out_idx = this->to_real(lookup_block.w_o()[gate_index]);
1303 uint32_t real_right_idx = this->to_real(lookup_block.w_r()[gate_index]);
1304 if (variables_gate_counts[real_out_idx] != 1 || variables_gate_counts[real_right_idx] != 1) {
1305 bool find_out = find_position(real_out_idx);
1306 auto q_c = lookup_block.q_c()[gate_index];
1307 if (q_c.is_zero()) {
1308 if (find_out) {
1309 variables_in_one_gate.erase(real_out_idx);
1310 }
1311 }
1312 }
1313 }
1314}
1315
1325template <typename FF, typename CircuitBuilder>
1327{
1328 auto find_position = [&](uint32_t real_variable_index) {
1329 return variables_in_one_gate.contains(real_variable_index);
1330 };
1331 auto& lookup_block = circuit_builder.blocks.lookup;
1332 auto& lookup_tables = circuit_builder.get_lookup_tables();
1333 auto table_index = static_cast<size_t>(static_cast<uint256_t>(lookup_block.q_3()[gate_index]));
1334 for (const auto& table : lookup_tables) {
1335 if (table.table_index == table_index) {
1336 std::unordered_set<bb::fr> column_1(table.column_1.begin(), table.column_1.end());
1337 std::unordered_set<bb::fr> column_2(table.column_2.begin(), table.column_2.end());
1338 std::unordered_set<bb::fr> column_3(table.column_3.begin(), table.column_3.end());
1339 bb::plookup::BasicTableId table_id = table.id;
1340 // false cases for AES
1341 this->remove_unnecessary_aes_plookup_variables(table_id, gate_index);
1342 // false cases for sha256
1343 this->remove_unnecessary_sha256_plookup_variables(table_id, gate_index);
1344 // false cases for keccak
1345 this->remove_unnecessary_keccak_plookup_variables(table_id, gate_index);
1346 // if the amount of unique elements from columns of plookup tables = 1, it means that
1347 // variable from this column aren't used and we can remove it.
1348 if (column_1.size() == 1) {
1349 uint32_t left_idx = lookup_block.w_l()[gate_index];
1350 uint32_t real_left_idx = this->to_real(left_idx);
1351 bool find_left = find_position(real_left_idx);
1352 if (find_left) {
1353 variables_in_one_gate.erase(real_left_idx);
1354 }
1355 }
1356 if (column_2.size() == 1) {
1357 uint32_t real_right_idx = this->to_real(lookup_block.w_r()[gate_index]);
1358 bool find_right = find_position(real_right_idx);
1359 if (find_right) {
1360 variables_in_one_gate.erase(real_right_idx);
1361 }
1362 }
1363 if (column_3.size() == 1) {
1364 uint32_t real_out_idx = this->to_real(lookup_block.w_o()[gate_index]);
1365 bool find_out = find_position(real_out_idx);
1366 if (find_out) {
1367 variables_in_one_gate.erase(real_out_idx);
1368 }
1369 }
1370 }
1371 }
1372}
1373
1380template <typename FF, typename CircuitBuilder>
1382{
1383 auto& lookup_block = circuit_builder.blocks.lookup;
1384 if (lookup_block.size() > 0) {
1385 for (size_t i = 0; i < lookup_block.size(); i++) {
1386 this->process_current_plookup_gate(i);
1387 }
1388 }
1389}
1390
1399template <typename FF, typename CircuitBuilder>
1401{
1402 auto block_data = circuit_builder.blocks.get();
1403 if (std::optional<size_t> blk_idx = find_block_index(circuit_builder.blocks.memory); blk_idx) {
1404 std::vector<uint32_t> to_remove;
1405 for (const auto& var_idx : variables_in_one_gate) {
1406 KeyPair key = { var_idx, *blk_idx };
1407 if (auto search = variable_gates.find(key); search != variable_gates.end()) {
1408 std::vector<size_t> gate_indexes = variable_gates[key];
1409 BB_ASSERT_EQ(gate_indexes.size(), 1U);
1410 size_t gate_idx = gate_indexes[0];
1411 auto q_1 = block_data[*blk_idx].q_1()[gate_idx];
1412 auto q_2 = block_data[*blk_idx].q_2()[gate_idx];
1413 auto q_3 = block_data[*blk_idx].q_3()[gate_idx];
1414 auto q_4 = block_data[*blk_idx].q_4()[gate_idx];
1415 auto q_m = block_data[*blk_idx].q_m()[gate_idx];
1416 auto q_arith = block_data[*blk_idx].q_arith()[gate_idx];
1417 if (q_1 == FF::one() && q_m == FF::one() && q_2.is_zero() && q_3.is_zero() && q_4.is_zero() &&
1418 q_arith.is_zero()) {
1419 // record witness can be in both ROM and RAM gates, so we can ignore q_c
1420 // record witness is written as 4th variable in RAM/ROM read/write gate, so we can get 4th
1421 // wire value and check it with our variable
1422 if (this->to_real(block_data[*blk_idx].w_4()[gate_idx]) == var_idx) {
1423 to_remove.emplace_back(var_idx);
1424 }
1425 }
1426 }
1427 }
1428 for (const auto& elem : to_remove) {
1429 variables_in_one_gate.erase(elem);
1430 }
1431 }
1432}
1433
1441template <typename FF, typename CircuitBuilder>
1443{
1444 variables_in_one_gate.clear();
1445 for (const auto& pair : variables_gate_counts) {
1446 bool is_not_constant_variable = check_is_not_constant_variable(pair.first);
1447 if (pair.second == 1 && pair.first != 0 && is_not_constant_variable) {
1448 variables_in_one_gate.insert(pair.first);
1449 }
1450 }
1451 auto range_lists = circuit_builder.range_lists;
1452 std::unordered_set<uint32_t> decompose_variables;
1453 for (auto& pair : range_lists) {
1454 for (auto& elem : pair.second.variable_indices) {
1455 bool is_not_constant_variable = check_is_not_constant_variable(elem);
1456 if (variables_gate_counts[circuit_builder.real_variable_index[elem]] == 1 && is_not_constant_variable) {
1457 decompose_variables.insert(circuit_builder.real_variable_index[elem]);
1458 }
1459 }
1460 }
1461 remove_unnecessary_decompose_variables(decompose_variables);
1462 remove_unnecessary_plookup_variables();
1463 remove_unnecessary_range_constrains_variables();
1464 for (const auto& elem : fixed_variables) {
1465 variables_in_one_gate.erase(elem);
1466 }
1467 // we found variables that were in one gate and they are intended cases.
1468 // so we have to remove them from the scope
1469 for (const auto& elem : circuit_builder.get_used_witnesses()) {
1470 variables_in_one_gate.erase(elem);
1471 }
1472 remove_record_witness_variables();
1473 return variables_in_one_gate;
1474}
1475
1481template <typename FF, typename CircuitBuilder>
1483{
1484 info("╔═══════╦═══════╦═════════════╦═══════════╦══════════════╗");
1485 info("║ CC# ║ Size ║ Range List ║ Finalize ║ Process ROM ║");
1486 info("╠═══════╬═══════╬═════════════╬═══════════╬══════════════╣");
1487
1488 for (size_t i = 0; i < connected_components.size(); i++) {
1489 const auto& cc = connected_components[i];
1490 std::ostringstream line;
1491
1492 line << "║ " << std::setw(5) << std::right << (i + 1) << " ║ " << std::setw(5) << std::right << cc.size()
1493 << " ║ " << std::setw(11) << std::left << (cc.is_range_list_cc ? "Yes" : "No") << " ║ " << std::setw(9)
1494 << std::left << (cc.is_finalize_cc ? "Yes" : "No") << " ║ " << std::setw(12) << std::left
1495 << (cc.is_process_rom_cc ? "Yes" : "No") << " ║";
1496 info(line.str());
1497 }
1498 info("╚═══════╩═══════╩═════════════╩═══════════╩══════════════╝");
1499 info("Total connected components: ", connected_components.size());
1500}
1501
1508template <typename FF, typename CircuitBuilder> void StaticAnalyzer_<FF, CircuitBuilder>::print_variables_gate_counts()
1509{
1510 for (const auto& it : variables_gate_counts) {
1511 info("number of gates with variables ", it.first, " == ", it.second);
1512 }
1513}
1514
1522template <typename FF, typename CircuitBuilder>
1524{
1525 auto q_arith = block.q_arith()[gate_index];
1526 if (!q_arith.is_zero()) {
1527 info("q_arith == ", q_arith);
1528 // fisrtly, print selectors for standard plonk gate
1529 info("q_m == ", block.q_m()[gate_index]);
1530 info("q1 == ", block.q_1()[gate_index]);
1531 info("q2 == ", block.q_2()[gate_index]);
1532 info("q3 == ", block.q_3()[gate_index]);
1533 info("q4 == ", block.q_4()[gate_index]);
1534 info("q_c == ", block.q_c()[gate_index]);
1535
1536 if (q_arith == FF(2)) {
1537 // we have to print w_4_shift from next gate
1538 info("w_4_shift == ", block.w_4()[gate_index + 1]);
1539 }
1540 if (q_arith == FF(3)) {
1541 // we have to print w_4_shift and w_1_shift from the next gate
1542 info("w_1_shift == ", block.w_l()[gate_index + 1]);
1543 info("w_4_shift == ", block.w_4()[gate_index + 1]);
1544 }
1545 } else {
1546 return;
1547 }
1548}
1549
1557template <typename FF, typename CircuitBuilder>
1559{
1560 auto q_elliptic = block.q_elliptic()[gate_index];
1561 if (!q_elliptic.is_zero()) {
1562 info("q_elliptic == ", q_elliptic);
1563 info("q_1 == ", block.q_1()[gate_index]);
1564 info("q_m == ", block.q_m()[gate_index]);
1565 bool is_elliptic_add_gate = !block.q_1()[gate_index].is_zero() && block.q_m()[gate_index].is_zero();
1566 bool is_elliptic_dbl_gate = block.q_1()[gate_index].is_zero() && block.q_m()[gate_index] == FF::one();
1567 if (is_elliptic_add_gate) {
1568 info("x2 == ", block.w_l()[gate_index + 1]);
1569 info("x3 == ", block.w_r()[gate_index + 1]);
1570 info("y3 == ", block.w_o()[gate_index + 1]);
1571 info("y2 == ", block.w_4()[gate_index + 1]);
1572 }
1573 if (is_elliptic_dbl_gate) {
1574 info("x3 == ", block.w_r()[gate_index + 1]);
1575 info("y3 == ", block.w_o()[gate_index + 1]);
1576 }
1577 } else {
1578 return;
1579 }
1580}
1581
1590template <typename FF, typename CircuitBuilder>
1592{
1593 auto q_lookup = block.q_lookup()[gate_index];
1594 if (!q_lookup.is_zero()) {
1595 info("q_lookup == ", q_lookup);
1596 auto q_2 = block.q_2()[gate_index];
1597 auto q_m = block.q_m()[gate_index];
1598 auto q_c = block.q_c()[gate_index];
1599 info("q_2 == ", q_2);
1600 info("q_m == ", q_m);
1601 info("q_c == ", q_c);
1602 if (!q_2.is_zero()) {
1603 info("w_1_shift == ", block.w_l()[gate_index + 1]);
1604 }
1605 if (!q_m.is_zero()) {
1606 info("w_2_shift == ", block.w_r()[gate_index + 1]);
1607 }
1608 if (!q_c.is_zero()) {
1609 info("w_3_shift == ", block.w_o()[gate_index + 1]);
1610 }
1611 } else {
1612 return;
1613 }
1614}
1615
1624template <typename FF, typename CircuitBuilder>
1626{
1627 auto q_delta_range = block.q_delta_range()[gate_index];
1628 if (!q_delta_range.is_zero()) {
1629 info("q_delta_range == ", q_delta_range);
1630 info("w_1 == ", block.w_l()[gate_index]);
1631 info("w_2 == ", block.w_r()[gate_index]);
1632 info("w_3 == ", block.w_o()[gate_index]);
1633 info("w_4 == ", block.w_4()[gate_index]);
1634 info("w_1_shift == ", block.w_l()[gate_index]);
1635 } else {
1636 return;
1637 }
1638}
1639
1648template <typename FF, typename CircuitBuilder>
1650{
1651 auto internal_selector = block.q_poseidon2_internal()[gate_index];
1652 auto external_selector = block.q_poseidon2_external()[gate_index];
1653 if (!internal_selector.is_zero() || !external_selector.is_zero()) {
1654 info("q_poseidon2_internal == ", internal_selector);
1655 info("q_poseidon2_external == ", external_selector);
1656 info("w_1 == ", block.w_l()[gate_index]);
1657 info("w_2 == ", block.w_r()[gate_index]);
1658 info("w_3 == ", block.w_o()[gate_index]);
1659 info("w_4 == ", block.w_4()[gate_index]);
1660 info("w_1_shift == ", block.w_l()[gate_index + 1]);
1661 info("w_2_shift == ", block.w_r()[gate_index + 1]);
1662 info("w_3_shift == ", block.w_o()[gate_index + 1]);
1663 info("w_4_shift == ", block.w_4()[gate_index + 1]);
1664 } else {
1665 return;
1666 }
1667}
1668
1677template <typename FF, typename CircuitBuilder>
1679{
1680 auto q_nnf = block.q_nnf()[gate_idx];
1681 if (!q_nnf.is_zero()) {
1682 info("q_nnf == ", q_nnf);
1683 auto q_2 = block.q_2()[gate_idx];
1684 auto q_3 = block.q_3()[gate_idx];
1685 auto q_4 = block.q_4()[gate_idx];
1686 auto q_m = block.q_m()[gate_idx];
1687 if (q_3 == FF::one() && q_4 == FF::one()) {
1688 info("w_1_shift == ", block.w_l()[gate_idx + 1]);
1689 info("w_2_shift == ", block.w_r()[gate_idx + 1]);
1690
1691 } else if (q_3 == FF::one() && q_m == FF::one()) {
1692 info("w_1_shift == ", block.w_l()[gate_idx + 1]);
1693 info("w_2_shift == ", block.w_r()[gate_idx + 1]);
1694 info("w_3_shift == ", block.w_o()[gate_idx + 1]);
1695 info("w_4_shift == ", block.w_4()[gate_idx + 1]);
1696 } else if (q_2 == FF::one() && (q_3 == FF::one() || q_4 == FF::one() || q_m == FF::one())) {
1697 info("w_1_shift == ", block.w_l()[gate_idx + 1]);
1698 info("w_2_shift == ", block.w_r()[gate_idx + 1]);
1699 if (q_4 == FF::one() || q_m == FF::one()) {
1700 info("w_3_shift == ", block.w_o()[gate_idx + 1]);
1701 info("w_4_shift == ", block.w_4()[gate_idx + 1]);
1702 }
1703 }
1704 } else {
1705 return;
1706 }
1707}
1708
1717template <typename FF, typename CircuitBuilder>
1719{
1720 auto q_memory = block.q_memory()[gate_index];
1721 if (!q_memory.is_zero()) {
1722 info("q_memory == ", q_memory);
1723 auto q_1 = block.q_1()[gate_index];
1724 auto q_2 = block.q_2()[gate_index];
1725 auto q_3 = block.q_3()[gate_index];
1726 auto q_4 = block.q_4()[gate_index];
1727 if (q_1 == FF::one() && q_4 == FF::one()) {
1728 info("q_1 == ", q_1);
1729 info("q_4 == ", q_4);
1730 info("w_1_shift == ", block.w_l()[gate_index + 1]);
1731 info("w_2_shift == ", block.w_r()[gate_index + 1]);
1732 } else if (q_1 == FF::one() && q_2 == FF::one()) {
1733 info("q_1 == ", q_1);
1734 info("q_2 == ", q_2);
1735 info("w_1_shift == ", block.w_l()[gate_index + 1]);
1736 info("w_4_shift == ", block.w_4()[gate_index + 1]);
1737 } else if (!q_3.is_zero()) {
1738 info("q_3 == ", q_3);
1739 info("w_1_shift == ", block.w_l()[gate_index + 1]);
1740 info("w_2_shift == ", block.w_r()[gate_index + 1]);
1741 info("w_3_shift == ", block.w_o()[gate_index + 1]);
1742 info("w_4_shift == ", block.w_4()[gate_index + 1]);
1743 }
1744 } else {
1745 return;
1746 }
1747}
1748
1756template <typename FF, typename CircuitBuilder>
1758{
1759 const auto& block_data = circuit_builder.blocks.get();
1760 for (const auto& [key, gates] : variable_gates) {
1761 if (key.first == real_idx) {
1762 for (size_t i = 0; i < gates.size(); i++) {
1763 size_t gate_index = gates[i];
1764 auto& block = block_data[key.second];
1765 info("---- printing variables in this gate");
1766 info("w_l == ",
1767 block.w_l()[gate_index],
1768 " w_r == ",
1769 block.w_r()[gate_index],
1770 " w_o == ",
1771 block.w_o()[gate_index],
1772 " w_4 == ",
1773 block.w_4()[gate_index]);
1774 info("---- printing gate info where variable with index ", key.first, " was found ----");
1775 print_arithmetic_gate_info(gate_index, block);
1776 print_elliptic_gate_info(gate_index, block);
1777 print_plookup_gate_info(gate_index, block);
1778 print_poseidon2s_gate_info(gate_index, block);
1779 print_delta_range_gate_info(gate_index, block);
1780 print_nnf_gate_info(gate_index, block);
1781 print_memory_gate_info(gate_index, block);
1782 if constexpr (IsMegaBuilder<CircuitBuilder>) {
1783 auto q_databus = block.q_busread()[gate_index];
1784 if (!q_databus.is_zero()) {
1785 info("q_databus == ", q_databus);
1786 }
1787 }
1788 info("---- finished printing ----");
1789 }
1790 }
1791 }
1792}
1793
1803template <typename FF, typename CircuitBuilder>
1805 analyze_circuit(bool filter_cc)
1806{
1807 auto variables_in_one_gate = get_variables_in_one_gate();
1808 find_connected_components();
1809 if (filter_cc) {
1810 std::vector<ConnectedComponent> main_connected_components;
1811 main_connected_components.reserve(connected_components.size());
1812 for (auto& cc : connected_components) {
1813 if (!cc.is_range_list_cc && !cc.is_finalize_cc && !cc.is_process_rom_cc) {
1814 main_connected_components.emplace_back(cc);
1815 }
1816 }
1817 return std::make_pair(std::move(main_connected_components), std::move(variables_in_one_gate));
1818 }
1819 return std::make_pair(connected_components, std::move(variables_in_one_gate));
1820}
1821
1824
1825} // namespace cdg
#define BB_ASSERT(expression,...)
Definition assert.hpp:70
#define BB_ASSERT_EQ(actual, expected,...)
Definition assert.hpp:83
std::vector< uint32_t > real_variable_index
Map from witness index to real variable index.
TranslatorCircuitBuilder creates a circuit that evaluates the correctness of the evaluation of EccOpQ...
void print_delta_range_gate_info(size_t gate_idx, auto &block)
this method prints all information about range constrain gate where variable was found
Definition graph.cpp:1625
void process_execution_trace()
Definition graph.cpp:653
void print_memory_gate_info(size_t gate_idx, auto &block)
this method prints all information about memory gate where variable was found
Definition graph.cpp:1718
void print_plookup_gate_info(size_t gate_idx, auto &block)
this method prints all information about plookup gate where variable was found
Definition graph.cpp:1591
std::vector< uint32_t > get_ram_table_connected_component(const bb::RamTranscript &ram_array)
this method gets the RAM table connected component by processing RAM transcript records
Definition graph.cpp:540
std::unordered_map< uint32_t, std::vector< uint32_t > > variable_adjacency_lists
Definition graph.hpp:175
bool is_gate_sorted_rom(size_t memory_block_idx, size_t gate_idx) const
this method checks if current gate is sorted ROM gate
Definition graph.cpp:935
std::vector< uint32_t > get_eccop_part_connected_component(size_t index, size_t block_idx, auto &blk)
this method creates connected components from elliptic curve operation gates
Definition graph.cpp:620
std::vector< uint32_t > get_memory_gate_connected_component(size_t index, size_t block_idx, auto &blk)
this method creates connected components from Memory gates (RAM and ROM consistency checks)
Definition graph.cpp:337
std::vector< uint32_t > get_plookup_gate_connected_component(size_t index, size_t block_idx, auto &blk)
this method creates connected components from plookup gates
Definition graph.cpp:261
void remove_unnecessary_decompose_variables(const std::unordered_set< uint32_t > &decompose_variables)
this method removes unnecessary variables from decompose chains
Definition graph.cpp:1111
std::vector< ConnectedComponent > find_connected_components()
this methond finds all connected components in the graph described by adjacency lists and marks some ...
Definition graph.cpp:903
void depth_first_search(const uint32_t &variable_index, std::unordered_set< uint32_t > &is_used, std::vector< uint32_t > &connected_component)
this method implements depth-first search algorithm for undirected graphs
Definition graph.cpp:874
bool check_is_not_constant_variable(const uint32_t &variable_index)
this method checks whether the variable with given index is not constant
Definition graph.cpp:803
std::vector< uint32_t > get_arithmetic_gate_connected_component(size_t index, size_t block_idx, auto &blk)
this method creates connected components from arithmetic gates
Definition graph.cpp:86
void remove_unnecessary_sha256_plookup_variables(bb::plookup::BasicTableId &table_id, size_t gate_index)
this method removes false cases in sha256 lookup tables. tables which are enumerated in the unordered...
Definition graph.cpp:1237
std::unordered_set< uint32_t > get_variables_in_one_gate()
this method returns a final set of variables that were in one gate
Definition graph.cpp:1442
std::vector< uint32_t > get_non_native_field_gate_connected_component(size_t index, size_t block_idx, auto &blk)
this method creates connected components from Non-Native Field gates (bigfield operations)
Definition graph.cpp:397
void remove_record_witness_variables()
this method removes record witness variables from variables in one gate. initially record witness is ...
Definition graph.cpp:1400
void print_variable_info(const uint32_t real_idx)
this method prints all information about gates where variable was found
Definition graph.cpp:1757
void remove_unnecessary_range_constrains_variables()
this method removes variables from range constraints that are not security critical
Definition graph.cpp:1161
std::pair< std::vector< ConnectedComponent >, std::unordered_set< uint32_t > > analyze_circuit(bool filter_cc=true)
this functions was made for more convenient testing process
Definition graph.cpp:1805
void print_elliptic_gate_info(size_t gate_idx, auto &block)
this method prints all information about elliptic gate where variable was found
Definition graph.cpp:1558
StaticAnalyzer_()=default
std::vector< uint32_t > get_databus_connected_component(size_t index, size_t block_idx, auto &blk)
this method creates connected components from databus gates
Definition graph.cpp:595
void connect_all_variables_in_vector(const std::vector< uint32_t > &variables_vector)
this method connects 2 variables if they are in one gate and 1) have different indices,...
Definition graph.cpp:820
void print_connected_components_info()
this method prints additional information about connected components that were found in the graph
Definition graph.cpp:1482
std::vector< uint32_t > get_rom_table_connected_component(const bb::RomTranscript &rom_array)
this method gets the ROM table connected component by processing ROM transcript records
Definition graph.cpp:477
std::vector< uint32_t > get_poseido2s_gate_connected_component(size_t index, size_t block_idx, auto &blk)
this method creates connected components from poseidon2 gates
Definition graph.cpp:302
void print_poseidon2s_gate_info(size_t gate_idx, auto &block)
this method prints all information about poseidon2s gate where variable was found
Definition graph.cpp:1649
std::unordered_map< uint32_t, size_t > variables_gate_counts
Definition graph.hpp:178
std::vector< uint32_t > get_sort_constraint_connected_component(size_t index, size_t block_idx, auto &blk)
this method creates connected components from sorted constraints
Definition graph.cpp:223
void save_constant_variable_indices()
this method needs to save all constant variables indices in one data structure in order to not go thr...
Definition graph.cpp:786
std::optional< size_t > find_block_index(const auto &block)
this method finds index of the block in circuit builder by comparing pointers to blocks
Definition graph.cpp:30
bool variable_only_in_sorted_rom_gates(uint32_t var_idx, size_t blk_idx) const
this method checks that every gate for given variable in a given block is sorted ROM gate
Definition graph.cpp:952
void remove_unnecessary_aes_plookup_variables(bb::plookup::BasicTableId &table_id, size_t gate_index)
this method removes false positive cases variables from aes plookup tables. AES_SBOX_MAP,...
Definition graph.cpp:1199
void process_gate_variables(std::vector< uint32_t > &gate_variables, size_t gate_index, size_t blk_idx)
this method processes variables from a gate by removing duplicates and updating tracking structures
Definition graph.cpp:56
CircuitBuilder & circuit_builder
Definition graph.hpp:171
void remove_unnecessary_plookup_variables()
this method removes false cases plookup variables from variables in one gate
Definition graph.cpp:1381
std::vector< uint32_t > get_elliptic_gate_connected_component(size_t index, size_t block_idx, auto &blk)
this method creates connected components from elliptic gates
Definition graph.cpp:171
void print_nnf_gate_info(size_t gate_idx, auto &block)
this method prints all information about non natife field gate where variable was found
Definition graph.cpp:1678
void print_arithmetic_gate_info(size_t gate_idx, auto &block)
this method prints all information about arithmetic gate where variable was found
Definition graph.cpp:1523
void process_current_plookup_gate(size_t gate_index)
this method removes false cases in lookup table for a given gate. it uses all functions above for loo...
Definition graph.cpp:1326
void mark_range_list_connected_components()
this method marks some connected componets like they represent range lists tool needs this method to ...
Definition graph.cpp:1002
void print_variables_gate_counts()
this method prints a number of gates for each variable
Definition graph.cpp:1508
void mark_process_rom_connected_component()
this method marks some connected components if they were created by function process_rom_array....
Definition graph.cpp:976
std::unordered_map< uint32_t, size_t > variables_degree
Definition graph.hpp:180
void remove_unnecessary_keccak_plookup_variables(bb::plookup::BasicTableId &table_id, size_t gate_index)
This method removes false positive cases from keccak lookup tables. Tables which are enumerated in ke...
Definition graph.cpp:1285
size_t process_current_decompose_chain(size_t index)
this method removes variables that were created in a function decompose_into_default_range because th...
Definition graph.cpp:1058
void add_new_edge(const uint32_t &first_variable_index, const uint32_t &second_variable_index)
this method creates an edge between two variables in graph. All needed checks in a function above
Definition graph.cpp:855
void mark_finalize_connected_components()
this method marks some connected components like they represent separated finalize blocks the point i...
Definition graph.cpp:1030
#define info(...)
Definition log.hpp:93
@ SHA256_BASE16_ROTATE2
Definition types.hpp:49
@ SHA256_BASE28_ROTATE6
Definition types.hpp:46
Entry point for Barretenberg command-line interface.
Definition api.hpp:5
Definition graph.cpp:20
std::pair< uint32_t, size_t > KeyPair
Definition graph.hpp:33
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
RamTranscript contains the RamRecords for a particular RAM table (recording READ and WRITE operations...
std::vector< RamRecord > records
RomTranscript contains the RomRecords for a particular ROM table as well as the vector whose ith entr...
std::vector< RomRecord > records
BB_INLINE constexpr bool is_zero() const noexcept