diff --git a/barretenberg/barretenberg.code-workspace b/barretenberg/barretenberg.code-workspace index cd08c7a3166..c150125270a 100644 --- a/barretenberg/barretenberg.code-workspace +++ b/barretenberg/barretenberg.code-workspace @@ -145,6 +145,6 @@ "cwd": "${command:cmake.buildDirectory}", "internalConsoleOptions": "openOnSessionStart", "console": "internalConsole", - } + } }, } \ No newline at end of file diff --git a/barretenberg/cpp/src/barretenberg/boomerang_value_detection/CMakeLists.txt b/barretenberg/cpp/src/barretenberg/boomerang_value_detection/CMakeLists.txt index dae39eee2de..3b7f86dca34 100644 --- a/barretenberg/cpp/src/barretenberg/boomerang_value_detection/CMakeLists.txt +++ b/barretenberg/cpp/src/barretenberg/boomerang_value_detection/CMakeLists.txt @@ -1 +1 @@ -barretenberg_module(boomerang_value_detection stdlib_circuit_builders circuit_checker stdlib_primitives numeric stdlib_aes128 stdlib_sha256 stdlib_blake2s stdlib_blake3s) \ No newline at end of file +barretenberg_module(boomerang_value_detection stdlib_circuit_builders circuit_checker stdlib_primitives numeric stdlib_aes128 stdlib_sha256 stdlib_blake2s stdlib_blake3s stdlib_poseidon2 stdlib_primitives) diff --git a/barretenberg/cpp/src/barretenberg/boomerang_value_detection/graph.cpp b/barretenberg/cpp/src/barretenberg/boomerang_value_detection/graph.cpp index 03c31e53137..442ff9cb755 100644 --- a/barretenberg/cpp/src/barretenberg/boomerang_value_detection/graph.cpp +++ b/barretenberg/cpp/src/barretenberg/boomerang_value_detection/graph.cpp @@ -1,27 +1,64 @@ -#include "graph.hpp" +#include "./graph.hpp" +#include "barretenberg/stdlib_circuit_builders/ultra_circuit_builder.hpp" #include <algorithm> #include <stack> using namespace bb::plookup; using namespace bb; +namespace cdg { + /** - * @brief this method removes duplicate variables from a gate, - * converts variables from a gate to real variables, and then - * updates variable gates count for real variable indexes + * @brief this method finds index of the block in circuit builder by comparing pointers to blocks + * @tparam FF field type + * @param ultra_builder circuit builder containing the blocks + * @param block block to find + * @return size_t index of the found block */ +template <typename FF> +size_t Graph_<FF>::find_block_index(UltraCircuitBuilder& ultra_builder, const UltraBlock& block) { + auto gate_blocks = ultra_builder.blocks.get_gate_blocks(); + size_t index = 0; + for (size_t i = 0; i < gate_blocks.size(); i++) { + if ((void*)(&gate_blocks[i]) == (void*)(&block)) { + index = i; + break; + } + } + return index; +} + +/** + * @brief this method processes variables from a gate by removing duplicates and updating tracking structures + * @tparam FF field type + * @param ultra_circuit_builder circuit builder containing the variables + * @param gate_variables vector of variables to process + * @param gate_index index of the current gate + * @param block_idx index of the current block + * @details The method performs several operations: + * 1) Removes duplicate variables from the input vector + * 2) Converts each variable to its real index using to_real + * 3) Creates key-value pairs of (variable_index, block_index) for tracking + * 4) Updates variable_gates map with gate indices for each variable + * 5) Increments the gate count for each processed variable + */ template <typename FF> inline void Graph_<FF>::process_gate_variables(UltraCircuitBuilder& ultra_circuit_builder, - std::vector<uint32_t>& gate_variables) + std::vector<uint32_t>& gate_variables, + size_t gate_index, + size_t block_idx) { auto unique_variables = std::unique(gate_variables.begin(), gate_variables.end()); gate_variables.erase(unique_variables, gate_variables.end()); if (gate_variables.empty()) { return; } - for (size_t i = 0; i < gate_variables.size(); i++) { - gate_variables[i] = this->to_real(ultra_circuit_builder, gate_variables[i]); + for (auto& var_idx: gate_variables) { + var_idx = this->to_real(ultra_circuit_builder, var_idx); + KeyPair key = std::make_pair(var_idx, block_idx); + variable_gates[key].emplace_back(gate_index); + } for (const auto& variable_index : gate_variables) { variables_gate_counts[variable_index] += 1; @@ -29,31 +66,41 @@ inline void Graph_<FF>::process_gate_variables(UltraCircuitBuilder& ultra_circui } /** - * @brief this method implements connected components from arithmetic gates - * @tparam FF - * @param ultra_circuit_builder - * @param index - * @return std::vector<uint32_t> + * @brief this method creates connected components from arithmetic gates + * @tparam FF field type + * @param ultra_circuit_builder circuit builder containing the gates + * @param index index of the current gate + * @param block_idx index of the current block + * @param blk block containing the gates + * @return std::vector<std::vector<uint32_t>> vector of connected components from the gate and minigate + * @details Processes both regular arithmetic gates and minigates, handling fixed witness gates + * and different arithmetic operations based on selector values */ - template <typename FF> -inline std::vector<uint32_t> Graph_<FF>::get_arithmetic_gate_connected_component( - bb::UltraCircuitBuilder& ultra_circuit_builder, size_t index) +inline std::vector<std::vector<uint32_t>> Graph_<FF>::get_arithmetic_gate_connected_component( + bb::UltraCircuitBuilder& ultra_circuit_builder, size_t index, size_t block_idx, UltraBlock& blk) { - auto& arithmetic_block = ultra_circuit_builder.blocks.arithmetic; - uint32_t left_idx = arithmetic_block.w_l()[index]; - uint32_t right_idx = arithmetic_block.w_r()[index]; - uint32_t out_idx = arithmetic_block.w_o()[index]; - uint32_t fourth_idx = arithmetic_block.w_4()[index]; - auto q_m = arithmetic_block.q_m()[index]; - auto q_1 = arithmetic_block.q_1()[index]; - auto q_2 = arithmetic_block.q_2()[index]; - auto q_3 = arithmetic_block.q_3()[index]; - auto q_4 = arithmetic_block.q_4()[index]; - std::vector<uint32_t> gate_variables = {}; - if (q_m != 0 || q_1 != 1 || q_2 != 0 || q_3 != 0 || q_4 != 0) { - // this is not the gate for fix_witness, so we have to process this gate - if (arithmetic_block.q_arith()[index] > 0) { + auto q_arith = blk.q_arith()[index]; + std::vector<uint32_t> gate_variables; + std::vector<uint32_t> minigate_variables; + std::vector<std::vector<uint32_t>> all_gates_variables; + if (!q_arith.is_zero()) { + auto q_m = blk.q_m()[index]; + auto q_1 = blk.q_1()[index]; + auto q_2 = blk.q_2()[index]; + auto q_3 = blk.q_3()[index]; + auto q_4 = blk.q_4()[index]; + + uint32_t left_idx = blk.w_l()[index]; + uint32_t right_idx = blk.w_r()[index]; + uint32_t out_idx = blk.w_o()[index]; + uint32_t fourth_idx = blk.w_4()[index]; + if (q_m == 0 && q_1 == 1 && q_2 == 0 && q_3 == 0 && q_4 == 0 && q_arith == 1) { + //this is fixed_witness gate. So, variable index contains in left wire. So, we have to take only it. + fixed_variables.insert(this->to_real(ultra_circuit_builder, left_idx)); + } + else if (q_m != 0 || q_1 != 1 || q_2 != 0 || q_3 != 0 || q_4 != 0) { + // this is not the gate for fix_witness, so we have to process this gate if (q_m != 0) { gate_variables.emplace_back(left_idx); gate_variables.emplace_back(right_idx); @@ -70,140 +117,441 @@ inline std::vector<uint32_t> Graph_<FF>::get_arithmetic_gate_connected_component if (q_4 != 0) { gate_variables.emplace_back(fourth_idx); } - if (arithmetic_block.q_arith()[index] == 2) { + if (q_arith == 2) { // We have to use w_4_shift from the next gate // if and only if the current gate isn't last, cause we can't // look into the next gate - if (index != arithmetic_block.size() - 1) { - uint32_t fourth_shift_idx = arithmetic_block.w_4()[index + 1]; - gate_variables.emplace_back(fourth_shift_idx); + if (index != blk.size() - 1) { + gate_variables.emplace_back(blk.w_4()[index + 1]); } } - if (arithmetic_block.q_arith()[index] == 3) { - // TODO(daniel): want to process this case later - ASSERT(false); + if (q_arith == 3) { + //In this gate mini gate is enabled, we have 2 equations: + //q_1 * w_1 + q_2 * w_2 + q_3 * w_3 + q_4 * w_4 + q_c + 2 * w_4_omega = 0 + //w_1 + w_4 - w_1_omega + q_m = 0 + minigate_variables.insert(minigate_variables.end(), {left_idx, fourth_idx}); + if (index != blk.size() - 1) { + gate_variables.emplace_back(blk.w_4()[index + 1]); + minigate_variables.emplace_back(blk.w_l()[index + 1]); + } } } + this->process_gate_variables(ultra_circuit_builder, gate_variables, index, block_idx); + this->process_gate_variables(ultra_circuit_builder, minigate_variables, index, block_idx); + all_gates_variables.emplace_back(gate_variables); + if (!minigate_variables.empty()) { + all_gates_variables.emplace_back(minigate_variables); + } } - this->process_gate_variables(ultra_circuit_builder, gate_variables); - return gate_variables; + return all_gates_variables; } /** * @brief this method creates connected components from elliptic gates - * @tparam FF - * @param ultra_circuit_builder - * @param index - * @return std::vector<uint32_t> + * @tparam FF field type + * @param ultra_circuit_builder circuit builder containing the gates + * @param index index of the current gate + * @param block_idx index of the current block + * @param blk block containing the gates + * @return std::vector<uint32_t> vector of connected variables from the gate + * @details Handles both elliptic curve addition and doubling operations, + * collecting variables from current and next gates as needed */ - template <typename FF> inline std::vector<uint32_t> Graph_<FF>::get_elliptic_gate_connected_component( - bb::UltraCircuitBuilder& ultra_circuit_builder, size_t index) + bb::UltraCircuitBuilder& ultra_circuit_builder, size_t index, size_t block_idx, UltraBlock& blk) { - auto& elliptic_block = ultra_circuit_builder.blocks.elliptic; std::vector<uint32_t> gate_variables = {}; - bool is_elliptic_gate = elliptic_block.q_elliptic()[index] == 1; - bool is_elliptic_add_gate = elliptic_block.q_1()[index] != 0 && elliptic_block.q_m()[index] == 0; - bool is_elliptic_dbl_gate = elliptic_block.q_1()[index] == 0 && elliptic_block.q_m()[index] == 1; - if (is_elliptic_gate) { - auto right_idx = elliptic_block.w_r()[index]; - auto out_idx = elliptic_block.w_o()[index]; + if (!blk.q_elliptic()[index].is_zero()) { + bool is_elliptic_add_gate = blk.q_1()[index] != 0 && blk.q_m()[index] == 0; + bool is_elliptic_dbl_gate = blk.q_1()[index] == 0 && blk.q_m()[index] == 1; + auto right_idx = blk.w_r()[index]; + auto out_idx = blk.w_o()[index]; gate_variables.emplace_back(right_idx); gate_variables.emplace_back(out_idx); - if (index != elliptic_block.size() - 1) { + if (index != blk.size() - 1) { if (is_elliptic_add_gate) { // if this gate is ecc_add_gate, we have to get indices x2, x3, y3, y2 from the next gate - gate_variables.emplace_back(elliptic_block.w_l()[index + 1]); - gate_variables.emplace_back(elliptic_block.w_r()[index + 1]); - gate_variables.emplace_back(elliptic_block.w_o()[index + 1]); - gate_variables.emplace_back(elliptic_block.w_4()[index + 1]); + gate_variables.emplace_back(blk.w_l()[index + 1]); + gate_variables.emplace_back(blk.w_r()[index + 1]); + gate_variables.emplace_back(blk.w_o()[index + 1]); + gate_variables.emplace_back(blk.w_4()[index + 1]); } if (is_elliptic_dbl_gate) { // if this gate is ecc_dbl_gate, we have to indices x3, y3 from right and output wires - gate_variables.emplace_back(elliptic_block.w_r()[index + 1]); - gate_variables.emplace_back(elliptic_block.w_o()[index + 1]); + gate_variables.emplace_back(blk.w_r()[index + 1]); + gate_variables.emplace_back(blk.w_o()[index + 1]); } } + this->process_gate_variables(ultra_circuit_builder, gate_variables, index, block_idx); } - this->process_gate_variables(ultra_circuit_builder, gate_variables); return gate_variables; } /** * @brief this method creates connected components from sorted constraints - * - * @tparam FF - * @param ultra_circuit_builder - * @param index - * @return std::vector<uint32_t> + * @tparam FF field type + * @param ultra_circuit_builder circuit builder containing the gates + * @param index index of the current gate + * @param block_idx index of the current block + * @param block block containing the gates + * @return std::vector<uint32_t> vector of connected variables from the gate + * @details Processes delta range constraints by collecting all wire indices + * from the current gate */ - template <typename FF> inline std::vector<uint32_t> Graph_<FF>::get_sort_constraint_connected_component( - bb::UltraCircuitBuilder& ultra_circuit_builder, size_t index) + bb::UltraCircuitBuilder& ultra_circuit_builder, size_t index, size_t blk_idx, UltraBlock& block) { - auto& delta_range_block = ultra_circuit_builder.blocks.delta_range; std::vector<uint32_t> gate_variables = {}; - if (delta_range_block.q_delta_range()[index] == 1) { - auto left_idx = delta_range_block.w_l()[index]; - auto right_idx = delta_range_block.w_r()[index]; - auto out_idx = delta_range_block.w_o()[index]; - auto fourth_idx = delta_range_block.w_4()[index]; + if (!block.q_delta_range()[index].is_zero()) { + auto left_idx = block.w_l()[index]; + auto right_idx = block.w_r()[index]; + auto out_idx = block.w_o()[index]; + auto fourth_idx = block.w_4()[index]; gate_variables.insert(gate_variables.end(), { left_idx, right_idx, out_idx, fourth_idx }); } - this->process_gate_variables(ultra_circuit_builder, gate_variables); + this->process_gate_variables(ultra_circuit_builder, gate_variables, index, blk_idx); return gate_variables; } /** * @brief this method creates connected components from plookup gates - * - * @tparam FF - * @param ultra_circuit_builder - * @param index - * @return std::vector<uint32_t> + * @tparam FF field type + * @param ultra_circuit_builder circuit builder containing the gates + * @param index index of the current gate + * @param block_idx index of the current block + * @param block block containing the gates + * @return std::vector<uint32_t> vector of connected variables from the gate + * @details Processes plookup gates by collecting variables based on selector values, + * including variables from the next gate when necessary */ - template <typename FF> inline std::vector<uint32_t> Graph_<FF>::get_plookup_gate_connected_component( - bb::UltraCircuitBuilder& ultra_circuit_builder, size_t index) + bb::UltraCircuitBuilder& ultra_circuit_builder, size_t index, size_t blk_idx, UltraBlock& block) { std::vector<uint32_t> gate_variables; - auto& lookup_block = ultra_circuit_builder.blocks.lookup; - auto q_2 = lookup_block.q_2()[index]; - auto q_m = lookup_block.q_m()[index]; - auto q_c = lookup_block.q_c()[index]; - auto left_idx = lookup_block.w_l()[index]; - auto right_idx = lookup_block.w_r()[index]; - auto out_idx = lookup_block.w_o()[index]; - gate_variables.emplace_back(left_idx); - gate_variables.emplace_back(right_idx); - gate_variables.emplace_back(out_idx); - if (index < lookup_block.size() - 1) { - if (q_2 != 0 || q_m != 0 || q_c != 0) { - if (q_2 != 0) { - gate_variables.emplace_back(lookup_block.w_l()[index + 1]); + auto q_lookup_type = block.q_lookup_type()[index]; + if (!q_lookup_type.is_zero()) { + auto q_2 = block.q_2()[index]; + auto q_m = block.q_m()[index]; + auto q_c = block.q_c()[index]; + auto left_idx = block.w_l()[index]; + auto right_idx = block.w_r()[index]; + auto out_idx = block.w_o()[index]; + gate_variables.emplace_back(left_idx); + gate_variables.emplace_back(right_idx); + gate_variables.emplace_back(out_idx); + if (index < block.size() - 1) { + if (q_2 != 0 || q_m != 0 || q_c != 0) { + if (q_2 != 0) { + gate_variables.emplace_back(block.w_l()[index + 1]); + } + if (q_m != 0) { + gate_variables.emplace_back(block.w_r()[index + 1]); + } + if (q_c != 0) { + gate_variables.emplace_back(block.w_o()[index + 1]); + } } - if (q_m != 0) { - gate_variables.emplace_back(lookup_block.w_r()[index + 1]); + } + this->process_gate_variables(ultra_circuit_builder, gate_variables, index, blk_idx); + } + return gate_variables; +} + +/** + * @brief this method creates connected components from poseidon2 gates + * @tparam FF field type + * @param ultra_circuit_builder circuit builder containing the gates + * @param index index of the current gate + * @param blk_idx index of the current block + * @param block block containing the gates + * @return std::vector<uint32_t> vector of connected variables from the gate + */ +template <typename FF> +inline std::vector<uint32_t> Graph_<FF>::get_poseido2s_gate_connected_component( + bb::UltraCircuitBuilder& ultra_circuit_builder, size_t index, size_t blk_idx, UltraBlock& block) +{ + std::vector<uint32_t> gate_variables; + auto internal_selector = block.q_poseidon2_internal()[index]; + auto external_selector = block.q_poseidon2_external()[index]; + if (!internal_selector.is_zero() || !external_selector.is_zero()) { + gate_variables.insert(gate_variables.end(), + { block.w_l()[index], block.w_r()[index], block.w_o()[index], block.w_4()[index] }); + if (index != block.size() - 1) { + gate_variables.insert( + gate_variables.end(), + { block.w_l()[index + 1], block.w_r()[index + 1], block.w_o()[index + 1], block.w_4()[index + 1] }); + } + this->process_gate_variables(ultra_circuit_builder, gate_variables, index, blk_idx); + } + return gate_variables; +} + +/** + * @brief this method creates connected components from auxiliary gates, including bigfield operations, + * RAM and ROM consistency checks + * @tparam FF field type + * @param ultra_builder circuit builder containing the gates + * @param index index of the current gate + * @param blk_idx index of the current block + * @param block block containing the gates + * @return std::vector<uint32_t> vector of connected variables from the gate + */ +template <typename FF> +inline std::vector<uint32_t> Graph_<FF>::get_auxiliary_gate_connected_component(bb::UltraCircuitBuilder& ultra_builder, + size_t index, size_t blk_idx, UltraBlock& block) +{ + std::vector<uint32_t> gate_variables; + if (!block.q_aux()[index].is_zero()) { + auto q_1 = block.q_1()[index]; + auto q_2 = block.q_2()[index]; + auto q_3 = block.q_3()[index]; + auto q_4 = block.q_4()[index]; + auto q_m = block.q_m()[index]; + auto q_arith = block.q_arith()[index]; + [[maybe_unused]]auto q_c = block.q_c()[index]; + + auto w_l = block.w_l()[index]; + auto w_r = block.w_r()[index]; + auto w_o = block.w_o()[index]; + auto w_4 = block.w_4()[index]; + if (q_3 == 1 && q_4 == 1) { + // bigfield limb accumulation 1 + ASSERT(q_arith.is_zero()); + if (index < block.size() - 1) { + gate_variables.insert(gate_variables.end(), + { w_l, + w_r, + w_o, + w_4, + block.w_l()[index + 1], + block.w_r()[index + 1] }); + } + } + else if (q_3 == 1 && q_m == 1) { + ASSERT(q_arith.is_zero()); + // bigfield limb accumulation 2 + if (index < block.size() - 1) { + gate_variables.insert(gate_variables.end(), + { w_o, + w_4, + block.w_l()[index + 1], + block.w_r()[index + 1], + block.w_o()[index + 1], + block.w_4()[index + 1] }); + } + } + else if (q_2 == 1 && (q_3 == 1 || q_4 == 1 || q_m == 1)) { + ASSERT(q_arith.is_zero()); + // bigfield product cases + if (index < block.size() - 1) { + std::vector<uint32_t> limb_subproduct_vars = { + w_l, w_r, block.w_l()[index + 1], block.w_r()[index + 1] + }; + if (q_3 == 1) { + // bigfield product 1 + ASSERT(q_4.is_zero() && q_m.is_zero()); + gate_variables.insert( + gate_variables.end(), limb_subproduct_vars.begin(), limb_subproduct_vars.end()); + gate_variables.insert(gate_variables.end(), { w_o, w_4 }); + } + if (q_4 == 1) { + // bigfield product 2 + ASSERT(q_3.is_zero() && q_m.is_zero()); + std::vector<uint32_t> non_native_field_gate_2 = { w_l, + w_4, + w_r, + w_o, + block.w_o()[index + 1] }; + gate_variables.insert( + gate_variables.end(), non_native_field_gate_2.begin(), non_native_field_gate_2.end()); + gate_variables.emplace_back(block.w_4()[index + 1]); + gate_variables.insert( + gate_variables.end(), limb_subproduct_vars.begin(), limb_subproduct_vars.end()); + } + if (q_m == 1) { + // bigfield product 3 + ASSERT(q_4.is_zero() && q_3.is_zero()); + gate_variables.insert( + gate_variables.end(), limb_subproduct_vars.begin(), limb_subproduct_vars.end()); + gate_variables.insert(gate_variables.end(), + { w_4, block.w_o()[index + 1], block.w_4()[index + 1] }); + } + } + } + else if (q_1 == 1 && q_4 == 1) { + ASSERT(q_arith.is_zero()); + // ram timestamp check + if (index < block.size() - 1) { + gate_variables.insert(gate_variables.end(), + { block.w_r()[index + 1], + block.w_r()[index], + block.w_l()[index], + block.w_l()[index + 1], + block.w_o()[index] }); } - if (q_c != 0) { - gate_variables.emplace_back(lookup_block.w_o()[index + 1]); + } + else if (q_1 == 1 && q_2 == 1) { + ASSERT(q_arith.is_zero()); + // rom constitency check + if (index < block.size() - 1) { + gate_variables.insert( + gate_variables.end(), + { block.w_l()[index], block.w_l()[index + 1], block.w_4()[index], block.w_4()[index + 1] }); + } + } + else { + // ram constitency check + if (!q_arith.is_zero()) { + if (index < block.size() - 1) { + gate_variables.insert(gate_variables.end(), + { block.w_o()[index], + block.w_4()[index], + block.w_l()[index + 1], + block.w_r()[index + 1], + block.w_o()[index + 1], + block.w_4()[index + 1] }); + } } } } - this->process_gate_variables(ultra_circuit_builder, gate_variables); + this->process_gate_variables(ultra_builder, gate_variables, index, blk_idx); return gate_variables; } /** - * @brief Construct a new Graph from Ultra Circuit Builder - * @tparam FF - * @param ultra_circuit_constructor + * @brief this method gets the ROM table connected component by processing ROM transcript records + * @tparam FF field type + * @param ultra_builder circuit builder containing the gates + * @param rom_array ROM transcript containing records with witness indices and gate information + * @return std::vector<uint32_t> vector of connected variables from ROM table gates */ +template <typename FF> +inline std::vector<uint32_t> Graph_<FF>::get_rom_table_connected_component( + bb::UltraCircuitBuilder& ultra_builder, const UltraCircuitBuilder::RomTranscript& rom_array) +{ + size_t block_index = find_block_index(ultra_builder, ultra_builder.blocks.aux); + ASSERT(block_index == 3); -template <typename FF> Graph_<FF>::Graph_(bb::UltraCircuitBuilder& ultra_circuit_constructor) + // Every RomTranscript data structure has 2 main components that are interested for static analyzer: + // 1) records contains values that were put in the gate, we can use them to create connections between variables + // 2) states contains values witness indexes that we can find in the ROM record in the RomTrascript, so we can ignore + // state of the ROM transcript, because we still can connect all variables using variables from records. + std::vector<uint32_t> rom_table_variables; + + for (const auto& record : rom_array.records) { + std::vector<uint32_t> gate_variables; + size_t gate_index = record.gate_index; + + auto q_1 = ultra_builder.blocks.aux.q_1()[gate_index]; + auto q_2 = ultra_builder.blocks.aux.q_2()[gate_index]; + auto q_3 = ultra_builder.blocks.aux.q_3()[gate_index]; + auto q_4 = ultra_builder.blocks.aux.q_4()[gate_index]; + auto q_m = ultra_builder.blocks.aux.q_m()[gate_index]; + auto q_arith = ultra_builder.blocks.aux.q_arith()[gate_index]; + auto q_c = ultra_builder.blocks.aux.q_c()[gate_index]; + + auto index_witness = record.index_witness; + auto vc1_witness = record.value_column1_witness; //state[0] from RomTranscript + auto vc2_witness = record.value_column2_witness; //state[1] from RomTranscript + auto record_witness = record.record_witness; + + if (q_1 == 1 && q_m == 1 && q_2 == 0 && q_3 == 0 && q_4 == 0 && q_c == 0 && q_arith == 0) { + //By default ROM read gate uses variables (w_1, w_2, w_3, w_4) = (index_witness, vc1_witness, vc2_witness, record_witness) + //So we can update all of them + gate_variables.emplace_back(index_witness); + if (vc1_witness != ultra_builder.zero_idx) { + gate_variables.emplace_back(vc1_witness); + } + if (vc2_witness != ultra_builder.zero_idx) { + gate_variables.emplace_back(vc2_witness); + } + gate_variables.emplace_back(record_witness); + } + this->process_gate_variables(ultra_builder, gate_variables, gate_index, block_index); + //after process_gate_variables function gate_variables constists of real variables indexes, so we can add all this variables in the + //final vector to connect all of them + if (!gate_variables.empty()) { + rom_table_variables.insert(rom_table_variables.end(), gate_variables.begin(), gate_variables.end()); + } + } + return rom_table_variables; +} + +/** + * @brief this method gets the RAM table connected component by processing RAM transcript records + * @tparam FF field type + * @param ultra_builder circuit builder containing the gates + * @param ram_array RAM transcript containing records with witness indices and gate information + * @return std::vector<uint32_t> vector of connected variables from RAM table gates + */ +template <typename FF> +inline std::vector<uint32_t> Graph_<FF>::get_ram_table_connected_component( + bb::UltraCircuitBuilder& ultra_builder, const UltraCircuitBuilder::RamTranscript& ram_array) +{ + size_t block_index = find_block_index(ultra_builder, ultra_builder.blocks.aux); + ASSERT(block_index == 3); + std::vector<uint32_t> ram_table_variables; + for (const auto& record : ram_array.records) { + std::vector<uint32_t> gate_variables; + size_t gate_index = record.gate_index; + + auto q_1 = ultra_builder.blocks.aux.q_1()[gate_index]; + auto q_2 = ultra_builder.blocks.aux.q_2()[gate_index]; + auto q_3 = ultra_builder.blocks.aux.q_3()[gate_index]; + auto q_4 = ultra_builder.blocks.aux.q_4()[gate_index]; + auto q_m = ultra_builder.blocks.aux.q_m()[gate_index]; + auto q_arith = ultra_builder.blocks.aux.q_arith()[gate_index]; + auto q_c = ultra_builder.blocks.aux.q_c()[gate_index]; + + auto index_witness = record.index_witness; + auto timestamp_witness = record.timestamp_witness; + auto value_witness = record.value_witness; + auto record_witness = record.record_witness; + + if (q_1 == 1 && q_m == 1 && q_2 == 0 && q_3 == 0 && q_4 == 0 && q_arith == 0 && (q_c == 0 || q_c == 1)) { + //By default RAM read/write gate uses variables (w_1, w_2, w_3, w_4) = (index_witness, timestamp_witness, value_witness, record_witness) + //So we can update all of them + gate_variables.emplace_back(index_witness); + if (timestamp_witness != ultra_builder.zero_idx) { + gate_variables.emplace_back(timestamp_witness); + } + if (value_witness != ultra_builder.zero_idx) { + gate_variables.emplace_back(value_witness); + } + gate_variables.emplace_back(record_witness); + } + this->process_gate_variables(ultra_builder, gate_variables, gate_index, block_index); + //after process_gate_variables function gate_variables constists of real variables indexes, so we can add all these variables in the + //final vector to connect all of them + ram_table_variables.insert(ram_table_variables.end(), gate_variables.begin(), gate_variables.end()); + } + return ram_table_variables; +} + +/** + * @brief Construct a new Graph from Ultra Circuit Builder + * @tparam FF field type used in the circuit + * @param ultra_circuit_constructor circuit builder containing all gates and variables + * @details This constructor initializes the graph structure by: + * 1) Creating data structures for tracking: + * - Number of gates each variable appears in (variables_gate_counts) + * - Adjacency lists for each variable (variable_adjacency_lists) + * - Degree of each variable (variables_degree) + * 2) Processing different types of gates: + * - Arithmetic gates + * - Elliptic curve gates + * - Plookup gates + * - Poseidon2 gates + * - Auxiliary gates + * - Delta range gates + * 3) Creating connections between variables that appear in the same gate + * 4) Special handling for sorted constraints in delta range blocks + */ +template <typename FF> +Graph_<FF>::Graph_(bb::UltraCircuitBuilder& ultra_circuit_constructor) { this->variables_gate_counts = std::unordered_map<uint32_t, size_t>(ultra_circuit_constructor.real_variable_index.size()); @@ -217,49 +565,62 @@ template <typename FF> Graph_<FF>::Graph_(bb::UltraCircuitBuilder& ultra_circuit } std::map<FF, uint32_t> constant_variable_indices = ultra_circuit_constructor.constant_variable_indices; - const auto& arithmetic_block = ultra_circuit_constructor.blocks.arithmetic; - auto arithmetic_gates_numbers = arithmetic_block.size(); - bool arithmetic_gates_exist = arithmetic_gates_numbers > 0; - if (arithmetic_gates_exist) { - for (size_t i = 0; i < arithmetic_gates_numbers; i++) { - auto gate_variables = this->get_arithmetic_gate_connected_component(ultra_circuit_constructor, i); - this->connect_all_variables_in_vector(ultra_circuit_constructor, gate_variables, false); - } - } - const auto& elliptic_block = ultra_circuit_constructor.blocks.elliptic; - auto elliptic_gates_numbers = elliptic_block.size(); - bool elliptic_gates_exist = elliptic_gates_numbers > 0; - if (elliptic_gates_exist) { - for (size_t i = 0; i < elliptic_gates_numbers; i++) { - std::vector<uint32_t> gate_variables = - this->get_elliptic_gate_connected_component(ultra_circuit_constructor, i); - this->connect_all_variables_in_vector(ultra_circuit_constructor, gate_variables, false); - } - } - const auto& range_block = ultra_circuit_constructor.blocks.delta_range; - auto range_gates = range_block.size(); - bool range_gates_exists = range_gates > 0; - if (range_gates_exists) { - std::vector<uint32_t> sorted_variables; - for (size_t i = 0; i < range_gates; i++) { - auto current_gate = this->get_sort_constraint_connected_component(ultra_circuit_constructor, i); - if (current_gate.empty()) { - this->connect_all_variables_in_vector(ultra_circuit_constructor, sorted_variables, true); - sorted_variables.clear(); - } else { - sorted_variables.insert(sorted_variables.end(), current_gate.begin(), current_gate.end()); - } - } - } - - const auto& lookup_block = ultra_circuit_constructor.blocks.lookup; - auto lookup_gates = lookup_block.size(); - bool lookup_gates_exists = lookup_gates > 0; - if (lookup_gates_exists) { - for (size_t i = 0; i < lookup_gates; i++) { + auto gate_blocks = ultra_circuit_constructor.blocks.get_gate_blocks(); + for (size_t blk_idx = 0; blk_idx < gate_blocks.size(); blk_idx++) { + if (gate_blocks[blk_idx].size() > 0) { + std::vector<uint32_t> sorted_variables; + for (size_t gate_idx = 0; gate_idx < gate_blocks[blk_idx].size(); gate_idx++) { + auto arithmetic_gates_variables = get_arithmetic_gate_connected_component(ultra_circuit_constructor, gate_idx, blk_idx, gate_blocks[blk_idx]); + if (!arithmetic_gates_variables.empty()) { + for (const auto& gate_variables: arithmetic_gates_variables) { + //info("size of arithmetic_gate == ", gate_variables.size()); + connect_all_variables_in_vector(ultra_circuit_constructor, gate_variables, /*is_sorted_variables=*/false); + } + } + auto elliptic_gate_variables = get_elliptic_gate_connected_component(ultra_circuit_constructor, gate_idx, blk_idx, gate_blocks[blk_idx]); + //info("size of elliptic_gate == ", elliptic_gate_variables.size()); + connect_all_variables_in_vector(ultra_circuit_constructor, elliptic_gate_variables, /*is_sorted_variables=*/false); + auto lookup_gate_variables = get_plookup_gate_connected_component(ultra_circuit_constructor, gate_idx, blk_idx, gate_blocks[blk_idx]); + //info("size of lookup_gate == ", lookup_gate_variables.size()); + connect_all_variables_in_vector(ultra_circuit_constructor, lookup_gate_variables, /*is_sorted_variables=*/false); + auto poseidon2_gate_variables = get_poseido2s_gate_connected_component(ultra_circuit_constructor, gate_idx, blk_idx, gate_blocks[blk_idx]); + //info("size of poseidon2_gate == ", poseidon2_gate_variables.size()); + connect_all_variables_in_vector(ultra_circuit_constructor, poseidon2_gate_variables, /*is_sorted_variables=*/false); + auto aux_gate_variables = get_auxiliary_gate_connected_component(ultra_circuit_constructor, gate_idx, blk_idx, gate_blocks[blk_idx]); + //info("size of aux_gate == ", aux_gate_variables.size()); + connect_all_variables_in_vector(ultra_circuit_constructor, aux_gate_variables, /*is_sorted_variables=*/false); + if (arithmetic_gates_variables.empty() && elliptic_gate_variables.empty() && lookup_gate_variables.empty() && poseidon2_gate_variables.empty() && aux_gate_variables.empty()) { + //if all vectors are empty it means that current block is delta range, and it needs another processing method + auto delta_range_gate_variables = get_sort_constraint_connected_component(ultra_circuit_constructor, gate_idx, blk_idx, gate_blocks[blk_idx]); + if (delta_range_gate_variables.empty()) { + connect_all_variables_in_vector(ultra_circuit_constructor, sorted_variables, /*is_sorted_variables=*/true); + sorted_variables.clear(); + } + else { + sorted_variables.insert(sorted_variables.end(), delta_range_gate_variables.begin(), delta_range_gate_variables.end()); + } + } + } + } + } + + const auto& rom_arrays = ultra_circuit_constructor.rom_arrays; + if (rom_arrays.size() > 0) { + for (size_t i = 0; i < rom_arrays.size(); i++) { + std::vector<uint32_t> variable_indices = + this->get_rom_table_connected_component(ultra_circuit_constructor, rom_arrays[i]); + this->connect_all_variables_in_vector( + ultra_circuit_constructor, variable_indices, /*is_sorted_variables=*/false); + } + } + + const auto& ram_arrays = ultra_circuit_constructor.ram_arrays; + if (ram_arrays.size() > 0) { + for (size_t i = 0; i < ram_arrays.size(); i++) { std::vector<uint32_t> variable_indices = - this->get_plookup_gate_connected_component(ultra_circuit_constructor, i); - this->connect_all_variables_in_vector(ultra_circuit_constructor, variable_indices, false); + this->get_ram_table_connected_component(ultra_circuit_constructor, ram_arrays[i]); + this->connect_all_variables_in_vector( + ultra_circuit_constructor, variable_indices, /*is_sorted_variables=*/false); } } } @@ -289,7 +650,7 @@ bool Graph_<FF>::check_is_not_constant_variable(bb::UltraCircuitBuilder& ultra_c } /** - * @brief this method adds connection between 2 variables, if they are in one gate, they are not constraint variables, + * @brief this method adds connection between 2 variables, if they are in one gate, they are not constrant variables, * and they have different indexes * @tparam FF * @param ultra_circuit_builder @@ -388,7 +749,7 @@ void Graph_<FF>::depth_first_search(const uint32_t& variable_index, /** * @brief this methond finds all connected components in the graph described by adjacency lists * @tparam FF - * @return std::vector<std::vector<uint32_t>> + * @return std::vector<std::vector<uint32_t>> list of connected components where each component is a vector of variable indices */ template <typename FF> std::vector<std::vector<uint32_t>> Graph_<FF>::find_connected_components() @@ -472,8 +833,7 @@ inline size_t Graph_<FF>::process_current_decompose_chain(bb::UltraCircuitBuilde } /** - * @brief this method gets the endpoints of the decompose chains. For that it has to clean variable_index - from unnecessary variables for example, left, right, output wires and go through all decompose chain + * @brief this method removes unnecessary variables from decompose chains * @tparam FF * @param ultra_circuit_builder * @param variables_in_one_gate @@ -522,8 +882,42 @@ inline void Graph_<FF>::remove_unnecessary_decompose_variables(bb::UltraCircuitB } } } + /** - * @brief this method removes false positive cass variables from aes plookup tables. + * @brief this method removes variables from range constraints that are not security critical + * @tparam FF field type + * @param ultra_builder circuit builder containing the range lists + * @details Right now static analyzer removes two types of variables: + * 1) Variables from delta_range_constraints created by finalize_circuit() + * 2) Variables from range_constraints created by range_constraint_into_two_limbs + */ +template <typename FF> +void Graph_<FF>::remove_unnecessary_range_constrains_variables(bb::UltraCircuitBuilder& ultra_builder) { + std::map<uint64_t, UltraCircuitBuilder::RangeList> range_lists = ultra_builder.range_lists; + std::unordered_set<uint32_t> range_lists_tau_tags; + std::unordered_set<uint32_t> range_lists_range_tags; + std::vector<uint32_t> real_variable_tags = ultra_builder.real_variable_tags; + for (const auto& pair: range_lists) { + UltraCircuitBuilder::RangeList list = pair.second; + range_lists_tau_tags.insert(list.tau_tag); + range_lists_range_tags.insert(list.range_tag); + } + for (uint32_t real_index = 0; real_index < real_variable_tags.size(); real_index++) { + if (variables_in_one_gate.contains(real_index)) { + //this if helps us to remove variables from delta_range_constraints when finalize_circuit() function was called + if (range_lists_tau_tags.contains(real_variable_tags[real_index])) { + variables_in_one_gate.erase(real_index); + } + //this if helps us to remove variables from range_constraints when range_constraint_into_two_limbs function was called + if (range_lists_range_tags.contains(real_variable_tags[real_index])) { + variables_in_one_gate.erase(real_index); + } + } + } +} + +/** + * @brief this method removes false cases variables from aes plookup tables. * AES_SBOX_MAP, AES_SPARSE_MAP, AES_SPARSE_NORMALIZE tables are used in read_from_1_to_2_table function which * return values C2[0], so C3[0] isn't used anymore in these cases, but this situation isn't dangerous. * So, we have to remove these variables. @@ -699,21 +1093,60 @@ inline void Graph_<FF>::remove_unnecessary_plookup_variables(bb::UltraCircuitBui } } +/** + * @brief this method removes record witness variables from variables in one gate. + * initially record witness is added in the circuit as ctx->add_variable(0), where ctx -- circuit builder. + * then aren't used anymore, so we can remove from the static analyzer. + * @tparam FF + * @param ultra_builder + */ + +template <typename FF> +inline void Graph_<FF>::remove_record_witness_variables(bb::UltraCircuitBuilder& ultra_builder) { + const auto& gate_blocks = ultra_builder.blocks.get_gate_blocks(); + size_t blk_idx = find_block_index(ultra_builder, ultra_builder.blocks.aux); + std::vector<uint32_t> to_remove; + ASSERT(blk_idx == 3); + for (const auto& var_idx: variables_in_one_gate) { + KeyPair key = {var_idx, blk_idx}; + if (auto search = variable_gates.find(key); search != variable_gates.end()) { + std::vector<size_t> gate_indexes = variable_gates[key]; + ASSERT(gate_indexes.size() == 1); + size_t gate_idx = gate_indexes[0]; + auto q_1 = gate_blocks[blk_idx].q_1()[gate_idx]; + auto q_2 = gate_blocks[blk_idx].q_2()[gate_idx]; + auto q_3 = gate_blocks[blk_idx].q_3()[gate_idx]; + auto q_4 = gate_blocks[blk_idx].q_4()[gate_idx]; + auto q_m = gate_blocks[blk_idx].q_m()[gate_idx]; + auto q_arith = gate_blocks[blk_idx].q_arith()[gate_idx]; + if (q_1 == 1 && q_m == 1 && q_2 == 0 && q_3 == 0 && q_4 == 0 && q_arith == 0) { + //record witness can be in both ROM and RAM gates, so we can ignore q_c + //record witness is written as 4th variable in RAM/ROM read/write gate, so we can get 4th wire value and check it with our variable + if (this->to_real(ultra_builder, gate_blocks[blk_idx].w_4()[gate_idx]) == var_idx) { + to_remove.emplace_back(var_idx); + } + } + } + } + for (const auto& elem: to_remove) { + variables_in_one_gate.erase(elem); + } +} + /** * @brief this method returns a final set of variables that were in one gate * @tparam FF - * @param ultra_circuit_builder - * @return std::unordered_set<uint32_t> + * @param ultra_circuit_builder circuit builder containing the variables + * @return std::unordered_set<uint32_t> set of variable indices */ template <typename FF> std::unordered_set<uint32_t> Graph_<FF>::show_variables_in_one_gate(bb::UltraCircuitBuilder& ultra_circuit_builder) { - std::unordered_set<uint32_t> variables_in_one_gate; for (const auto& pair : variables_gate_counts) { bool is_not_constant_variable = this->check_is_not_constant_variable(ultra_circuit_builder, pair.first); if (pair.second == 1 && pair.first != 0 && is_not_constant_variable) { - variables_in_one_gate.insert(pair.first); + this->variables_in_one_gate.insert(pair.first); } } auto range_lists = ultra_circuit_builder.range_lists; @@ -727,18 +1160,22 @@ std::unordered_set<uint32_t> Graph_<FF>::show_variables_in_one_gate(bb::UltraCir } } } - this->remove_unnecessary_decompose_variables(ultra_circuit_builder, variables_in_one_gate, decompose_varialbes); - this->remove_unnecessary_plookup_variables(ultra_circuit_builder, variables_in_one_gate); + this->remove_unnecessary_decompose_variables( + ultra_circuit_builder, this->variables_in_one_gate, decompose_varialbes); + this->remove_unnecessary_plookup_variables(ultra_circuit_builder, this->variables_in_one_gate); + this->remove_unnecessary_range_constrains_variables(ultra_circuit_builder); + for (const auto& elem: this->fixed_variables) { + this->variables_in_one_gate.erase(elem); + } + this->remove_record_witness_variables(ultra_circuit_builder); return variables_in_one_gate; } /** - * @brief this method returns connected component with a given index and size of this component - * sometimes for debugging we want to check the size one of the connected component, so it would be - * useful to know its size - * @param connected_components - * @param index - * @return std::pair<std::vector<uint32_t>, size_t> + * @brief this method returns connected component with a given index and its size + * @param connected_components vector of all connected components + * @param index index of required component + * @return std::pair<std::vector<uint32_t>, size_t> pair of component and its size */ std::pair<std::vector<uint32_t>, size_t> get_connected_component_with_index( @@ -763,7 +1200,7 @@ std::pair<std::vector<uint32_t>, size_t> get_connected_component_with_index( template <typename FF> void Graph_<FF>::print_graph() { for (const auto& elem : variable_adjacency_lists) { - info("variable with index", elem.first); + info("variable with index ", elem.first); if (variable_adjacency_lists[elem.first].empty()) { info("is isolated"); } else { @@ -791,9 +1228,7 @@ template <typename FF> void Graph_<FF>::print_connected_components() } /** - * @brief this method prints a number of gates for each variable. - * while processing the arithmetic circuit, we count for each variable the number of gates it has participated in. - * sometimes for debugging purposes it is useful to see how many gates each variable has participated in. + * @brief this method prints a number of gates for each variable * @tparam FF */ @@ -805,20 +1240,38 @@ template <typename FF> void Graph_<FF>::print_variables_gate_counts() } /** - * @brief this method prints a number of edges for each variable. - * while processing the arithmetic circuit, we conut for each variable the number of edges, i.e. connections with other - * variables though the gates. perhaps in the future counting the number of edges for each vertex can be useful for - * analysis, and this function will be used for debugging. - * @tparam FF + * @brief this method prints a number of edges for each variable + * @tparam FF + * @param ultra_builder */ -template <typename FF> void Graph_<FF>::print_variables_edge_counts() -{ - for (const auto& it : variables_degree) { - if (it.first != 0) { - info("variable index = ", it.first, "number of edges for this variable = ", it.second); - } - } -} + template <typename FF> void Graph_<FF>::print_variables_in_one_gate(bb::UltraCircuitBuilder& ultra_builder) + { + const auto& gate_blocks = ultra_builder.blocks.get_gate_blocks(); + for (const auto& [key, gates]: variable_gates) { + if (variables_in_one_gate.contains(key.first)) { + ASSERT(gates.size() == 1); + size_t gate_index = gates[0]; + UltraBlock block = gate_blocks[key.second]; + info("---- printing gate selectors where variable with index ", key.first, " was found ----"); + info("q_m == ", block.q_m()[gate_index]); + info("q_c == ", block.q_c()[gate_index]); + info("q_1 == ", block.q_1()[gate_index]); + info("q_2 == ", block.q_2()[gate_index]); + info("q_3 == ", block.q_3()[gate_index]); + info("q_4 == ", block.q_4()[gate_index]); + info("q_arith == ", block.q_arith()[gate_index]); + info("q_delta_range == ", block.q_delta_range()[gate_index]); + info("q_elliptic == ", block.q_elliptic()[gate_index]); + info("q_aux == ", block.q_aux()[gate_index]); + info("q_lookup_type == ", block.q_lookup_type()[gate_index]); + info("q_poseidon2_external == ", block.q_poseidon2_external()[gate_index]); + info("q_poseidon2_internal == ", block.q_poseidon2_internal()[gate_index]); + info("---- finished printing ----"); + } + } + } template class Graph_<bb::fr>; + +} //namespace cdg diff --git a/barretenberg/cpp/src/barretenberg/boomerang_value_detection/graph.hpp b/barretenberg/cpp/src/barretenberg/boomerang_value_detection/graph.hpp index c4c88e1e159..13ee090ee47 100644 --- a/barretenberg/cpp/src/barretenberg/boomerang_value_detection/graph.hpp +++ b/barretenberg/cpp/src/barretenberg/boomerang_value_detection/graph.hpp @@ -7,6 +7,7 @@ #include <unordered_set> #include <utility> #include <vector> +#include <typeinfo> /* * this class describes arithmetic circuit as an undirected graph, where vertices are variables from circuit. @@ -16,6 +17,38 @@ * constrained properly. if number of connected components > 1, it means that there were missed some connections between * variables. */ + +namespace cdg { + +/* + * we add a new feature for static analyzer, now it contains gates where it found every variable. This may be helpful, if we want to do functions that + * remove false-positive variables from the analyzer using selectors in the gate + some additional knowledge about this variable, for example, tau or range tags. + * this info contains in unordered map with key as std::pair<uint32_t, size_t>, where uint32_t -- real variable index and size_t -- index of UltraTraceBlock in + * Reference Array with all TraceBlocks, that Ultra Circuit Builder contains inside. But there was a problem with unordered map -- it doesn't have default hash function and + * function for checking equivalence for std::pair as a key, so we had to implement it ourselves. We decided to choose approach based on function hash_combine from boost library + * for C++, and it's not so difficult to hash 2 elements in pair and check their equivalence. +*/ +using UltraBlock = bb::UltraCircuitBuilder::Arithmetization::UltraTraceBlock; +using KeyPair = std::pair<uint32_t, size_t>; + +struct KeyHasher{ + size_t operator()(const KeyPair& pair) const { + size_t combined_hash = 0; + auto hash_combiner = [](size_t lhs, size_t rhs) { + return lhs ^ (rhs + 0x9e3779b9 + (lhs << 6) + (lhs >> 2)); + }; + combined_hash = hash_combiner(combined_hash, std::hash<uint32_t>()(pair.first)); + combined_hash = hash_combiner(combined_hash, std::hash<size_t>()(pair.second)); + return combined_hash; + } +}; + +struct KeyEquals{ + bool operator()(const KeyPair& p1, const KeyPair& p2) const { + return (p1.first == p2.first && p1.second == p2.second); + } +}; + template <typename FF> class Graph_ { public: Graph_() = default; @@ -25,24 +58,36 @@ template <typename FF> class Graph_ { Graph_&& operator=(Graph_&& other) = delete; Graph_(const bb::StandardCircuitBuilder_<FF>& circuit_constructor); Graph_(bb::UltraCircuitBuilder& ultra_circuit_constructor); - + uint32_t to_real(bb::UltraCircuitBuilder& ultra_circuit_constructor, const uint32_t& variable_index) { return ultra_circuit_constructor.real_variable_index[variable_index]; }; + size_t find_block_index(bb::UltraCircuitBuilder& ultra_builder, const UltraBlock& block); void process_gate_variables(bb::UltraCircuitBuilder& ultra_circuit_constructor, - std::vector<uint32_t>& gate_variables); - + std::vector<uint32_t>& gate_variables, + size_t gate_index, + size_t blk_idx); std::unordered_map<uint32_t, size_t> get_variables_gate_counts() { return this->variables_gate_counts; }; - std::vector<uint32_t> get_arithmetic_gate_connected_component(bb::UltraCircuitBuilder& ultra_circuit_builder, - size_t index); + std::vector<std::vector<uint32_t>> get_arithmetic_gate_connected_component(bb::UltraCircuitBuilder& ultra_circuit_builder, + size_t index, size_t block_idx, UltraBlock& blk); std::vector<uint32_t> get_elliptic_gate_connected_component(bb::UltraCircuitBuilder& ultra_circuit_builder, - size_t index); + size_t index, size_t block_idx, UltraBlock& blk); std::vector<uint32_t> get_plookup_gate_connected_component(bb::UltraCircuitBuilder& ultra_circuit_builder, - size_t index); + size_t index, size_t block_idx, UltraBlock& blk); std::vector<uint32_t> get_sort_constraint_connected_component(bb::UltraCircuitBuilder& ultra_circuit_builder, - size_t index); + size_t index, size_t block_idx, UltraBlock& blk); + std::vector<uint32_t> get_poseido2s_gate_connected_component(bb::UltraCircuitBuilder& ultra_circuit_builder, + size_t index, + size_t block_idx, + UltraBlock& blk); + std::vector<uint32_t> get_auxiliary_gate_connected_component(bb::UltraCircuitBuilder& ultra_circuit_builder, + size_t index, size_t block_idx, UltraBlock& blk); + std::vector<uint32_t> get_rom_table_connected_component(bb::UltraCircuitBuilder& ultra_circuit_builder, + const bb::UltraCircuitBuilder::RomTranscript& rom_array); + std::vector<uint32_t> get_ram_table_connected_component(bb::UltraCircuitBuilder& ultra_builder, + const bb::UltraCircuitBuilder::RamTranscript& ram_array); void add_new_edge(const uint32_t& first_variable_index, const uint32_t& second_variable_index); std::vector<uint32_t> get_variable_adjacency_list(const uint32_t& variable_index) @@ -89,6 +134,7 @@ template <typename FF> class Graph_ { const std::unordered_set<uint32_t>& decompose_variables); void remove_unnecessary_plookup_variables(bb::UltraCircuitBuilder& ultra_circuit_builder, std::unordered_set<uint32_t>& variables_in_on_gate); + void remove_unnecessary_range_constrains_variables(bb::UltraCircuitBuilder& ultra_builder); std::unordered_set<uint32_t> show_variables_in_one_gate(bb::UltraCircuitBuilder& ultra_circuit_builder); void remove_unnecessary_aes_plookup_variables(std::unordered_set<uint32_t>& variables_in_one_gate, @@ -99,11 +145,13 @@ template <typename FF> class Graph_ { bb::UltraCircuitBuilder& ultra_circuit_builder, bb::plookup::BasicTableId& table_id, size_t gate_index); + void remove_record_witness_variables(bb::UltraCircuitBuilder& ultra_builder); void print_graph(); void print_connected_components(); void print_variables_gate_counts(); void print_variables_edge_counts(); + void print_variables_in_one_gate(bb::UltraCircuitBuilder& ultra_builder); ~Graph_() = default; private: @@ -114,6 +162,10 @@ template <typename FF> class Graph_ { variables_gate_counts; // we use this data structure to count, how many gates use every variable std::unordered_map<uint32_t, size_t> variables_degree; // we use this data structure to count, how many every variable have edges + std::unordered_map<KeyPair, std::vector<size_t>, KeyHasher, KeyEquals> variable_gates; //we use this data structure to store gates and TraceBlocks for every variables, where static analyzer found them in the circuit. + std::unordered_set<uint32_t> variables_in_one_gate; + std::unordered_set<uint32_t> fixed_variables; }; -using Graph = Graph_<bb::fr>; \ No newline at end of file +using Graph = Graph_<bb::fr>; +} //namespace cgd diff --git a/barretenberg/cpp/src/barretenberg/boomerang_value_detection/graph_description.test.cpp b/barretenberg/cpp/src/barretenberg/boomerang_value_detection/graph_description.test.cpp index 26f50cc8db1..e357192a347 100644 --- a/barretenberg/cpp/src/barretenberg/boomerang_value_detection/graph_description.test.cpp +++ b/barretenberg/cpp/src/barretenberg/boomerang_value_detection/graph_description.test.cpp @@ -11,7 +11,7 @@ #include <gtest/gtest.h> using namespace bb; - +using namespace cdg; /** * @brief this test checks graph description of the circuit with arithmetic gates the number of connected components = the number of pair (i, j), 0<=i, j <16, i.e 256 @@ -38,10 +38,8 @@ TEST(boomerang_ultra_circuit_constructor, test_graph_for_arithmetic_gates) Graph graph = Graph(circuit_constructor); auto connected_components = graph.find_connected_components(); - auto num_connected_components = connected_components.size(); - auto variables_in_one_gate = graph.show_variables_in_one_gate(circuit_constructor); - bool result = num_connected_components == 256; - EXPECT_EQ(result, true); + [[maybe_unused]]auto variables_in_one_gate = graph.show_variables_in_one_gate(circuit_constructor); + EXPECT_EQ(connected_components.size(), 256); } /** diff --git a/barretenberg/cpp/src/barretenberg/boomerang_value_detection/graph_description_aes128.test.cpp b/barretenberg/cpp/src/barretenberg/boomerang_value_detection/graph_description_aes128.test.cpp index 9db54f96429..45e458f72de 100644 --- a/barretenberg/cpp/src/barretenberg/boomerang_value_detection/graph_description_aes128.test.cpp +++ b/barretenberg/cpp/src/barretenberg/boomerang_value_detection/graph_description_aes128.test.cpp @@ -12,19 +12,17 @@ using namespace bb; using namespace bb::stdlib; +using namespace cdg; using Builder = UltraCircuitBuilder; -typedef stdlib::field_t<UltraCircuitBuilder> field_pt; -typedef stdlib::witness_t<bb::UltraCircuitBuilder> witness_pt; +using field_pt = stdlib::field_t<UltraCircuitBuilder>; +using witness_pt = stdlib::witness_t<bb::UltraCircuitBuilder>; -bool check_in_vector(const std::vector<field_pt>& input_vector, const uint32_t& real_var_index) +void fix_vector_witness(std::vector<field_pt>& input_vector) { - for (const auto& elem : input_vector) { - if (elem.witness_index == real_var_index) { - return true; - } + for (auto& elem : input_vector) { + elem.fix_witness(); } - return false; } /** @@ -41,7 +39,7 @@ TEST(boomerang_stdlib_aes, test_graph_for_aes_64_bytes) 0x30, 0xc8, 0x1c, 0x46, 0xa3, 0x5c, 0xe4, 0x11, 0xe5, 0xfb, 0xc1, 0x19, 0x1a, 0x0a, 0x52, 0xef, 0xf6, 0x9f, 0x24, 0x45, 0xdf, 0x4f, 0x9b, 0x17, 0xad, 0x2b, 0x41, 0x7b, 0xe6, 0x6c, 0x37, 0x10 }; - const auto convert_bytes = [](uint8_t* data) { + auto convert_bytes = [](uint8_t* data) { uint256_t converted(0); for (uint64_t i = 0; i < 16; ++i) { uint256_t to_add = uint256_t((uint64_t)(data[i])) << uint256_t((15 - i) * 8); @@ -59,17 +57,21 @@ TEST(boomerang_stdlib_aes, test_graph_for_aes_64_bytes) witness_pt(&builder, fr(convert_bytes(in + 48))), }; + fix_vector_witness(in_field); + field_pt key_field(witness_pt(&builder, fr(convert_bytes(key)))); field_pt iv_field(witness_pt(&builder, fr(convert_bytes(iv)))); + key_field.fix_witness(); + iv_field.fix_witness(); - const auto result = stdlib::aes128::encrypt_buffer_cbc(in_field, iv_field, key_field); + auto result = stdlib::aes128::encrypt_buffer_cbc(in_field, iv_field, key_field); + fix_vector_witness(result); Graph graph = Graph(builder); auto connected_components = graph.find_connected_components(); - auto num_connected_components = connected_components.size(); - bool graph_result = num_connected_components == 1; - - EXPECT_EQ(graph_result, true); + EXPECT_EQ(connected_components.size(), 1); + auto variables_in_one_gate = graph.show_variables_in_one_gate(builder); + EXPECT_EQ(variables_in_one_gate.size(), 0); } /** @@ -88,7 +90,7 @@ TEST(boomerang_stdlib_aes, test_variable_gates_count_for_aes128cbc) 0x30, 0xc8, 0x1c, 0x46, 0xa3, 0x5c, 0xe4, 0x11, 0xe5, 0xfb, 0xc1, 0x19, 0x1a, 0x0a, 0x52, 0xef, 0xf6, 0x9f, 0x24, 0x45, 0xdf, 0x4f, 0x9b, 0x17, 0xad, 0x2b, 0x41, 0x7b, 0xe6, 0x6c, 0x37, 0x10 }; - const auto convert_bytes = [](uint8_t* data) { + auto convert_bytes = [](uint8_t* data) { uint256_t converted(0); for (uint64_t i = 0; i < 16; ++i) { uint256_t to_add = uint256_t((uint64_t)(data[i])) << uint256_t((15 - i) * 8); @@ -106,18 +108,19 @@ TEST(boomerang_stdlib_aes, test_variable_gates_count_for_aes128cbc) witness_pt(&builder, fr(convert_bytes(in + 48))), }; + fix_vector_witness(in_field); + field_pt key_field(witness_pt(&builder, fr(convert_bytes(key)))); field_pt iv_field(witness_pt(&builder, fr(convert_bytes(iv)))); + key_field.fix_witness(); + iv_field.fix_witness(); - const auto result = stdlib::aes128::encrypt_buffer_cbc(in_field, iv_field, key_field); + auto result = stdlib::aes128::encrypt_buffer_cbc(in_field, iv_field, key_field); + fix_vector_witness(result); Graph graph = Graph(builder); + auto connected_components = graph.find_connected_components(); + EXPECT_EQ(connected_components.size(), 1); std::unordered_set<uint32_t> variables_in_one_gate = graph.show_variables_in_one_gate(builder); - for (const auto& elem : variables_in_one_gate) { - bool result1 = check_in_vector(in_field, elem); - bool result2 = check_in_vector(result, elem); - bool check = - (result1 == 1) || (result2 == 1) || (elem == key_field.witness_index) || (elem == iv_field.witness_index); - EXPECT_EQ(check, true); - } + EXPECT_EQ(variables_in_one_gate.size(), 0); } \ No newline at end of file diff --git a/barretenberg/cpp/src/barretenberg/boomerang_value_detection/graph_description_bigfield.test.cpp b/barretenberg/cpp/src/barretenberg/boomerang_value_detection/graph_description_bigfield.test.cpp new file mode 100644 index 00000000000..cfdf030c58a --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/boomerang_value_detection/graph_description_bigfield.test.cpp @@ -0,0 +1,397 @@ +#include "barretenberg/boomerang_value_detection/graph.hpp" +#include "barretenberg/stdlib/primitives/bigfield/bigfield.hpp" +#include "barretenberg/numeric/random/engine.hpp" + +#include "barretenberg/ecc/curves/bn254/fq.hpp" +#include "barretenberg/ecc/curves/bn254/fr.hpp" + +#include "barretenberg/stdlib/primitives/bool/bool.hpp" +#include "barretenberg/stdlib/primitives/byte_array/byte_array.hpp" +#include "barretenberg/stdlib/primitives/field/field.hpp" +#include "barretenberg/circuit_checker/circuit_checker.hpp" +#include "barretenberg/stdlib/primitives/circuit_builders/circuit_builders.hpp" +#include "barretenberg/stdlib/primitives/curves/bn254.hpp" +#include "barretenberg/transcript/origin_tag.hpp" +#include "barretenberg/common/test.hpp" +#include <memory> +#include <utility> + +using namespace bb; +using namespace cdg; + +namespace { +auto& engine = numeric::get_debug_randomness(); +} + +using Builder = UltraCircuitBuilder; +using bn254 = stdlib::bn254<Builder>; +using fr_ct = bn254::ScalarField; +using fq_ct = bn254::BaseField; +using public_witness_ct = bn254::public_witness_ct; +using witness_ct = bn254::witness_ct; + +void fix_bigfield_element(const fq_ct& element) { + for (int i = 0; i < 4; i++) { + element.binary_basis_limbs[i].element.fix_witness(); + } + element.prime_basis_limb.fix_witness(); +} + +/** + * @brief this test checks graph description for bigfield constructors + * The result is one connected component with one variable in one gate, + * testing different types of bigfield construction + * @details Tests construction of: + * - Constant value + * - Witness from u512 + * - Small field witness + * - Mixed construction with lower limb addition + */ +TEST(boomerang_bigfield, test_graph_description_bigfield_constructors) { + Builder builder; + [[maybe_unused]]fq_ct constant = fq_ct(1); + [[maybe_unused]]fq_ct var = fq_ct::create_from_u512_as_witness(&builder, 1); + [[maybe_unused]]fr_ct small_var = witness_ct(&builder, fr(1)); + [[maybe_unused]]fq_ct mixed = fq_ct(1).add_to_lower_limb(small_var, 1); + [[maybe_unused]]fq_ct r; + + auto graph = Graph(builder); + auto connected_components = graph.find_connected_components(); + EXPECT_EQ(connected_components.size(), 1); + auto variables_in_one_gate = graph.show_variables_in_one_gate(builder); + EXPECT_EQ(variables_in_one_gate.size(), 1); +} + +/** + * @brief this test checks graph description for bigfield addition operations + * The result is one connected component with no variables in one gate, + * testing various addition combinations with fix_bigfield_element + */ +TEST(boomerang_bigfield, test_graph_description_bigfield_addition) { + Builder builder; + [[maybe_unused]]fq_ct var = fq_ct::create_from_u512_as_witness(&builder, 1); + [[maybe_unused]]fr_ct small_var = witness_ct(&builder, fr(1)); + [[maybe_unused]]fq_ct mixed = fq_ct(1).add_to_lower_limb(small_var, 1); + [[maybe_unused]]fq_ct r; + [[maybe_unused]]fq_ct r1; + [[maybe_unused]]fq_ct r2; + + r = mixed + var; + fix_bigfield_element(r); + r1 = r + mixed; + fix_bigfield_element(r1); + r2 = r + var; + fix_bigfield_element(r2); + + auto graph = Graph(builder); + auto connected_components = graph.find_connected_components(); + EXPECT_EQ(connected_components.size(), 1); + auto variables_in_one_gate = graph.show_variables_in_one_gate(builder); + EXPECT_EQ(variables_in_one_gate.size(), 0); +} + +/** + * @brief this test checks graph description for bigfield subtraction operations + * The result is one connected component with no variables in one gate, + * testing all possible subtraction combinations between mixed, constant, and variable values + */ +TEST(boomerang_bigfield, test_graph_description_bigfield_substraction) { + Builder builder; + [[maybe_unused]]fq_ct constant = fq_ct(1); + [[maybe_unused]]fq_ct var = fq_ct::create_from_u512_as_witness(&builder, 1); + [[maybe_unused]]fr_ct small_var = witness_ct(&builder, fr(1)); + [[maybe_unused]]fq_ct mixed = fq_ct(1).add_to_lower_limb(small_var, 1); + [[maybe_unused]]fq_ct r; + + r = mixed - mixed; + fix_bigfield_element(r); + r = mixed - constant; + fix_bigfield_element(r); + r = mixed - var; + fix_bigfield_element(r); + r = var - mixed; + fix_bigfield_element(r); + + auto graph = Graph(builder); + auto connected_components = graph.find_connected_components(); + EXPECT_EQ(connected_components.size(), 1); + auto variables_in_one_gate = graph.show_variables_in_one_gate(builder); + EXPECT_EQ(variables_in_one_gate.size(), 0); + for (const auto& elem: variables_in_one_gate) { + info("elem == ", elem); + } +} + +/** + * @brief this test checks graph description for bigfield multiplication operations + * The result is one connected component with no variables in one gate, + * testing all possible multiplication combinations + */ +TEST(boomerang_bigfield, test_graph_description_bigfield_multiplication) { + Builder builder; + [[maybe_unused]]fq_ct constant = fq_ct(1); + [[maybe_unused]]fq_ct var = fq_ct::create_from_u512_as_witness(&builder, 1); + [[maybe_unused]]fr_ct small_var = witness_ct(&builder, fr(1)); + [[maybe_unused]]fq_ct mixed = fq_ct(1).add_to_lower_limb(small_var, 1); + [[maybe_unused]]fq_ct r; + + r = var * constant; + r = constant * constant; + r = mixed * var; + r = mixed * constant; + r = mixed * mixed; + auto graph = Graph(builder); + auto connected_components = graph.find_connected_components(); + EXPECT_EQ(connected_components.size(), 1); + auto variables_in_one_gate = graph.show_variables_in_one_gate(builder); + EXPECT_EQ(variables_in_one_gate.size(), 0); +} + +/** + * @brief this test checks graph description for bigfield division operations + * The result is one connected component with three variables in one gate, + * testing division operations with circuit checking + * @details Each division operator creates one inverse variable for polynomial gate check (a * a_inv - 1 = 0) + */ +TEST(boomerang_bigfield, test_graph_description_bigfield_division) { + Builder builder; + [[maybe_unused]]fq_ct constant = fq_ct(1); + [[maybe_unused]]fq_ct var = fq_ct::create_from_u512_as_witness(&builder, 1); + [[maybe_unused]]fr_ct small_var = witness_ct(&builder, fr(1)); + [[maybe_unused]]fq_ct mixed = fq_ct(1).add_to_lower_limb(small_var, 1); + [[maybe_unused]]fq_ct r; + + r = constant / var; + fix_bigfield_element(r); + r = constant / constant; + r = mixed / mixed; + fix_bigfield_element(r); + r = mixed / var; + fix_bigfield_element(r); + + CircuitChecker::check(builder); + auto graph = Graph(builder); + auto connected_components = graph.find_connected_components(); + EXPECT_EQ(connected_components.size(), 1); + auto variables_in_one_gate = graph.show_variables_in_one_gate(builder); + //every operator / in bigfield creates one inverse variable for poly gate to check a * a_inv - 1 = 0. + //it is the false case, but it will be secure just to check that there are no other variables except for them + //otherwise there is a possibility to remove dangerous variables from other functions. + EXPECT_EQ(variables_in_one_gate.size(), 3); +} + +/** + * @brief this test checks graph description for mixed bigfield operations + * The result is one connected component with two variables in one gate, + * testing combinations of addition, subtraction, multiplication and division + */ +TEST(boomerang_bigfield, test_graph_description_bigfield_mix_operations) { + auto builder = Builder(); + fq_ct constant = fq_ct(1); + fq_ct var = fq_ct::create_from_u512_as_witness(&builder, 1); + fr_ct small_var = witness_ct(&builder, fr(1)); + fq_ct mixed = fq_ct(1).add_to_lower_limb(small_var, 1); + fq_ct r; + + r = mixed + mixed; + fix_bigfield_element(r); + r = mixed - mixed; + fix_bigfield_element(r); + r = mixed + var; + fix_bigfield_element(r); + r = mixed + constant; + fix_bigfield_element(r); + r = mixed - var; + fix_bigfield_element(r); + r = mixed - constant; + fix_bigfield_element(r); + r = var - mixed; + fix_bigfield_element(r); + + r = var * constant; + fix_bigfield_element(r); + r = constant / var; + fix_bigfield_element(r); + r = constant * constant; + r = constant / constant; + + r = mixed * var; + fix_bigfield_element(r); + r = mixed / var; + fix_bigfield_element(r); + r = mixed * mixed; + fix_bigfield_element(r); + r = mixed * constant; + fix_bigfield_element(r); + auto graph = Graph(builder); + auto connected_components = graph.find_connected_components(); + EXPECT_EQ(connected_components.size(), 1); + auto variables_in_one_gate = graph.show_variables_in_one_gate(builder); + EXPECT_EQ(variables_in_one_gate.size(), 2); +} + +/** + * @brief this test checks graph description for high/low bits constructor and operations + * The result is one connected component with no variables in one gate, + * testing bit-sliced construction and repeated additions + */ +TEST(boomerang_bigfield, test_graph_description_constructor_high_low_bits_and_operations) { + auto builder = Builder(); + size_t num_repetitions = 3; + fq inputs[2]{ fq::random_element(), fq::random_element() }; + fq_ct a(witness_ct(&builder, fr(uint256_t(inputs[0]).slice(0, fq_ct::NUM_LIMB_BITS * 2))), + witness_ct(&builder, fr(uint256_t(inputs[0]).slice(fq_ct::NUM_LIMB_BITS * 2, fq_ct::NUM_LIMB_BITS * 4)))); + fq_ct b(witness_ct(&builder, fr(uint256_t(inputs[1]).slice(0, fq_ct::NUM_LIMB_BITS * 2))), + witness_ct(&builder, fr(uint256_t(inputs[1]).slice(fq_ct::NUM_LIMB_BITS * 2, fq_ct::NUM_LIMB_BITS * 4)))); + fq_ct c = a * b; + for (size_t i = 0; i < num_repetitions; ++i) { + fq d = fq::random_element(); + fq_ct d1(witness_ct(&builder, fr(uint256_t(d).slice(0, fq_ct::NUM_LIMB_BITS * 2))), + witness_ct(&builder, fr(uint256_t(d).slice(fq_ct::NUM_LIMB_BITS * 2, fq_ct::NUM_LIMB_BITS * 4)))); + c = c + d1; + } + fix_bigfield_element(c); + auto graph = Graph(builder); + auto connected_components = graph.find_connected_components(); + EXPECT_EQ(connected_components.size(), 1); + auto variables_in_one_gate = graph.show_variables_in_one_gate(builder); + EXPECT_EQ(variables_in_one_gate.size(), 0); +} + +/** + * @brief this test checks graph description for multiple multiplication operations + * The result is num_repetitions connected components with no variables in one gate, + * testing independent multiplication operations + */ +TEST(boomerang_bigfield, test_graph_description_mul_function) { + auto builder = Builder(); + size_t num_repetitions = 4; + for (size_t i = 0; i < num_repetitions; ++i) { + fq inputs[2]{ fq::random_element(), fq::random_element()}; + fq_ct a(witness_ct(&builder, fr(uint256_t(inputs[0]).slice(0, fq_ct::NUM_LIMB_BITS * 2))), + witness_ct(&builder, fr(uint256_t(inputs[0]).slice(fq_ct::NUM_LIMB_BITS * 2, fq_ct::NUM_LIMB_BITS * 4)))); + fq_ct b(witness_ct(&builder, fr(uint256_t(inputs[1]).slice(0, fq_ct::NUM_LIMB_BITS * 2))), + witness_ct(&builder, fr(uint256_t(inputs[1]).slice(fq_ct::NUM_LIMB_BITS * 2, fq_ct::NUM_LIMB_BITS * 4)))); + + fq_ct c = a * b; + fix_bigfield_element(c); + } + auto graph = Graph(builder); + auto connected_components = graph.find_connected_components(); + EXPECT_EQ(connected_components.size(), num_repetitions); + auto variables_in_one_gate = graph.show_variables_in_one_gate(builder); + EXPECT_EQ(variables_in_one_gate.size(), 0); +} + +/** + * @brief this test checks graph description for square operations + * The result is num_repetitions connected components with no variables in one gate, + * testing repeated squaring operations on random inputs + */ +TEST(boomerang_bigfield, test_graph_description_sqr_function) { + auto builder = Builder(); + size_t num_repetitions = 10; + for (size_t i = 0; i < num_repetitions; ++i) { + fq input = fq::random_element(); + fq_ct a(witness_ct(&builder, fr(uint256_t(input).slice(0, fq_ct::NUM_LIMB_BITS * 2))), + witness_ct(&builder, fr(uint256_t(input).slice(fq_ct::NUM_LIMB_BITS * 2, fq_ct::NUM_LIMB_BITS * 4)))); + fq_ct c = a.sqr(); + fix_bigfield_element(c); + } + auto graph = Graph(builder); + auto connected_components = graph.find_connected_components(); + EXPECT_EQ(connected_components.size(), num_repetitions); + auto variables_in_one_gate = graph.show_variables_in_one_gate(builder); + EXPECT_EQ(variables_in_one_gate.size(), 0); +} + +/** + * @brief this test checks graph description for multiply-add operations + * The result is num_repetitions connected components with no variables in one gate, + * testing multiply-add operations with three inputs + */ +TEST(boomerang_bigfield, test_graph_description_madd_function) { + auto builder = Builder(); + size_t num_repetitions = 5; + for (size_t i = 0; i < num_repetitions; ++i) { + fq inputs[3]{ fq::random_element(), fq::random_element(), fq::random_element() }; + fq_ct a(witness_ct(&builder, fr(uint256_t(inputs[0]).slice(0, fq_ct::NUM_LIMB_BITS * 2))), + witness_ct(&builder, fr(uint256_t(inputs[0]).slice(fq_ct::NUM_LIMB_BITS * 2, fq_ct::NUM_LIMB_BITS * 4)))); + fq_ct b(witness_ct(&builder, fr(uint256_t(inputs[1]).slice(0, fq_ct::NUM_LIMB_BITS * 2))), + witness_ct(&builder, fr(uint256_t(inputs[1]).slice(fq_ct::NUM_LIMB_BITS * 2, fq_ct::NUM_LIMB_BITS * 4)))); + fq_ct c(witness_ct(&builder, fr(uint256_t(inputs[2]).slice(0, fq_ct::NUM_LIMB_BITS * 2))), + witness_ct(&builder, fr(uint256_t(inputs[2]).slice(fq_ct::NUM_LIMB_BITS * 2, fq_ct::NUM_LIMB_BITS * 4)))); + [[maybe_unused]]fq_ct d = a.madd(b, { c }); + fix_bigfield_element(d); + } + auto graph = Graph(builder); + auto connected_components = graph.find_connected_components(); + EXPECT_EQ(connected_components.size(), num_repetitions); + auto variables_in_one_gate = graph.show_variables_in_one_gate(builder); + EXPECT_EQ(variables_in_one_gate.size(), 0); +} + +/** + * @brief this test checks graph description for multiple multiply-add operations + * The result is connected components with no variables in one gate, + * testing batch multiply-add operations with multiple inputs + * @details Uses arrays of size number_of_madds=16 for left multiply, right multiply and add values + */ +TEST(boomerang_bigfield, test_graph_description_mult_madd_function) { + + auto builder = Builder(); + size_t num_repetitions = 1; + const size_t number_of_madds = 16; + for (size_t i = 0; i < num_repetitions; ++i) { + fq mul_left_values[number_of_madds]; + fq mul_right_values[number_of_madds]; + fq to_add_values[number_of_madds]; + + std::vector<fq_ct> mul_left; + std::vector<fq_ct> mul_right; + std::vector<fq_ct> to_add; + mul_left.reserve(number_of_madds); + mul_right.reserve(number_of_madds); + to_add.reserve(number_of_madds); + for (size_t j = 0; j < number_of_madds; j++) { + mul_left_values[j] = fq::random_element(); + mul_right_values[j] = fq::random_element(); + mul_left.emplace_back( + fq_ct::create_from_u512_as_witness(&builder, uint512_t(uint256_t(mul_left_values[j])))); + mul_right.emplace_back( + fq_ct::create_from_u512_as_witness(&builder, uint512_t(uint256_t(mul_right_values[j])))); + to_add_values[j] = fq::random_element(); + to_add.emplace_back( + fq_ct::create_from_u512_as_witness(&builder, uint512_t(uint256_t(to_add_values[j])))); + } + fq_ct f = fq_ct::mult_madd(mul_left, mul_right, to_add); + fix_bigfield_element(f); + } + builder.finalize_circuit(false); + auto graph = Graph(builder); + auto variables_in_one_gate = graph.show_variables_in_one_gate(builder); + EXPECT_EQ(variables_in_one_gate.size(), 0); +} + +/** + * @brief this test checks graph description for high/low bits constructor + * The result is connected components with no variables in one gate, + * testing basic multiplication with bit-sliced construction + */ +TEST(boomerang_bigfield, test_graph_description_constructor_high_low_bits) +{ + auto builder = Builder(); + fq mul_left_value = fq::random_element(); + fq mul_right_value = fq::random_element(); + //fq mul_right_value = fq::random_element(); + [[maybe_unused]]fq_ct mul_left = fq_ct::create_from_u512_as_witness(&builder, uint512_t(uint256_t(mul_left_value))); + [[maybe_unused]]fq_ct mul_right = fq_ct::create_from_u512_as_witness(&builder, uint512_t(uint256_t(mul_right_value))); + fq_ct product = mul_left * mul_right; + fix_bigfield_element(product); + builder.finalize_circuit(false); + auto graph = Graph(builder); + auto connected_components = graph.find_connected_components(); + auto variables_in_one_gate = graph.show_variables_in_one_gate(builder); + EXPECT_EQ(variables_in_one_gate.size(), 0); +} + diff --git a/barretenberg/cpp/src/barretenberg/boomerang_value_detection/graph_description_blake2s.test.cpp b/barretenberg/cpp/src/barretenberg/boomerang_value_detection/graph_description_blake2s.test.cpp index 934e92b568a..a7711330f04 100644 --- a/barretenberg/cpp/src/barretenberg/boomerang_value_detection/graph_description_blake2s.test.cpp +++ b/barretenberg/cpp/src/barretenberg/boomerang_value_detection/graph_description_blake2s.test.cpp @@ -10,6 +10,10 @@ using namespace bb; using namespace bb::stdlib; +<<<<<<< HEAD +using namespace cdg; +======= +>>>>>>> a86b797d059502fbd402550492f9ad13bd4ede1c using Builder = UltraCircuitBuilder; @@ -21,7 +25,7 @@ using public_witness_t = public_witness_t<Builder>; /** * @brief this tests check graph description of circuit for blake2s for one and two blocks. - * all graphs must have one connected component. + * all graphs must have one connected component and 0 variables in one gate. */ TEST(boomerang_stdlib_blake2s, test_graph_for_blake2s_single_block_plookup) @@ -36,6 +40,8 @@ TEST(boomerang_stdlib_blake2s, test_graph_for_blake2s_single_block_plookup) Graph graph = Graph(builder); auto connected_components = graph.find_connected_components(); EXPECT_EQ(connected_components.size(), 1); + auto variables_in_one_gate = graph.show_variables_in_one_gate(builder); + EXPECT_EQ(variables_in_one_gate.size(), 0); } TEST(boomerang_stdlib_blake2s, test_graph_for_blake2s_double_block_plookup) @@ -54,4 +60,6 @@ TEST(boomerang_stdlib_blake2s, test_graph_for_blake2s_double_block_plookup) Graph graph = Graph(builder); auto connected_components = graph.find_connected_components(); EXPECT_EQ(connected_components.size(), 1); + auto variables_in_one_gate = graph.show_variables_in_one_gate(builder); + EXPECT_EQ(variables_in_one_gate.size(), 0); } diff --git a/barretenberg/cpp/src/barretenberg/boomerang_value_detection/graph_description_blake3s.test.cpp b/barretenberg/cpp/src/barretenberg/boomerang_value_detection/graph_description_blake3s.test.cpp index d826f1e080b..c8729d3ef44 100644 --- a/barretenberg/cpp/src/barretenberg/boomerang_value_detection/graph_description_blake3s.test.cpp +++ b/barretenberg/cpp/src/barretenberg/boomerang_value_detection/graph_description_blake3s.test.cpp @@ -9,6 +9,10 @@ #include <gtest/gtest.h> using namespace bb; +<<<<<<< HEAD +using namespace cdg; +======= +>>>>>>> a86b797d059502fbd402550492f9ad13bd4ede1c using byte_array = stdlib::byte_array<bb::StandardCircuitBuilder>; using public_witness_t = stdlib::public_witness_t<bb::StandardCircuitBuilder>; @@ -26,17 +30,14 @@ TEST(boomerang_stdlib_blake3s, test_single_block_plookup) auto builder = UltraBuilder(); std::string input = "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz01"; std::vector<uint8_t> input_v(input.begin(), input.end()); - byte_array_plookup input_arr(&builder, input_v); byte_array_plookup output = stdlib::blake3s(input_arr); - std::vector<uint8_t> expected = blake3::blake3s(input_v); - - EXPECT_EQ(output.get_value(), expected); - Graph graph = Graph(builder); auto connected_components = graph.find_connected_components(); EXPECT_EQ(connected_components.size(), 1); + auto variables_in_one_gate = graph.show_variables_in_one_gate(builder); + EXPECT_EQ(variables_in_one_gate.size(), 0); } TEST(boomerang_stdlib_blake3s, test_double_block_plookup) @@ -50,9 +51,9 @@ TEST(boomerang_stdlib_blake3s, test_double_block_plookup) std::vector<uint8_t> expected = blake3::blake3s(input_v); - EXPECT_EQ(output.get_value(), expected); - Graph graph = Graph(builder); auto connected_components = graph.find_connected_components(); EXPECT_EQ(connected_components.size(), 1); + auto variables_in_one_gate = graph.show_variables_in_one_gate(builder); + EXPECT_EQ(variables_in_one_gate.size(), 0); } \ No newline at end of file diff --git a/barretenberg/cpp/src/barretenberg/boomerang_value_detection/graph_description_dynamic_array.test.cpp b/barretenberg/cpp/src/barretenberg/boomerang_value_detection/graph_description_dynamic_array.test.cpp new file mode 100644 index 00000000000..3fba4f8b37d --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/boomerang_value_detection/graph_description_dynamic_array.test.cpp @@ -0,0 +1,89 @@ +#include "barretenberg/boomerang_value_detection/graph.hpp" +#include "barretenberg/common/test.hpp" +#include "barretenberg/numeric/random/engine.hpp" +#include "barretenberg/stdlib/primitives/bool/bool.hpp" +#include "barretenberg/stdlib/primitives/circuit_builders/circuit_builders.hpp" +#include "barretenberg/stdlib/primitives/memory/dynamic_array.hpp" + +using namespace bb; +using namespace cdg; +namespace { +auto& engine = bb::numeric::get_debug_randomness(); +} + +// Defining ultra-specific types for local testing. +using Builder = UltraCircuitBuilder; +using bool_ct = stdlib::bool_t<Builder>; +using field_ct = stdlib::field_t<Builder>; +using witness_ct = stdlib::witness_t<Builder>; +using DynamicArray_ct = stdlib::DynamicArray<Builder>; + +/** + * @brief this test checks graph description for dynamic array resize operation + * The result is one connected component with one variable in one gate, + * testing array initialization, pushing elements, and resizing operations + * @details Test includes: + * - Array initialization with max size + * - Sequential push of witness elements + * - Resize operation with witness size + */ +TEST(boomerang_stdlib_dynamic_array, graph_description_dynamic_array_method_resize_test) +{ + + Builder builder; + const size_t max_size = 10; + + DynamicArray_ct array(&builder, max_size); + + field_ct next_size = field_ct(witness_ct(&builder, (uint256_t)(max_size - 1))); + for (size_t i = 0; i < max_size; ++i) { + array.push(field_ct::from_witness(&builder, i)); + } + + array.resize(next_size, 7); + Graph graph = Graph(builder); + auto connected_components = graph.find_connected_components(); + auto variables_in_one_gate = graph.show_variables_in_one_gate(builder); + EXPECT_EQ(connected_components.size(), 1); + EXPECT_EQ(variables_in_one_gate.size(), max_size); +} + + +/** + * @brief this test checks graph description for dynamic array consistency methods + * The result is one connected component with no variables in one gate, + * testing all array manipulation operations + * @details Test includes sequence of operations: + * - Sequential push of witness elements + * - Sequential pop of all elements + * - Array resize + * - Conditional push operations (true and false cases) + * - Conditional pop operations (true and false cases) + */ +TEST(boomerang_stdlib_dynamic_array, graph_description_dynamic_array_consistency_methods) +{ + Builder builder; + const size_t max_size = 10; + + DynamicArray_ct array(&builder, max_size); + + for (size_t i = 0; i < max_size; ++i) { + array.push(field_ct::from_witness(&builder, i)); + } + + for (size_t i = 0; i < max_size; ++i) { + array.pop(); + } + + array.resize(max_size - 1, 7); + + array.conditional_push(false, 100); + array.conditional_push(true, 100); + array.conditional_pop(false); + array.conditional_pop(true); + Graph graph = Graph(builder); + auto connected_components = graph.find_connected_components(); + EXPECT_EQ(connected_components.size(), 1); + auto variables_in_one_gate = graph.show_variables_in_one_gate(builder); + EXPECT_EQ(variables_in_one_gate.size(), max_size); +} \ No newline at end of file diff --git a/barretenberg/cpp/src/barretenberg/boomerang_value_detection/graph_description_poseidon2s_permutation.test.cpp b/barretenberg/cpp/src/barretenberg/boomerang_value_detection/graph_description_poseidon2s_permutation.test.cpp new file mode 100644 index 00000000000..2fd690d4f32 --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/boomerang_value_detection/graph_description_poseidon2s_permutation.test.cpp @@ -0,0 +1,215 @@ +#include "barretenberg/boomerang_value_detection/graph.hpp" + +#include "barretenberg/crypto/poseidon2/poseidon2.hpp" +#include "barretenberg/crypto/poseidon2/poseidon2_params.hpp" +#include "barretenberg/stdlib/hash/poseidon2/poseidon2.hpp" +#include "barretenberg/stdlib/hash/poseidon2/poseidon2_permutation.hpp" + +#include "barretenberg/plonk_honk_shared/arithmetization/gate_data.hpp" +#include "barretenberg/stdlib/primitives/circuit_builders/circuit_builders.hpp" +#include "barretenberg/stdlib/primitives/curves/bn254.hpp" + +#include "barretenberg/circuit_checker/circuit_checker.hpp" +#include "barretenberg/common/test.hpp" +#include "barretenberg/numeric/random/engine.hpp" +using namespace bb; +using namespace cdg; + +namespace { +auto& engine = numeric::get_debug_randomness(); +} + +using Params = crypto::Poseidon2Bn254ScalarFieldParams; +using Builder = UltraCircuitBuilder; +using Permutation = stdlib::Poseidon2Permutation<Params, Builder>; +using field_t = stdlib::field_t<Builder>; +using witness_t = stdlib::witness_t<Builder>; +using _curve = stdlib::bn254<Builder>; +using byte_array_ct = _curve::byte_array_ct; +using fr_ct = typename _curve::ScalarField; +using witness_ct = typename _curve::witness_ct; + + +bool check_in_input_vector(const std::vector<field_t>& input_vector, const uint32_t& real_var_index) +{ + for (const auto& elem : input_vector) { + if (elem.witness_index == real_var_index) { + return true; + } + } + return false; +} + +/** + * @brief this test checks graph description for poseidon2 hash with random inputs + * The result is one connected component, and all output variables must be in one gate + */ +void test_poseidon2s_circuit(size_t num_inputs = 5) +{ + auto builder = Builder(); + std::vector<field_t> inputs; + + for (size_t i = 0; i < num_inputs; ++i) { + auto element = fr::random_element(&engine); + inputs.emplace_back(field_t(witness_t(&builder, element))); + } + + for (auto& elem: inputs) { + elem.fix_witness(); + } + [[maybe_unused]]auto result = stdlib::poseidon2<Builder>::hash(builder, inputs); + auto graph = Graph(builder); + auto connected_components = graph.find_connected_components(); + EXPECT_EQ(connected_components.size(), 1); + auto variables_in_one_gate = graph.show_variables_in_one_gate(builder); + std::unordered_set<uint32_t> outputs{result.witness_index, result.witness_index + 1, result.witness_index + 2, result.witness_index + 3}; + for (const auto& elem: variables_in_one_gate) { + EXPECT_EQ(outputs.contains(elem), true); + } +} + +/** + * @brief this test checks graph description for poseidon2 hash with byte array input + * The result is one connected component, and all output variables must be in one gate + */ +void test_poseidon2s_hash_byte_array(size_t num_inputs = 5) +{ + Builder builder; + + std::vector<uint8_t> input; + input.reserve(num_inputs); + for (size_t i = 0; i < num_inputs; ++i) { + input.push_back(engine.get_random_uint8()); + } + + byte_array_ct circuit_input(&builder, input); + auto result = stdlib::poseidon2<Builder>::hash_buffer(builder, circuit_input); + auto graph = Graph(builder); + auto connected_components = graph.find_connected_components(); + EXPECT_EQ(connected_components.size(), 1); + auto variables_in_one_gate = graph.show_variables_in_one_gate(builder); + std::unordered_set<uint32_t> outputs{result.witness_index, result.witness_index + 1, result.witness_index + 2, result.witness_index + 3}; + for (const auto& elem: variables_in_one_gate) { + EXPECT_EQ(outputs.contains(elem), true); + } +} + +/** + * @brief this test checks graph description for repeated poseidon2 hash operations + * The result is one connected component with repeated hashing of pairs, + * all output variables from each hash operation must be in one gate + */ +void test_poseidon2s_hash_repeated_pairs(size_t num_inputs = 5) +{ + Builder builder; + + fr left_in = fr::random_element(); + fr right_in = fr::random_element(); + + fr_ct left = witness_ct(&builder, left_in); + fr_ct right = witness_ct(&builder, right_in); + right.fix_witness(); + std::unordered_set<uint32_t> outputs{left.witness_index}; + // num_inputs - 1 iterations since the first hash hashes two elements + for (size_t i = 0; i < num_inputs - 1; ++i) { + left = stdlib::poseidon2<Builder>::hash(builder, { left, right }); + outputs.insert(left.witness_index + 1); + outputs.insert(left.witness_index + 2); + outputs.insert(left.witness_index + 3); + } + left.fix_witness(); + + auto graph = Graph(builder); + auto connected_components = graph.find_connected_components(); + EXPECT_EQ(connected_components.size(), 1); + auto variables_in_one_gate = graph.show_variables_in_one_gate(builder); + for (const auto& elem: variables_in_one_gate) { + EXPECT_EQ(outputs.contains(elem), true); + } +} + +/** + * @brief this test checks graph description for a single poseidon2 permutation + * The result is one connected component with no variables in one gate, + * as permutation connects all variables through its internal structure + */ +TEST(boomerang_poseidon2s, test_graph_for_poseidon2s_one_permutation) +{ + std::array<field_t, Params::t> inputs; + auto builder = Builder(); + + for (size_t i = 0; i < Params::t; ++i) { + const auto element = fr::random_element(&engine); + inputs[i] = field_t(witness_t(&builder, element)); + } + + auto poseidon2permutation = Permutation(); + [[maybe_unused]] auto new_state = poseidon2permutation.permutation(&builder, inputs); + for (auto& elem: new_state) { + elem.fix_witness(); + } + + auto graph = Graph(builder); + auto connected_components = graph.find_connected_components(); + EXPECT_EQ(connected_components.size(), 1); + auto variables_in_one_gate = graph.show_variables_in_one_gate(builder); + EXPECT_EQ(variables_in_one_gate.size(), 0); +} + +/** + * @brief this test checks graph description for two separate poseidon2 permutations + * The result is two connected components (one for each permutation) with no variables in one gate, + * verifying that different input sets create separate components + */ +TEST(boomerang_poseidon2s, test_graph_for_poseidon2s_two_permutations) +{ + // we want to check that 2 permutations for different inputs give different connected components + std::array<field_t, Params::t> input1; + std::array<field_t, Params::t> input2; + auto builder = Builder(); + + for (size_t i = 0; i < Params::t; ++i) { + const auto el1 = fr::random_element(&engine); + input1[i] = field_t(witness_t(&builder, el1)); + const auto el2 = fr::random_element(&engine); + input2[i] = field_t(witness_t(&builder, el2)); + } + + auto poseidon2permutation = Permutation(); + [[maybe_unused]] auto state1 = poseidon2permutation.permutation(&builder, input1); + [[maybe_unused]] auto state2 = poseidon2permutation.permutation(&builder, input2); + for (auto& elem: state1) { + elem.fix_witness(); + } + for (auto& elem: state2) { + elem.fix_witness(); + } + auto graph = Graph(builder); + auto connected_components = graph.find_connected_components(); + EXPECT_EQ(connected_components.size(), 2); + auto variables_in_one_gate = graph.show_variables_in_one_gate(builder); + EXPECT_EQ(variables_in_one_gate.size(), 0); +} + +TEST(boomerang_poseidon2s, test_graph_for_poseidon2s) +{ + for (size_t num_inputs = 6; num_inputs < 100; num_inputs++) { + test_poseidon2s_circuit(num_inputs); + } +} + +TEST(boomerang_poseidon2s, test_graph_for_poseidon2s_for_one_input_size) +{ + test_poseidon2s_circuit(); +} + +TEST(boomerang_poseidon2s, test_graph_for_poseidon2s_hash_byte_array) { + for (size_t num_inputs = 6; num_inputs < 100; num_inputs++) { + test_poseidon2s_hash_byte_array(num_inputs); + } +} + +TEST(boomerang_poseidon2s, test_graph_for_poseidon2s_hash_repeated_pairs) +{ + test_poseidon2s_hash_repeated_pairs(); +} \ No newline at end of file diff --git a/barretenberg/cpp/src/barretenberg/boomerang_value_detection/graph_description_ram_rom.test.cpp b/barretenberg/cpp/src/barretenberg/boomerang_value_detection/graph_description_ram_rom.test.cpp new file mode 100644 index 00000000000..9bb96666f81 --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/boomerang_value_detection/graph_description_ram_rom.test.cpp @@ -0,0 +1,166 @@ +#include "barretenberg/boomerang_value_detection/graph.hpp" +#include "barretenberg/common/test.hpp" +#include "barretenberg/stdlib/primitives/circuit_builders/circuit_builders.hpp" +#include "barretenberg/stdlib/primitives/memory/ram_table.hpp" +#include "barretenberg/stdlib/primitives/memory/rom_table.hpp" + +using namespace bb; +using namespace cdg; +namespace { +auto& engine = numeric::get_debug_randomness(); +} + +using Builder = UltraCircuitBuilder; +using field_ct = stdlib::field_t<Builder>; +using witness_ct = stdlib::witness_t<Builder>; +using rom_table_ct = stdlib::rom_table<Builder>; +using ram_table_ct = stdlib::ram_table<Builder>; + +/** + * @brief this test checks graph description for ROM table operations + * The result is one connected component from reading random values at sequential indices, + * with no variables in one gate due to connections through table accesses + */ +TEST(boomerang_rom_ram_table, graph_description_rom_table) +{ + Builder builder; + + std::vector<field_ct> table_values; + const size_t table_size = 10; + for (size_t i = 0; i < table_size; ++i) { + table_values.emplace_back(witness_ct(&builder, bb::fr::random_element())); + } + for (auto& elem: table_values) { + elem.fix_witness(); + } + + rom_table_ct table(table_values); + std::unordered_set<uint32_t> safety_variables; + + field_ct result = field_ct(witness_ct(&builder, (uint64_t)0)); + + for (size_t i = 0; i < 10; ++i) { + safety_variables.insert(result.witness_index); + field_ct index(witness_ct(&builder, (uint64_t)i)); + index.fix_witness(); + result += table[index]; + } + + result.fix_witness(); + Graph graph = Graph(builder); + auto connected_components = graph.find_connected_components(); + EXPECT_EQ(connected_components.size(), 1); + auto variables_in_one_gate = graph.show_variables_in_one_gate(builder); + for (const auto& elem: variables_in_one_gate) { + EXPECT_EQ(variables_in_one_gate.contains(elem), true); + } +} + +/** + * @brief this test checks graph description for RAM table read operations + * The result is one connected component from reading random values at sequential indices, + * with no variables in one gate due to connections through table reads + */ +TEST(boomerang_rom_ram_table, graph_description_ram_table_read) +{ + Builder builder; + + std::vector<field_ct> table_values; + const size_t table_size = 10; + for (size_t i = 0; i < table_size; ++i) { + table_values.emplace_back(witness_ct(&builder, bb::fr::random_element())); + } + + for (auto& elem: table_values) { + elem.fix_witness(); + } + + ram_table_ct table(table_values); + field_ct result = field_ct(witness_ct(&builder, (uint64_t)0)); + std::unordered_set<uint32_t> safety_variables; + + for (size_t i = 0; i < 10; ++i) { + safety_variables.insert(result.witness_index); + field_ct index(witness_ct(&builder, (uint64_t)i)); + index.fix_witness(); + result += table.read(index); + } + + result.fix_witness(); + Graph graph = Graph(builder); + auto connected_components = graph.find_connected_components(); + EXPECT_EQ(connected_components.size(), 1); + auto variables_in_one_gate = graph.show_variables_in_one_gate(builder); + for (const auto& elem: variables_in_one_gate) { + EXPECT_EQ(safety_variables.contains(elem), true); + } +} + +/** + * @brief this test checks graph description for RAM table write and read operations + * The result is one connected component from alternating write and read operations, + * with non-sequential access patterns and no variables in one gate. + * @details Test includes: + * - Initial zero initialization + * - Multiple update-read cycles + * - Non-sequential read access pattern + */ +TEST(boomerang_rom_ram_table, graph_description_ram_table_write) +{ + Builder builder; + const size_t table_size = 10; + + std::vector<fr> table_values(table_size); + ram_table_ct table(&builder, table_size); + + for (size_t i = 0; i < table_size; ++i) { + table.write(i, 0); + } + std::unordered_set<uint32_t> safety_variables; + field_ct result(0); + safety_variables.insert(result.witness_index); + + const auto update = [&]() { + for (size_t i = 0; i < table_size / 2; ++i) { + table_values[2 * i] = fr::random_element(); + table_values[2 * i + 1] = fr::random_element(); + + // init with both constant and variable values + field_ct value1(witness_ct(&builder, table_values[2 * i])); + field_ct value2(witness_ct(&builder, table_values[2 * i + 1])); + value1.fix_witness(); + value2.fix_witness(); + table.write(2 * i, value1); + table.write(2 * i + 1, value2); + } + }; + + const auto read = [&]() { + for (size_t i = 0; i < table_size / 2; ++i) { + const size_t index = table_size - 2 - (i * 2); // access in something other than basic incremental order + field_ct index1(witness_ct(&builder, index)); + field_ct index2(witness_ct(&builder, index + 1)); + index1.fix_witness(); + index2.fix_witness(); + result += table.read(index1); + safety_variables.insert(result.witness_index); + result += table.read(index2); + safety_variables.insert(result.witness_index); + } + }; + + update(); + read(); + update(); + read(); + update(); + + result.fix_witness(); + Graph graph = Graph(builder); + auto connected_components = graph.find_connected_components(); + EXPECT_EQ(connected_components.size(), 1); + auto variables_in_one_gate = graph.show_variables_in_one_gate(builder); + for (const auto& elem: variables_in_one_gate) { + EXPECT_EQ(safety_variables.contains(elem), true); + } +} \ No newline at end of file diff --git a/barretenberg/cpp/src/barretenberg/boomerang_value_detection/graph_description_sha256.test.cpp b/barretenberg/cpp/src/barretenberg/boomerang_value_detection/graph_description_sha256.test.cpp index 0370398d81d..4f641447c71 100644 --- a/barretenberg/cpp/src/barretenberg/boomerang_value_detection/graph_description_sha256.test.cpp +++ b/barretenberg/cpp/src/barretenberg/boomerang_value_detection/graph_description_sha256.test.cpp @@ -14,12 +14,24 @@ using namespace bb; using namespace bb::stdlib; +using namespace cdg; using Builder = UltraCircuitBuilder; +using byte_array_pt = byte_array<Builder>; +using packed_byte_array_pt = packed_byte_array<Builder>; +using field_pt = field_t<Builder>; + +void fix_vector(std::vector<field_pt>& vector) { + for (auto& elem: vector) { + elem.fix_witness(); + } +} + +void fix_byte_array(packed_byte_array_pt& input) { + std::vector<field_pt> limbs = input.get_limbs(); + fix_vector(limbs); +} -using byte_array_ct = byte_array<Builder>; -using packed_byte_array_ct = packed_byte_array<Builder>; -using field_ct = field_t<Builder>; /** all these tests check graph description for sha256 circuits. All circuits have to consist from 1 connected component @@ -30,23 +42,29 @@ TEST(boomerang_stdlib_sha256, test_graph_for_sha256_55_bytes) // 55 bytes is the largest number of bytes that can be hashed in a single block, // accounting for the single padding bit, and the 64 size bits required by the SHA-256 standard. auto builder = Builder(); - packed_byte_array_ct input(&builder, "An 8 character password? Snow White and the 7 Dwarves.."); + packed_byte_array_pt input(&builder, "An 8 character password? Snow White and the 7 Dwarves.."); + fix_byte_array(input); - packed_byte_array_ct output_bits = stdlib::sha256(input); + packed_byte_array_pt output_bits = stdlib::sha256(input); - std::vector<field_ct> output = output_bits.to_unverified_byte_slices(4); + std::vector<field_pt> output = output_bits.to_unverified_byte_slices(4); + fix_vector(output); Graph graph = Graph(builder); auto connected_components = graph.find_connected_components(); EXPECT_EQ(connected_components.size(), 1); + auto variables_in_one_gate = graph.show_variables_in_one_gate(builder); + EXPECT_EQ(variables_in_one_gate.size(), 0); + if (variables_in_one_gate.size() > 0) { + for (const auto& elem: variables_in_one_gate) { + info("elem == ", elem); + } + } } HEAVY_TEST(boomerang_stdlib_sha256, test_graph_for_sha256_NIST_vector_five) { - typedef stdlib::field_t<UltraCircuitBuilder> field_pt; - typedef stdlib::packed_byte_array<UltraCircuitBuilder> packed_byte_array_pt; - - auto builder = UltraCircuitBuilder(); + auto builder = Builder(); packed_byte_array_pt input( &builder, @@ -61,11 +79,77 @@ HEAVY_TEST(boomerang_stdlib_sha256, test_graph_for_sha256_NIST_vector_five) "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" "AAAAAAAAAA"); + fix_byte_array(input); packed_byte_array_pt output_bits = stdlib::sha256<bb::UltraCircuitBuilder>(input); - + std::vector<field_pt> output = output_bits.to_unverified_byte_slices(4); + fix_vector(output); + + info("start creating the Graph"); + Graph graph = Graph(builder); + info("graph creating is ended"); + auto connected_components = graph.find_connected_components(); + auto variables_in_one_gate = graph.show_variables_in_one_gate(builder); + EXPECT_EQ(variables_in_one_gate.size(), 0); + EXPECT_EQ(connected_components.size(), 1); +} + +TEST(boomerang_stdlib_sha256, test_graph_for_sha256_NIST_vector_one) +{ + auto builder = Builder(); + packed_byte_array_pt input(&builder, "abc"); + fix_byte_array(input); + packed_byte_array_pt output_bits = stdlib::sha256(input); + fix_byte_array(output_bits); + Graph graph = Graph(builder); + auto connected_components = graph.find_connected_components(); + EXPECT_EQ(connected_components.size(), 1); + std::unordered_set<uint32_t> variables_in_one_gate = graph.show_variables_in_one_gate(builder); + EXPECT_EQ(variables_in_one_gate.size(), 0); +} + +TEST(boomerang_stdlib_sha256, test_graph_for_sha256_NIST_vector_two) +{ + auto builder = Builder(); + packed_byte_array_pt input(&builder, "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"); + fix_byte_array(input); + packed_byte_array_pt output_bits = stdlib::sha256(input); + fix_byte_array(output_bits); + Graph graph = Graph(builder); + auto connected_components = graph.find_connected_components(); + EXPECT_EQ(connected_components.size(), 1); + std::unordered_set<uint32_t> variables_in_one_gate = graph.show_variables_in_one_gate(builder); + EXPECT_EQ(variables_in_one_gate.size(), 0); +} + +TEST(boomerang_stdlib_sha256, test_graph_for_sha256_NIST_vector_three) +{ + auto builder = Builder(); + + // one byte, 0xbd + packed_byte_array_pt input(&builder, std::vector<uint8_t>{ 0xbd }); + fix_byte_array(input); + packed_byte_array_pt output_bits = stdlib::sha256(input); + fix_byte_array(output_bits); + Graph graph = Graph(builder); + auto connected_components = graph.find_connected_components(); + EXPECT_EQ(connected_components.size(), 1); + std::unordered_set<uint32_t> variables_in_one_gate = graph.show_variables_in_one_gate(builder); + EXPECT_EQ(variables_in_one_gate.size(), 0); +} + +TEST(boomerang_stdlib_sha256, test_graph_for_sha256_NIST_vector_four) +{ + auto builder = Builder(); + // 4 bytes, 0xc98c8e55 + packed_byte_array_pt input(&builder, std::vector<uint8_t>{ 0xc9, 0x8c, 0x8e, 0x55 }); + fix_byte_array(input); + packed_byte_array_pt output_bits = stdlib::sha256<Builder>(input); + fix_byte_array(output_bits); Graph graph = Graph(builder); auto connected_components = graph.find_connected_components(); EXPECT_EQ(connected_components.size(), 1); + std::unordered_set<uint32_t> variables_in_one_gate = graph.show_variables_in_one_gate(builder); + EXPECT_EQ(variables_in_one_gate.size(), 0); } \ No newline at end of file diff --git a/barretenberg/cpp/src/barretenberg/boomerang_value_detection/variable_gates_count.test.cpp b/barretenberg/cpp/src/barretenberg/boomerang_value_detection/variable_gates_count.test.cpp index d07ebeeac4f..df54296651a 100644 --- a/barretenberg/cpp/src/barretenberg/boomerang_value_detection/variable_gates_count.test.cpp +++ b/barretenberg/cpp/src/barretenberg/boomerang_value_detection/variable_gates_count.test.cpp @@ -10,8 +10,9 @@ #include <gtest/gtest.h> using namespace bb; +using namespace cdg; -TEST(boomerang_ultra_circuit_constructor, test_variable_gates_count_for_decompose) +TEST(ultra_circuit_constructor, test_variable_gates_count_for_decompose) { UltraCircuitBuilder circuit_constructor = UltraCircuitBuilder(); auto c = fr::random_element(); @@ -24,10 +25,10 @@ TEST(boomerang_ultra_circuit_constructor, test_variable_gates_count_for_decompos Graph graph = Graph(circuit_constructor); std::unordered_set<uint32_t> variables_in_on_gate = graph.show_variables_in_one_gate(circuit_constructor); - EXPECT_EQ(variables_in_on_gate.size(), 1); + EXPECT_EQ(variables_in_on_gate.size(), 0); } -TEST(boomerang_ultra_circuit_constructor, test_variable_gates_count_for_decompose2) +TEST(ultra_circuit_constructor, test_variable_gates_count_for_decompose2) { UltraCircuitBuilder circuit_constructor = UltraCircuitBuilder(); auto c = fr::random_element(); @@ -40,10 +41,10 @@ TEST(boomerang_ultra_circuit_constructor, test_variable_gates_count_for_decompos Graph graph = Graph(circuit_constructor); auto variables_in_on_gate = graph.show_variables_in_one_gate(circuit_constructor); - EXPECT_EQ(variables_in_on_gate.size(), 1); + EXPECT_EQ(variables_in_on_gate.size(), 0); } -TEST(boomerang_utils, test_selectors_for_decompose) +TEST(utils, test_selectors_for_decompose) { auto is_power_two = [&](const uint256_t& number) { return number > 0 && ((number & (number - 1)) == 0); }; const uint64_t target_range_bitnum = 14; @@ -65,7 +66,7 @@ TEST(boomerang_utils, test_selectors_for_decompose) EXPECT_EQ(q_3_is_power_two, true); } -TEST(boomerang_ultra_circuit_constructor, test_variable_gates_count_for_two_decomposes) +TEST(ultra_circuit_constructor, test_variable_gates_count_for_two_decomposes) { UltraCircuitBuilder circuit_constructor = UltraCircuitBuilder(); auto c1 = fr::random_element(); @@ -85,10 +86,10 @@ TEST(boomerang_ultra_circuit_constructor, test_variable_gates_count_for_two_deco Graph graph = Graph(circuit_constructor); std::unordered_set<uint32_t> variables_in_one_gate = graph.show_variables_in_one_gate(circuit_constructor); - EXPECT_EQ(variables_in_one_gate.size(), 2); + EXPECT_EQ(variables_in_one_gate.size(), 0); } -TEST(boomerang_ultra_circuit_constructor, test_decompose_with_boolean_gates) +TEST(ultra_circuit_constructor, test_decompose_with_boolean_gates) { UltraCircuitBuilder circuit_constructor = UltraCircuitBuilder(); auto c1 = fr::random_element(); @@ -126,5 +127,5 @@ TEST(boomerang_ultra_circuit_constructor, test_decompose_for_6_bit_number) Graph graph = Graph(circuit_constructor); std::unordered_set<uint32_t> variables_in_on_gate = graph.show_variables_in_one_gate(circuit_constructor); - EXPECT_EQ(variables_in_on_gate.size(), 1); + EXPECT_EQ(variables_in_on_gate.size(), 0); } diff --git a/barretenberg/cpp/src/barretenberg/circuit_checker/standard_circuit_builder.test.cpp b/barretenberg/cpp/src/barretenberg/circuit_checker/standard_circuit_builder.test.cpp index 65d96e1c998..56f12a17348 100644 --- a/barretenberg/cpp/src/barretenberg/circuit_checker/standard_circuit_builder.test.cpp +++ b/barretenberg/cpp/src/barretenberg/circuit_checker/standard_circuit_builder.test.cpp @@ -465,4 +465,4 @@ TEST(standard_circuit_constructor, test_check_circuit_broken) bool result = CircuitChecker::check(circuit_constructor); EXPECT_EQ(result, false); -} +} \ No newline at end of file diff --git a/barretenberg/cpp/src/barretenberg/stdlib/hash/sha256/sha256_plookup.cpp b/barretenberg/cpp/src/barretenberg/stdlib/hash/sha256/sha256_plookup.cpp index c64afbeb10d..7282f603d8c 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib/hash/sha256/sha256_plookup.cpp +++ b/barretenberg/cpp/src/barretenberg/stdlib/hash/sha256/sha256_plookup.cpp @@ -321,7 +321,6 @@ std::array<field_t<Builder>, 8> sha256_block(const std::array<field_t<Builder>, output[5] = add_normalize(f.normal, h_init[5]); output[6] = add_normalize(g.normal, h_init[6]); output[7] = add_normalize(h.normal, h_init[7]); - /** * At this point, a malicilous prover could tweak the add_normalise function and the result could be 'overflowed'. * Thus, we need 32-bit range checks on the outputs. Note that we won't need range checks while applying the SHA-256 diff --git a/barretenberg/cpp/src/barretenberg/stdlib_circuit_builders/standard_circuit_builder.cpp b/barretenberg/cpp/src/barretenberg/stdlib_circuit_builders/standard_circuit_builder.cpp index 03effb88841..b7600b7fd6f 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib_circuit_builders/standard_circuit_builder.cpp +++ b/barretenberg/cpp/src/barretenberg/stdlib_circuit_builders/standard_circuit_builder.cpp @@ -1,11 +1,11 @@ #include "standard_circuit_builder.hpp" #include "barretenberg/ecc/curves/bn254/bn254.hpp" #include "barretenberg/ecc/curves/grumpkin/grumpkin.hpp" -#include <unordered_map> -#include <unordered_set> - #include "barretenberg/serialize/cbind.hpp" #include "barretenberg/serialize/msgpack.hpp" +#include <set> +#include <unordered_map> +#include <unordered_set> namespace bb {