From 21f995c0fe21ad25b46ab62f4d3b6599a140e6f3 Mon Sep 17 00:00:00 2001 From: Iluvmagick Date: Tue, 5 Mar 2024 11:48:28 +0400 Subject: [PATCH 1/2] Created batching mechanism for components and constants. Added constraint evaluator component. --- .github/workflows/run_tests.yml | 2 + .../benchmarks/circuit_generator.hpp | 261 +++++++++++ .../blueprint/blueprint/plonk/assignment.hpp | 260 ++++++++++- .../nil/blueprint/blueprint/plonk/circuit.hpp | 6 +- .../blueprint/plonk/circuit_proxy.hpp | 4 + include/nil/blueprint/component_batch.hpp | 331 ++++++++++++++ include/nil/blueprint/component_stretcher.hpp | 58 +-- .../expression_evaluation_component.hpp | 283 ++++++++++++ .../blueprint/utils/connectedness_check.hpp | 11 +- include/nil/blueprint/utils/gate_mover.hpp | 89 ++++ test/CMakeLists.txt | 2 + test/component_batch.cpp | 404 ++++++++++++++++++ test/test_plonk_component.hpp | 71 +-- .../expression_evaluation_component.cpp | 156 +++++++ 14 files changed, 1848 insertions(+), 90 deletions(-) create mode 100644 include/nil/blueprint/benchmarks/circuit_generator.hpp create mode 100644 include/nil/blueprint/component_batch.hpp create mode 100644 include/nil/blueprint/components/systems/snark/plonk/placeholder/detail/expression_evaluation_component.hpp create mode 100644 include/nil/blueprint/utils/gate_mover.hpp create mode 100644 test/component_batch.cpp create mode 100644 test/verifiers/placeholder/expression_evaluation_component.cpp diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml index a81b58081..9edf0e1bb 100644 --- a/.github/workflows/run_tests.yml +++ b/.github/workflows/run_tests.yml @@ -80,6 +80,8 @@ jobs: blueprint_verifiers_placeholder_gate_component_test, blueprint_proxy_test blueprint_mock_mocked_components_test + blueprint_component_batch_test + blueprint_verifiers_placeholder_expression_evaluation_component_test ] # Tests to execute include: # Abused to enable proof generation for some tests; add more as needed - target: blueprint_algebra_fields_plonk_non_native_logic_ops_test diff --git a/include/nil/blueprint/benchmarks/circuit_generator.hpp b/include/nil/blueprint/benchmarks/circuit_generator.hpp new file mode 100644 index 000000000..5e7da681e --- /dev/null +++ b/include/nil/blueprint/benchmarks/circuit_generator.hpp @@ -0,0 +1,261 @@ +//---------------------------------------------------------------------------// +// Copyright (c) 2024 Dmitrii Tabalin = +// +// MIT License +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//---------------------------------------------------------------------------// + +#pragma once + +#include + +#include + +#include +#include +#include +#include +#include + +#include + +#include +#include + +namespace nil { + namespace blueprint { + + template + crypto3::zk::snark::plonk_variable generate_random_global_var( + const assignment> &assignments, + boost::random::mt19937 &random_engine) { + + using var = crypto3::zk::snark::plonk_variable; + const std::size_t witness_amount = assignments.witnesses_amount(); + const std::size_t public_input_amount = assignments.public_inputs_amount(); + const std::size_t constant_amount = assignments.constants_amount(); + const std::size_t total_col_amount = witness_amount + public_input_amount + constant_amount; + const std::size_t rows_amount = assignments.rows_amount(); + const std::size_t random_row = + boost::random::uniform_int_distribution(0, rows_amount - 1)(random_engine); + std::size_t random_col = + boost::random::uniform_int_distribution(0, total_col_amount - 1)(random_engine); + typename var::column_type column_type; + if (random_col < witness_amount) { + column_type = var::column_type::witness; + } else if (random_col < witness_amount + public_input_amount) { + column_type = var::column_type::public_input; + random_col -= witness_amount; + } else { + column_type = var::column_type::constant; + random_col -= witness_amount + public_input_amount; + } + return var(random_col, random_row, true, column_type); + } + + template + crypto3::zk::snark::plonk_variable generate_random_local_var( + const assignment> &assignments, + boost::random::mt19937 &random_engine) { + + using var = crypto3::zk::snark::plonk_variable; + const std::size_t witness_amount = assignments.witnesses_amount(); + const std::size_t constant_amount = assignments.constants_amount(); + const std::size_t total_col_amount = witness_amount + constant_amount; + const std::int32_t random_offset = + boost::random::uniform_int_distribution(-1, 1)(random_engine); + std::size_t random_col = + boost::random::uniform_int_distribution(0, total_col_amount - 1)(random_engine); + typename var::column_type column_type; + if (random_col < witness_amount) { + column_type = var::column_type::witness; + } else { + column_type = var::column_type::constant; + random_col -= witness_amount; + } + return var(random_col, random_offset, true, column_type); + } + + template + void generate_random_copy_constraints( + const assignment> &assignments, + circuit> &bp, + const std::size_t num_constraints, + boost::random::mt19937 &random_engine) { + + using var = crypto3::zk::snark::plonk_variable; + for (std::size_t i = 0; i < num_constraints; ++i) { + const var a = generate_random_global_var(assignments, random_engine); + var b = generate_random_global_var(assignments, random_engine); + // note that we technically might not generate a unique copy constraint here and it + // might be already present + // for the sake of simplicity we don't check for that, as the probability of that is really small + // for the assignment tables of a reasonable size compared to the number of constraints + while (a == b) { [[unlikely]] + b = generate_random_global_var(assignments, random_engine); + } + bp.add_copy_constraint({a, b}); + } + // Sanity check + BOOST_ASSERT(bp.copy_constraints().size() == num_constraints); + } + + template + void fill_assignment_table( + assignment> &assignments, + const std::size_t rows_amount, + boost::random::mt19937 &random_engine) { + + using value_type = typename BlueprintFieldType::value_type; + crypto3::random::algebraic_engine engine(random_engine); + std::array, 3> access_functions = { + [&assignments](std::size_t col, std::size_t row) -> value_type& { + return assignments.witness(col, row); + }, + [&assignments](std::size_t col, std::size_t row) -> value_type& { + return assignments.public_input(col, row); + }, + [&assignments](std::size_t col, std::size_t row) -> value_type& { + return assignments.constant(col, row); + } + }; + std::array sizes = { + assignments.witnesses_amount(), assignments.public_inputs_amount(), assignments.constants_amount()}; + for (const auto &column_access_pair : + {std::pair(sizes[0], access_functions[0]), + std::pair(sizes[1], access_functions[1]), + std::pair(sizes[2], access_functions[2])}) { + const std::size_t column_amount = column_access_pair.first; + const auto &column_access_function = column_access_pair.second; + for (std::size_t col = 0; col < column_amount; ++col) { + for (std::size_t row = 0; row < rows_amount; ++row) { + column_access_function(col, row) = engine(); + } + } + } + } + + template + void fill_selectors( + assignment> &assignments, + const circuit> &bp, + boost::random::mt19937 &random_engine) { + + // We use a separate algorithm for filling selectors, as they are 0/1 + // In practicde the distribution is not uniform, but we ignore that for the purposes of this benchmark + // TODO: do something more clever + for (std::size_t i = 0; i < assignments.selectors_amount(); ++i) { + for (std::size_t row = 0; row < assignments.rows_amount(); ++row) { + assignments.selector(i, row) = random_engine() % 2; + } + } + } + + template + nil::crypto3::zk::snark::plonk_constraint generate_random_constraint( + const assignment> &assignments, + const std::size_t max_degree, + const std::size_t max_linear_comb_size, + boost::random::mt19937 &random_engine) { + // Strategy: generate two random polynomials of max_degree / 2, and then multiply them + // If max_degree % 2 != 0, we multiply the result by a random linear combination + // Which is incidentally the ouput of this function with max_degree = 1 + // This generates very "wide" gates on average. + // I need a different algorithm probably? Unsure. + if (max_degree > 1) { + auto a = generate_random_constraint( + assignments, max_degree / 2, max_linear_comb_size, random_engine); + auto b = generate_random_constraint( + assignments, max_degree / 2, max_linear_comb_size, random_engine); + if (max_degree % 2 != 0) { + auto c = generate_random_constraint( + assignments, 1, max_linear_comb_size, random_engine); + return a * b * c; + } else { + return a * b; + } + } else if (max_degree == 1) { + crypto3::random::algebraic_engine engine(random_engine); + nil::crypto3::zk::snark::plonk_constraint linear_comb; + const std::size_t linear_comb_size = + boost::random::uniform_int_distribution(1, max_linear_comb_size)(random_engine); + for (std::size_t i = 0; i < linear_comb_size; i++) { + linear_comb += engine() * generate_random_local_var(assignments, random_engine); + } + linear_comb += engine(); + return linear_comb; + } else { + BOOST_ASSERT_MSG(false, "max_degree must be > 0"); + } + __builtin_unreachable(); + } + + template + void generate_random_gate( + const assignment> &assignments, + circuit> &bp, + const std::size_t max_degree, + const std::size_t max_linear_comb_size, + const std::size_t constraints_amount, + boost::random::mt19937 &random_engine) { + + std::vector> constraints; + constraints.reserve(constraints_amount); + // first, ensure that we have at least one of the constraints with the given max_degree + constraints.emplace_back(generate_random_constraint( + assignments, max_degree, max_linear_comb_size, random_engine)); + // next, generate the rest of them + for (std::size_t i = 1; i < constraints_amount; ++i) { + const std::size_t degree = max_degree > 1 ? + boost::random::uniform_int_distribution(1, max_degree)(random_engine) + : 1; + constraints.emplace_back(generate_random_constraint( + assignments, degree, max_linear_comb_size, random_engine)); + } + bp.add_gate(constraints); + } + + + template + void generate_random_gates( + const assignment> &assignments, + circuit> &bp, + const std::size_t gates_amount, + const std::size_t max_degree, + const std::size_t max_linear_comb_size, + const std::size_t constraints_amount, + boost::random::mt19937 &random_engine) { + + BOOST_ASSERT_MSG(max_degree > 0, "max_degree must be > 0"); + BOOST_ASSERT_MSG(max_linear_comb_size > 0, "max_linear_comb_size must be > 0"); + BOOST_ASSERT_MSG(constraints_amount > 0, "constraints_amount must be > 0"); + + // Generate a gate with a given max_degree + generate_random_gate(assignments, bp, max_degree, max_linear_comb_size, constraints_amount, random_engine); + // Generate the rest of the gates with random max degrees + for (std::size_t i = 1; i < gates_amount; ++i) { + const std::size_t degree = max_degree > 1 ? + boost::random::uniform_int_distribution(1, max_degree)(random_engine) + : 1; + generate_random_gate(assignments, bp, degree, max_linear_comb_size, constraints_amount, random_engine); + } + } + } // namespace blueprint +} // namespace nil diff --git a/include/nil/blueprint/blueprint/plonk/assignment.hpp b/include/nil/blueprint/blueprint/plonk/assignment.hpp index f16ddaf2c..756295208 100644 --- a/include/nil/blueprint/blueprint/plonk/assignment.hpp +++ b/include/nil/blueprint/blueprint/plonk/assignment.hpp @@ -27,9 +27,18 @@ #define CRYPTO3_BLUEPRINT_ASSIGNMENT_PLONK_HPP #include +#include #include +#include #include +#include +#include +#include +#include +#include + +#include #include #include #include @@ -38,9 +47,22 @@ #include #include #include +#include namespace nil { namespace blueprint { + namespace detail { + template + struct constant_batch_ref_compare { + using value_type = typename BlueprintFieldType::value_type; + using ref_type = std::reference_wrapper; + using pair_type = std::pair; + + bool operator()(const pair_type &p1, const pair_type &p2) const { + return p1.first.get() < p2.first.get(); + } + }; + } // namespace detail template class assignment; @@ -48,6 +70,26 @@ namespace nil { template class circuit; + template + class component_batch; + + template + struct has_add_input; + + template + struct has_finalize_batch; + + template + struct input_type_v; + + template + struct result_type_v; + + struct _batch : boost::type_erasure::placeholder {}; + struct _component : boost::type_erasure::placeholder {}; + struct _input_type : boost::type_erasure::placeholder {}; + struct _result_type : boost::type_erasure::placeholder {}; + template class assignment> : public crypto3::zk::snark::plonk_assignment_table { @@ -61,14 +103,41 @@ namespace nil { using column_type = typename crypto3::zk::snark::plonk_column; using shared_container_type = typename std::array; + using constant_set_compare_type = detail::constant_batch_ref_compare; + std::size_t next_selector_index = 0; std::uint32_t assignment_allocated_rows = 0; std::vector assignment_private_storage; + // for variables used in component batching + std::vector assignment_batch_private_storage; + using batcher_type = boost::type_erasure::any< + boost::mpl::vector< + has_add_input<_batch, _input_type, _result_type>, + has_finalize_batch<_batch, ArithmetizationType, var>, + boost::type_erasure::same_type>, _input_type>, + boost::type_erasure::same_type>, _result_type>, + boost::type_erasure::less_than_comparable<_batch>, + boost::type_erasure::copy_constructible<_batch>, + boost::type_erasure::constructible<_batch(assignment&)>, + boost::type_erasure::destructible<_batch>, + boost::type_erasure::typeid_<_batch>, + boost::type_erasure::relaxed>, + _batch>; + std::set component_batches; + // technically we can delete this one after finalization + // but tests require it to replace the outputs + std::unordered_map batch_variable_map; + // for constants which we are going to try to put into aribtrary places + boost::container::stable_vector assignment_batch_constant_storage; + std::set, std::size_t>, constant_set_compare_type> + assignment_batch_constant_storage_set; shared_container_type shared_storage; // results of the previously prover std::set lookup_constant_cols; std::set lookup_selector_cols; public: static constexpr const std::size_t private_storage_index = std::numeric_limits::max(); + static constexpr const std::size_t batch_private_storage_index = std::numeric_limits::max() - 1; + static constexpr const std::size_t batch_constant_storage_index = std::numeric_limits::max() - 2; assignment(std::size_t witness_amount, std::size_t public_input_amount, std::size_t constant_amount, std::size_t selector_amount) @@ -80,6 +149,142 @@ namespace nil { desc.constant_columns, desc.selector_columns) { } + template + typename ComponentType::result_type add_input_to_batch(const typename ComponentType::input_type &input) { + using batching_type = component_batch; + batching_type batch(*this); + auto it = component_batches.find(batch); + if (it == component_batches.end()) { + auto result = batch.add_input(input); + component_batches.insert(batch); + return result; + } else { + // safe because the ordering doesn't depend on the batch inputs + return boost::type_erasure::any_cast(const_cast(*it)).add_input(input); + } + } + + std::size_t finalize_component_batches(nil::blueprint::circuit &bp, + std::size_t start_row_index) { + std::size_t next_row_index = start_row_index; + for (auto& batch : component_batches) { + // safe because the ordering doesn't depend on the batch inputs + next_row_index = const_cast(batch).finalize_batch( + bp, batch_variable_map, next_row_index); + } + auto ©_constraints = bp.mutable_copy_constraints(); + for (auto &constraint : copy_constraints) { + for (auto variable : {&(constraint.first), &(constraint.second)}) { + if (batch_variable_map.find(*variable) != batch_variable_map.end()) { + *variable = batch_variable_map[*variable]; + } + } + } + return next_row_index; + } + + const std::unordered_map& get_batch_variable_map() const { + return batch_variable_map; + } + + // currently only supports a single constant column to batch things into + // ideally we should not require more than one + std::size_t finalize_constant_batches( + nil::blueprint::circuit &bp, + std::size_t const_column, + std::size_t start_row_index = 1) { + if (assignment_batch_constant_storage.size() == 0) { + return start_row_index; + } + BOOST_ASSERT(start_row_index >= 1); + std::vector used_constants; + if (this->constant_column_size(const_column) > start_row_index) { + used_constants.resize(this->constant_column_size(const_column) - start_row_index, false); + const auto &immutable_copy_constraints = bp.copy_constraints(); + auto var_check = [const_column, start_row_index](const var &variable) -> bool { + return variable.type == var::column_type::constant && + variable.index == const_column && + variable.rotation >= start_row_index; + }; + for (const auto &constraint : immutable_copy_constraints) { + for (const auto variable : {&(constraint.first), &(constraint.second)}) { + if (var_check(*variable)) { + used_constants[variable->rotation - start_row_index] = true; + } + } + } + const auto &gates = bp.gates(); + for (const auto &gate : gates) { + std::unordered_set variable_set; + std::function variable_extractor = + [&variable_set, &var_check](const var &variable) { + if (var_check(variable)) { + variable_set.insert(variable); + } + }; + nil::crypto3::math::expression_for_each_variable_visitor visitor(variable_extractor); + for (const auto &constraint : gate.constraints) { + visitor.visit(constraint); + } + for (const auto &variable : variable_set) { + for (std::size_t row = start_row_index - 1; + row < this->selector_column_size(gate.selector_index); row++) { + if (this->selector(gate.selector_index, row) == value_type::one()) { + used_constants[row + variable.rotation - start_row_index] = true; + } + } + } + } + const auto &lookup_gates = bp.lookup_gates(); + for (const auto &gate : lookup_gates) { + std::unordered_set variable_set; + std::function variable_extractor = + [&variable_set, &var_check](const var &variable) { + if (var_check(variable)) { + variable_set.insert(variable); + } + }; + nil::crypto3::math::expression_for_each_variable_visitor visitor(variable_extractor); + for (const auto &lookup_constraint : gate.constraints) { + for (const auto &constraint : lookup_constraint.lookup_input) { + visitor.visit(constraint); + } + } + for (const auto &variable : variable_set) { + for (std::size_t row = start_row_index - 1; + row < this->selector_column_size(gate.tag_index); row++) { + if (this->selector(gate.tag_index, row) == BlueprintFieldType::value_type::one()) { + used_constants[row + variable.rotation - start_row_index] = true; + } + } + } + } + } + std::size_t row = start_row_index; + std::unordered_map batch_variable_map; + for (std::size_t constant_index = 0; constant_index < assignment_batch_constant_storage.size(); + constant_index++) { + while (row < (used_constants.size() + start_row_index) && used_constants[row - start_row_index]) { + row++; + } + const var curr_batch_var = + var(batch_constant_storage_index, constant_index, false, var::column_type::constant); + const var curr_bp_var = var(const_column, row, false, var::column_type::constant); + this->constant(const_column, row) = assignment_batch_constant_storage[constant_index]; + batch_variable_map[curr_batch_var] = curr_bp_var; + row++; + } + auto ©_constraints = bp.mutable_copy_constraints(); + for (auto &constraint : copy_constraints) { + for (auto variable : {&(constraint.first), &(constraint.second)}) { + if (batch_variable_map.find(*variable) != batch_variable_map.end()) { + *variable = batch_variable_map[*variable]; + } + } + } + return row; + } + virtual value_type &selector(std::size_t selector_index, std::uint32_t row_index) { assert(selector_index < this->_public_table._selectors.size()); @@ -183,8 +388,9 @@ namespace nil { virtual value_type witness(std::uint32_t witness_index, std::uint32_t row_index) const { BLUEPRINT_ASSERT(witness_index < this->_private_table._witnesses.size()); - BLUEPRINT_ASSERT(row_index < this->_private_table._witnesses[witness_index].size()); - + if (row_index >= this->_private_table._witnesses[witness_index].size()) { + BLUEPRINT_ASSERT(row_index < this->_private_table._witnesses[witness_index].size()); + } return this->_private_table._witnesses[witness_index][row_index]; } @@ -279,7 +485,7 @@ namespace nil { } virtual value_type private_storage(std::uint32_t storage_index) const { - BLUEPRINT_ASSERT(storage_index < private_storage.size()); + BLUEPRINT_ASSERT(storage_index < assignment_private_storage.size()); return assignment_private_storage[storage_index]; } @@ -303,6 +509,48 @@ namespace nil { return assignment_private_storage.size(); } + virtual std::size_t batch_private_storage_size() const { + return assignment_batch_private_storage.size(); + } + + virtual value_type batch_private_storage(std::uint32_t storage_index) const { + BLUEPRINT_ASSERT(storage_index < assignment_batch_private_storage.size()); + return assignment_batch_private_storage[storage_index]; + } + + virtual value_type &batch_private_storage(std::uint32_t storage_index) { + if (assignment_batch_private_storage.size() <= storage_index) { + assignment_batch_private_storage.resize(storage_index + 1); + } + return assignment_batch_private_storage[storage_index]; + } + + virtual var add_batch_variable(const value_type &value) { + assignment_batch_private_storage.push_back(value); + return var(batch_private_storage_index, assignment_batch_private_storage.size() - 1, false, + var::column_type::public_input); + } + + virtual value_type batch_constant_storage(std::uint32_t storage_index) const { + BLUEPRINT_ASSERT(storage_index < assignment_batch_constant_storage.size()); + return assignment_batch_constant_storage[storage_index]; + } + + virtual var add_batch_constant_variable(const value_type &value) { + auto existing_const = assignment_batch_constant_storage_set.find( + std::make_pair, std::size_t>(std::cref(value), 0)); + if (existing_const == assignment_batch_constant_storage_set.end()) { + assignment_batch_constant_storage.push_back(value); + assignment_batch_constant_storage_set.insert(std::make_pair(std::cref(assignment_batch_constant_storage.back()), + assignment_batch_constant_storage.size() - 1)); + return var(batch_constant_storage_index, assignment_batch_constant_storage.size() - 1, false, + var::column_type::constant); + } else { + return var(batch_constant_storage_index, existing_const->second, false, + var::column_type::constant); + } + } + virtual void export_table(std::ostream& os, bool wide_export = false) const { // wide_export is for e.g. potentiall fuzzer: does fixed width elements std::ios_base::fmtflags os_flags(os.flags()); @@ -388,6 +636,12 @@ namespace nil { if (input_var.index == assignment_type::private_storage_index) { return input_assignment.private_storage(input_var.rotation); } + if (input_var.index == assignment_type::batch_private_storage_index) { + return input_assignment.batch_private_storage(input_var.rotation); + } + if (input_var.index == assignment_type::batch_constant_storage_index) { + return input_assignment.batch_constant_storage(input_var.rotation); + } if (input_var.type == var_column_type::public_input && input_var.index > 0) { return input_assignment.shared(input_var.index - 1, input_var.rotation); } diff --git a/include/nil/blueprint/blueprint/plonk/circuit.hpp b/include/nil/blueprint/blueprint/plonk/circuit.hpp index b8847eedf..7e09805bc 100644 --- a/include/nil/blueprint/blueprint/plonk/circuit.hpp +++ b/include/nil/blueprint/blueprint/plonk/circuit.hpp @@ -90,6 +90,10 @@ namespace nil { return ArithmetizationType::copy_constraints(); } + virtual typename ArithmetizationType::copy_constraints_container_type& mutable_copy_constraints() { + return ArithmetizationType::mutable_copy_constraints(); + } + virtual const typename ArithmetizationType::lookup_gates_container_type& lookup_gates() const { return ArithmetizationType::lookup_gates(); } @@ -111,7 +115,7 @@ namespace nil { if (it != mapping.end()) { \ return it->second; \ } else { \ - std::size_t selector_index = next_selector_index; \ + const std::size_t selector_index = next_selector_index; \ mapping[gate_id] = selector_index; \ this->gate_container.emplace_back(selector_index, args); \ next_selector_index++; \ diff --git a/include/nil/blueprint/blueprint/plonk/circuit_proxy.hpp b/include/nil/blueprint/blueprint/plonk/circuit_proxy.hpp index f77e67eee..5373578b1 100644 --- a/include/nil/blueprint/blueprint/plonk/circuit_proxy.hpp +++ b/include/nil/blueprint/blueprint/plonk/circuit_proxy.hpp @@ -105,6 +105,10 @@ namespace nil { return circuit_ptr->copy_constraints(); } + virtual typename ArithmetizationType::copy_constraints_container_type& mutable_copy_constraints() override { + return circuit_ptr->mutable_copy_constraints(); + } + const std::set& get_used_lookup_gates() const { return used_lookup_gates; } diff --git a/include/nil/blueprint/component_batch.hpp b/include/nil/blueprint/component_batch.hpp new file mode 100644 index 000000000..95645f95c --- /dev/null +++ b/include/nil/blueprint/component_batch.hpp @@ -0,0 +1,331 @@ +//---------------------------------------------------------------------------// +// Copyright (c) 2024 Dmitrii Tabalin +// +// MIT License +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//---------------------------------------------------------------------------// + +#pragma once + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace nil { + namespace blueprint { + namespace detail { + template + struct comparison_for_inputs_results { + bool operator()(const InputType &lhs, const InputType &rhs) const { + // all_vars is not const, so we const_cast here + // we do not modify anything I swear + // copying into non-const is inefficient, and this would get called a ton for large batch sizes + InputType &lhs_input = const_cast(lhs); + InputType &rhs_input = const_cast(rhs); + auto lhs_vars = lhs_input.all_vars(); + auto rhs_vars = rhs_input.all_vars(); + auto result = std::lexicographical_compare( + lhs_vars.begin(), lhs_vars.end(), + rhs_vars.begin(), rhs_vars.end(), + [](const auto &lhs, const auto &rhs) { + return lhs.get() < rhs.get(); + } + ); + return result; + } + }; + } // namespace detail + + using detail::comparison_for_inputs_results; + + struct _batch; + + template + class assignment; + + template + class circuit; + + template + struct input_type_v { + typedef typename ComponentType::input_type type; + }; + + template<> + struct input_type_v<_batch> { + typedef typename boost::mpl::identity type; + }; + + template + struct result_type_v { + typedef typename ComponentType::result_type type; + }; + + template<> + struct result_type_v<_batch> { + typedef typename boost::mpl::identity type; + }; + + template + struct has_add_input { + static ResultType apply(BatchType& batch, const InputType& input) { + return batch.add_input(input); + } + }; + + template + struct has_finalize_batch { + static std::size_t apply(BatchType& batch, nil::blueprint::circuit &bp, + std::unordered_map &variable_map, + const std::uint32_t start_row_index) { + return batch.finalize_batch(bp, variable_map, start_row_index); + } + }; + + // Generic-ish enough batching solution for single-line components + // Lookups currently unsupported + // This even supports component prarameterization + // Although the parameters must be the same for each of the components in the batch + // This is NOT enforced automatically, because I don't see a good way of implementing that + template + class component_batch { + public: + using input_type = typename ComponentType::input_type; + using result_type = typename ComponentType::result_type; + using value_type = typename BlueprintFieldType::value_type; + using var = crypto3::zk::snark::plonk_variable; + using constraint_type = crypto3::zk::snark::plonk_constraint; + using gate_type = crypto3::zk::snark::plonk_gate; + // input-output pairs for batched components + std::map> + inputs_results; + // pointer to the assignment we are going to use in the end + assignment &parent_assignment; + // we cache this; use this to store intermediate results + assignment internal_assignment; + + component_batch(assignment &_assignment) + : parent_assignment(_assignment), + internal_assignment(_assignment.witnesses_amount(), 1, 0, 0) + {} + + ~component_batch() = default; + + void variable_transform(std::reference_wrapper variable) { + variable.get() = parent_assignment.add_batch_variable( + var_value(internal_assignment, variable.get())); + } + + // call this in both generate_assignments and generate_circuit + template + result_type add_input(const input_type &input, ComponentParams... params) { + // short-circuit if we are in generate_circuit and the input has already been through batching + if (inputs_results.find(input) != inputs_results.end()) { + return inputs_results.at(input); + } + // now we need to actually calculate the result without instantiating the component + // luckily, we already have the mechanism for that + input_type input_copy = input; + std::vector> vars = input_copy.all_vars(); + std::vector values; + for (const auto &var : vars) { + values.push_back(var_value(parent_assignment, var.get())); + } + // generate_empty_assignments is used to get the correctly filled result_type + const compiler_manifest assignment_manifest(parent_assignment.witnesses_amount(), 0, 0, 0); + const auto component_manifest = ComponentType::get_manifest(); + const auto intersection = assignment_manifest.intersect(component_manifest); + BOOST_ASSERT_MSG(intersection.is_satisfiable(), "Component either has a constant or does not fit"); + const std::size_t component_witness_amount = intersection.witness_amount->max_value_if_sat(); + + const std::vector constants = {}, public_inputs = {}; + std::vector witness_columns(component_witness_amount); + std::iota(witness_columns.begin(), witness_columns.end(), 0); + ComponentType component_instance(witness_columns, constants, public_inputs, params...); + // safety resize for the case where parent assignment got resized during the lifetime + internal_assignment.resize_witnesses(component_witness_amount); + // move the variables to internal_assignment's public_input column + for (std::size_t i = 0 ; i < vars.size(); i++) { + internal_assignment.public_input(0, i) = values[i]; + vars[i].get() = var(0, i, false, var::column_type::public_input); + } + auto result = generate_empty_assignments(component_instance, internal_assignment, input_copy, 0); + // and replace the variables with placeholders, while saving their values + for (auto variable : result.all_vars()) { + variable_transform(variable); + } + bool insertion_result = inputs_results.insert({input, result}).second; + BOOST_ASSERT(insertion_result); + return result; + } + + // call this once in the end in assignment + // note that the copy constraint replacement is done by assignment in order to reduce the amount of + // spinning through the constraints; we pass variable_map for this purpose + // returns the first free row index + template + std::size_t finalize_batch( + circuit &bp, + std::unordered_map &variable_map, + const std::uint32_t start_row_index, + ComponentParams... params) { + + if (inputs_results.empty()) { + return start_row_index; + } + // First figure out how much we can scale the component + const compiler_manifest assignment_manifest(parent_assignment.witnesses_amount(), 0, 0, true); + const auto component_manifest = ComponentType::get_manifest(); + const auto intersection = assignment_manifest.intersect(component_manifest); + BOOST_ASSERT_MSG(intersection.is_satisfiable(), "Component does not fit"); + const std::size_t component_witness_amount = intersection.witness_amount->max_value_if_sat(); + std::size_t row = start_row_index, + col_offset = 0; + const std::vector constants = {}, public_inputs = {}; + std::size_t gate_id = generate_batch_gate( + bp, inputs_results.begin()->first, component_witness_amount, params...); + for (auto &input_result : inputs_results) { + const input_type &input = input_result.first; + result_type &result = input_result.second; + if (col_offset == 0) { + parent_assignment.enable_selector(gate_id, row); + } + std::vector witness_columns(component_witness_amount); + std::iota(witness_columns.begin(), witness_columns.end(), col_offset); + ComponentType component_instance(witness_columns, constants, public_inputs, params...); + auto actual_result = generate_assignments(component_instance, parent_assignment, input, row); + generate_copy_constraints(component_instance, bp, parent_assignment, input, row); + std::size_t vars_amount = result.all_vars().size(); + for (std::size_t i = 0; i < vars_amount; i++) { + const var batch_var = result.all_vars()[i].get(); + variable_map[batch_var] = actual_result.all_vars()[i].get(); + } + + col_offset += component_witness_amount; + if (col_offset + component_witness_amount - 1 >= parent_assignment.witnesses_amount()) { + col_offset = 0; + row += 1; + } + } + // we fill the unused places with copies of components for the first input to satisfy the gate + if (col_offset != 0 && + (col_offset + component_witness_amount - 1 < parent_assignment.witnesses_amount())) { + + while (col_offset + component_witness_amount - 1 < parent_assignment.witnesses_amount()) { + std::vector witness_columns(component_witness_amount); + std::iota(witness_columns.begin(), witness_columns.end(), col_offset); + ComponentType component_instance(witness_columns, constants, public_inputs, params...); + generate_assignments(component_instance, parent_assignment, inputs_results.begin()->first, row); + col_offset += component_witness_amount; + } + row += 1; + } + return row; + } + + std::vector move_constraints( + const std::vector& constraints, + const std::size_t offset) { + + gate_mover mover([&offset](var v) -> var { + return var(v.index + offset, v.rotation, v.relative, v.type); + }); + std::vector result; + for (const auto& constraint : constraints) { + result.push_back(mover.visit(constraint)); + } + return result; + } + + template + std::size_t generate_batch_gate( + circuit &bp, + const input_type &example_input, + const std::size_t component_witness_amount, + ComponentParams... params) { + + circuit tmp_bp; + std::vector witness_columns(component_witness_amount); + const std::vector constants = {}, public_inputs = {}; + std::iota(witness_columns.begin(), witness_columns.end(), 0); + ComponentType component_instance(witness_columns, constants, public_inputs, params...); + generate_gates(component_instance, tmp_bp, parent_assignment, example_input); + const auto &gates = tmp_bp.gates(); + BOOST_ASSERT(gates.size() == 1); + + std::vector new_gate_constraints, one_gate_constraints; + auto curr_gate = gates[0]; + for (const auto &constraint : curr_gate.constraints) { + new_gate_constraints.push_back(constraint); + one_gate_constraints.push_back(constraint); + } + const std::size_t scaling_amount = parent_assignment.witnesses_amount() / component_witness_amount; + // Technically, we could generate 'not full' gate for the last batch + // We are unlikely to be able to use that space for anything else, so we reduce the amount of selectors + for (std::size_t i = 1; i < scaling_amount; i++) { + auto moved_constraints = move_constraints(one_gate_constraints, i * component_witness_amount); + for (auto &constraint : moved_constraints) { + new_gate_constraints.push_back(constraint); + } + } + return bp.add_gate(new_gate_constraints); + } + + template + bool operator<(const OtherBatchType &other) const { + return std::type_index(typeid(*this)) < std::type_index(typeid(other)); + } + }; + } // namespace blueprint +} // namespace nil + +namespace boost { + namespace type_erasure { + template + struct concept_interface, Base, BatchType> + : Base { + + ResultType add_input(typename as_param::type input) { + return call(nil::blueprint::has_add_input(), *this, input); + } + }; + + template + struct concept_interface, + Base, BatchType> : Base { + std::size_t finalize_batch( + typename as_param&>::type bp, + typename as_param&>::type variable_map, + typename as_param::type start_row_index) { + + return call(nil::blueprint::has_finalize_batch(), *this, + bp, variable_map, start_row_index); + } + }; + } // namespace type_erasure +} // namespace boost diff --git a/include/nil/blueprint/component_stretcher.hpp b/include/nil/blueprint/component_stretcher.hpp index 8f1118249..022d45322 100644 --- a/include/nil/blueprint/component_stretcher.hpp +++ b/include/nil/blueprint/component_stretcher.hpp @@ -35,6 +35,7 @@ #include #include #include +#include #include #include @@ -184,59 +185,6 @@ namespace nil { return stretched_witness_amount; } - // Visitor for expressions, used to move the gate to a new location - class gate_mover : public boost::static_visitor> { - public: - using expression = nil::crypto3::math::expression; - using term_type = nil::crypto3::math::term; - using pow_operation = nil::crypto3::math::pow_operation; - using binary_arithmetic_operation = nil::crypto3::math::binary_arithmetic_operation; - - gate_mover(const component_stretcher *stretcher_, std::size_t selector_) - : stretcher(stretcher_), selector(selector_) {} - - expression visit(const expression& expr) { - return boost::apply_visitor(*this, expr.get_expr()); - } - - expression operator()(const term_type& term) { - std::vector vars; - auto coeff = term.get_coeff(); - for (const auto& var: term.get_vars()) { - vars.emplace_back(stretcher->move_gate_var(var, selector)); - } - term_type result(vars, coeff); - return result; - } - - expression operator()(const pow_operation& pow) { - expression base = boost::apply_visitor( - *this, pow.get_expr().get_expr()); - return pow_operation(base, pow.get_power()); - } - - expression operator()( - const binary_arithmetic_operation& op) { - expression left = - boost::apply_visitor(*this, op.get_expr_left().get_expr()); - expression right = - boost::apply_visitor(*this, op.get_expr_right().get_expr()); - switch (op.get_op()) { - case nil::crypto3::math::ArithmeticOperator::ADD: - return left + right; - case nil::crypto3::math::ArithmeticOperator::SUB: - return left - right; - case nil::crypto3::math::ArithmeticOperator::MULT: - return left * right; - default: - __builtin_unreachable(); - } - } - private: - const component_stretcher *stretcher; - const std::size_t selector; - }; - ComponentType &component; const std::size_t old_witness_amount; const std::size_t stretched_witness_amount; @@ -401,7 +349,9 @@ namespace nil { // 1) Move gates, including properly generating them for (auto gate : tmp_circuit.gates()) { std::vector> new_constraints; - gate_mover gate_displacer = gate_mover(this, gate.selector_index); + gate_mover gate_displacer = gate_mover( + std::bind(&component_stretcher::move_gate_var, + this, std::placeholders::_1, gate.selector_index)); for (auto constraint: gate.constraints) { auto new_constraint = gate_displacer.visit(constraint); new_constraints.push_back(new_constraint); diff --git a/include/nil/blueprint/components/systems/snark/plonk/placeholder/detail/expression_evaluation_component.hpp b/include/nil/blueprint/components/systems/snark/plonk/placeholder/detail/expression_evaluation_component.hpp new file mode 100644 index 000000000..cf95ba0af --- /dev/null +++ b/include/nil/blueprint/components/systems/snark/plonk/placeholder/detail/expression_evaluation_component.hpp @@ -0,0 +1,283 @@ +//---------------------------------------------------------------------------// +// Copyright (c) 2024 Dmitrii Tabalin +// +// MIT License +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//---------------------------------------------------------------------------// + +#pragma once + +#include "nil/crypto3/zk/snark/arithmetization/plonk/assignment.hpp" +#include "nil/crypto3/zk/snark/arithmetization/plonk/constraint.hpp" +#include "nil/crypto3/zk/snark/arithmetization/plonk/constraint_system.hpp" +#include "nil/crypto3/zk/snark/arithmetization/plonk/variable.hpp" +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace nil { + namespace blueprint { + namespace components { + namespace detail { + + template + class expression_to_execution_simple : + public boost::static_visitor> { + public: + using value_type = typename BlueprintFieldType::value_type; + using var = nil::crypto3::zk::snark::plonk_variable; + using assignment_type = assignment; + using multiplication_component_type = nil::blueprint::components::multiplication< + ArithmetizationType, BlueprintFieldType, + nil::blueprint::basic_non_native_policy>; + using addition_component_type = nil::blueprint::components::addition< + ArithmetizationType, BlueprintFieldType, + nil::blueprint::basic_non_native_policy>; + using subtraction_component_type = nil::blueprint::components::subtraction< + ArithmetizationType, BlueprintFieldType, + nil::blueprint::basic_non_native_policy>; + + expression_to_execution_simple(assignment_type &_assignment, + const std::unordered_map &_variable_map) + : assignment(_assignment), variable_map(_variable_map) + {} + + var visit(const nil::crypto3::math::expression &expr) { + return boost::apply_visitor(*this, expr.get_expr()); + } + + var operator()(const nil::crypto3::math::term& term) { + var result; + const std::size_t term_size = term.get_vars().size(); + if (term_size == 0) { + return assignment.add_batch_constant_variable(term.get_coeff()); + } + std::size_t curr_term = 0; + if (term.get_coeff() != value_type::one()) { + auto coeff_var = assignment.add_batch_constant_variable(term.get_coeff()); + result = assignment.template add_input_to_batch( + {coeff_var, variable_map.at(term.get_vars()[curr_term])}).output; + } else { + result = variable_map.at(term.get_vars()[curr_term]); + } + curr_term++; + for (; curr_term < term_size; curr_term++) { + result = assignment.template add_input_to_batch( + {result, variable_map.at(term.get_vars()[curr_term])}).output; + } + return result; + } + + var operator()(const nil::crypto3::math::pow_operation& pow) { + int power = pow.get_power(); + BOOST_ASSERT(power > 0); + var expr_res = boost::apply_visitor(*this, pow.get_expr().get_expr()); + if (power == 1) { + return expr_res; + } + var result = assignment.add_batch_constant_variable(value_type::one()); + while (power > 1) { + if (power % 2 == 0) { + expr_res = assignment.template add_input_to_batch( + {expr_res, expr_res}).output; + power /= 2; + } else { + result = assignment.template add_input_to_batch( + {result, expr_res}).output; + power -= 1; + } + } + return assignment.template add_input_to_batch( + {result, expr_res}).output; + } + + var operator()(const nil::crypto3::math::binary_arithmetic_operation& op) { + auto res1 = boost::apply_visitor(*this, op.get_expr_left().get_expr()); + auto res2 = boost::apply_visitor(*this, op.get_expr_right().get_expr()); + switch (op.get_op()) { + case crypto3::math::ArithmeticOperator::ADD: + return assignment.template add_input_to_batch( + {res1, res2}).output; + case crypto3::math::ArithmeticOperator::SUB: + return assignment.template add_input_to_batch( + {res1, res2}).output; + case crypto3::math::ArithmeticOperator::MULT: + return assignment.template add_input_to_batch( + {res1, res2}).output; + default: + throw std::runtime_error("Unsupported operation"); + } + } + private: + assignment_type &assignment; + const std::unordered_map &variable_map; + }; + + template + class expression_evaluation_component; + + // Brute-force expression evaluation + // Should be relatively easy to repurpose for more opitmised versions + template + class expression_evaluation_component< + crypto3::zk::snark::plonk_constraint_system> + : public plonk_component { + public: + using component_type = plonk_component; + + using var = typename component_type::var; + using constraint_type = nil::crypto3::zk::snark::plonk_constraint; + using manifest_type = nil::blueprint::plonk_component_manifest; + using expression_evaluator_type = expression_to_execution_simple< + BlueprintFieldType, crypto3::zk::snark::plonk_constraint_system>; + + constraint_type constraint; + + // What do we even do with this if we are batching? + static const std::size_t rows_amount = 0; + static const std::size_t gates_amount = 0; + + class gate_manifest_type : public component_gate_manifest { + public: + gate_manifest_type() {} + + std::uint32_t gates_amount() const override { + return expression_evaluation_component::gates_amount; + } + }; + + static gate_manifest get_gate_manifest( + std::size_t witness_amount, + std::size_t lookup_column_amount, + constraint_type &constraint) { + static gate_manifest manifest = gate_manifest_type(); + // TODO: should we intersect with batched gates? + return manifest; + } + + static manifest_type get_manifest() { + static manifest_type manifest = + manifest_type(std::shared_ptr(new manifest_single_value_param(3)), true); + return manifest; + } + + constexpr static std::size_t get_rows_amount(std::size_t witness_amount, + std::size_t lookup_column_amount, + constraint_type &constraint) { + return expression_evaluation_component::rows_amount; + } + + struct input_type { + std::unordered_map variable_mapping; + + std::vector> all_vars() { + std::vector> result; + for (auto &pair : variable_mapping) { + result.push_back(pair.second); + } + return result; + } + }; + + struct result_type { + var output; + + result_type(var output_, std::size_t start_row_index) : output(output_) {} + result_type(var output_) : output(output_) {} + + std::vector> all_vars() { + return {output}; + } + }; + + template + expression_evaluation_component(ContainerType witness, constraint_type constraint_) : + component_type(witness, {}, {}, get_manifest()), constraint(constraint_) + {}; + + template + expression_evaluation_component(WitnessContainerType witness, ConstantContainerType constant, + PublicInputContainerType public_input, constraint_type constraint_) : + component_type(witness, constant, public_input, get_manifest()), constraint(constraint_) + {}; + + expression_evaluation_component( + std::initializer_list + witnesses, + std::initializer_list + constants, + std::initializer_list + public_inputs, + constraint_type constraint_) : + component_type(witnesses, constants, public_inputs, get_manifest()), constraint(constraint_) + {}; + }; + } // namespace detail + + template + using plonk_expression_evaluation_component = detail::expression_evaluation_component< + crypto3::zk::snark::plonk_constraint_system>; + + template + typename plonk_expression_evaluation_component::result_type generate_assignments( + const plonk_expression_evaluation_component + &component, + assignment> + &assignment, + const typename plonk_expression_evaluation_component::input_type + instance_input, + const std::size_t start_row_index) { + + using component_type = plonk_expression_evaluation_component; + using expression_evaluator_type = typename component_type::expression_evaluator_type; + + expression_evaluator_type evaluator(assignment, instance_input.variable_mapping); + return typename component_type::result_type(evaluator.visit(component.constraint), start_row_index); + } + + template + typename plonk_expression_evaluation_component::result_type generate_circuit( + const plonk_expression_evaluation_component + &component, + circuit> + &bp, + assignment> + &assignment, + const typename plonk_expression_evaluation_component::input_type + instance_input, + const std::size_t start_row_index) { + + using component_type = plonk_expression_evaluation_component; + using expression_evaluator_type = typename component_type::expression_evaluator_type; + + expression_evaluator_type evaluator(assignment, instance_input.variable_mapping); + return typename component_type::result_type(evaluator.visit(component.constraint), start_row_index); + } + } // namespace components + } // namespace blueprint +} // namespace nil diff --git a/include/nil/blueprint/utils/connectedness_check.hpp b/include/nil/blueprint/utils/connectedness_check.hpp index bced8e169..85740d99b 100644 --- a/include/nil/blueprint/utils/connectedness_check.hpp +++ b/include/nil/blueprint/utils/connectedness_check.hpp @@ -88,7 +88,7 @@ namespace nil { // We do '+1' in all the assignments to separate the unassigned cells (0 by default) // from the ones which actually got checked. - for (std::size_t witness_column = 0; witness_column < row_size; witness_column++) { + for (std::size_t witness_column = 0; witness_column < assignment.witnesses_amount(); witness_column++) { std::size_t last_row = std::min(end_row, assignment.witness_column_size(witness_column)); for (std::size_t row = start_row_index; row < last_row; row++) { @@ -114,15 +114,16 @@ namespace nil { const auto output_value = zones.find_set(copy_var_address( row_size, start_row_index, rows_amount, variable)) + 1; - switch (variable.type) { + const var &unwrapped_var = variable.get(); + switch (unwrapped_var.type) { case var::column_type::constant: - output_assignment.constant(variable.index, variable.rotation) = output_value; + output_assignment.constant(unwrapped_var.index, unwrapped_var.rotation) = output_value; break; case var::column_type::public_input: - output_assignment.public_input(variable.index, variable.rotation) = output_value; + output_assignment.public_input(unwrapped_var.index, unwrapped_var.rotation) = output_value; break; case var::column_type::witness: - output_assignment.witness(variable.index, variable.rotation) = output_value; + output_assignment.witness(unwrapped_var.index, unwrapped_var.rotation) = output_value; break; case var::column_type::selector: BOOST_ASSERT_MSG(false, "Selector variables should not be input variables."); diff --git a/include/nil/blueprint/utils/gate_mover.hpp b/include/nil/blueprint/utils/gate_mover.hpp new file mode 100644 index 000000000..18572eed7 --- /dev/null +++ b/include/nil/blueprint/utils/gate_mover.hpp @@ -0,0 +1,89 @@ +//---------------------------------------------------------------------------// +// Copyright (c) 2023 Dmitrii Tabalin +// +// MIT License +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//---------------------------------------------------------------------------// + +#pragma once + +#include + +#include + +#include +#include + +namespace nil { + namespace blueprint { + template + class gate_mover : public boost::static_visitor< + nil::crypto3::math::expression>> { + + using var = nil::crypto3::zk::snark::plonk_variable; + std::function var_mover; + public: + using expression = nil::crypto3::math::expression; + using term_type = nil::crypto3::math::term; + using pow_operation = nil::crypto3::math::pow_operation; + using binary_arithmetic_operation = nil::crypto3::math::binary_arithmetic_operation; + + gate_mover(std::function var_mover_) : var_mover(var_mover_) {} + + expression visit(const expression& expr) { + return boost::apply_visitor(*this, expr.get_expr()); + } + + expression operator()(const term_type& term) { + std::vector vars; + auto coeff = term.get_coeff(); + for (const auto& var: term.get_vars()) { + vars.emplace_back(var_mover(var)); + } + term_type result(vars, coeff); + return result; + } + + expression operator()(const pow_operation& pow) { + expression base = boost::apply_visitor( + *this, pow.get_expr().get_expr()); + return pow_operation(base, pow.get_power()); + } + + expression operator()( + const binary_arithmetic_operation& op) { + expression left = + boost::apply_visitor(*this, op.get_expr_left().get_expr()); + expression right = + boost::apply_visitor(*this, op.get_expr_right().get_expr()); + switch (op.get_op()) { + case nil::crypto3::math::ArithmeticOperator::ADD: + return left + right; + case nil::crypto3::math::ArithmeticOperator::SUB: + return left - right; + case nil::crypto3::math::ArithmeticOperator::MULT: + return left * right; + default: + __builtin_unreachable(); + } + } + }; + } // namespace blueprint +} // namespace nil \ No newline at end of file diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index e24e14030..6157c1bca 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -58,6 +58,7 @@ set(COMMON_TEST_FILES "private_input" "proxy" "mock/mocked_components" + "component_batch" ) set(NON_NATIVE_TESTS_FILES @@ -147,6 +148,7 @@ set(PLONK_TESTS_FILES "verifiers/placeholder/fri_cosets" "verifiers/placeholder/fri_lin_inter" "verifiers/placeholder/fri_array_swap" + "verifiers/placeholder/expression_evaluation_component" ) set(FIELDS_TESTS_FILES diff --git a/test/component_batch.cpp b/test/component_batch.cpp new file mode 100644 index 000000000..15f511381 --- /dev/null +++ b/test/component_batch.cpp @@ -0,0 +1,404 @@ +//---------------------------------------------------------------------------// +// Copyright (c) 2024 Dmitrii Tabalin +// +// MIT License +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//---------------------------------------------------------------------------// + +#define BOOST_TEST_MODULE blueprint_component_batch_test + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +using namespace nil::blueprint; +using namespace nil; + +template +struct compare_copy_constraints { + bool operator()(const crypto3::zk::snark::plonk_copy_constraint &lhs, + const crypto3::zk::snark::plonk_copy_constraint &rhs) const { + crypto3::zk::snark::plonk_copy_constraint norm_lhs = + lhs.first < lhs.second ? lhs : std::make_pair(lhs.second, lhs.first); + crypto3::zk::snark::plonk_copy_constraint norm_rhs = + rhs.first < rhs.second ? rhs : std::make_pair(rhs.second, rhs.first); + return norm_lhs < norm_rhs; + } +}; + +template +bool compare_copy_constraint_vectors(const std::vector> &lhs, + const std::vector> &rhs) { + std::set, compare_copy_constraints> + lhs_set(lhs.begin(), lhs.end()), rhs_set(rhs.begin(), rhs.end()); + if (lhs_set.size() != lhs.size() || rhs_set.size() != rhs.size()) { + return false; + } + return lhs_set == rhs_set; +} + +template +struct public_input_var_maker { + using var = crypto3::zk::snark::plonk_variable; + using assignment_type = assignment>; + assignment_type& assignment; + nil::crypto3::random::algebraic_engine generate_random; + std::size_t curr_idx = 0; + + public_input_var_maker(assignment_type& assignment_) : assignment(assignment_) { + boost::random::mt19937 seed_seq; + generate_random.seed(seed_seq); + } + + var operator()() { + assignment.public_input(0, curr_idx) = generate_random(); + return var(0, curr_idx++, false, var::column_type::public_input); + } +}; + +BOOST_AUTO_TEST_SUITE(blueprint_component_batch_test_suite) + +BOOST_AUTO_TEST_CASE(component_batch_basic_test) { + using curve_type = nil::crypto3::algebra::curves::vesta; + using field_type = typename curve_type::scalar_field_type; + + using assignment_type = assignment>; + using circuit_type = circuit>; + using ArithmetizationType = nil::crypto3::zk::snark::plonk_constraint_system; + using var = crypto3::zk::snark::plonk_variable; + using constraint_type = crypto3::zk::snark::plonk_constraint; + using copy_constraint_type = crypto3::zk::snark::plonk_copy_constraint; + + assignment_type assignment(14, 1, 0, 1); + circuit_type circuit; + public_input_var_maker public_input_var_maker(assignment); + + using component_type = components::multiplication< + ArithmetizationType, field_type, nil::blueprint::basic_non_native_policy>; + assignment.add_input_to_batch({public_input_var_maker(), public_input_var_maker()}); + assignment.add_input_to_batch({public_input_var_maker(), public_input_var_maker()}); + std::size_t row = assignment.finalize_component_batches(circuit, 0); + BOOST_CHECK_EQUAL(row, 1); + BOOST_CHECK_EQUAL(circuit.gates().size(), 1); + const auto &gate = circuit.gates()[0]; + BOOST_CHECK_EQUAL(gate.constraints.size(), 4); + std::array expected_constraints = { + var(0, 0) * var(1, 0) - var(2, 0), + var(3, 0) * var(4, 0) - var(5, 0), + var(6, 0) * var(7, 0) - var(8, 0), + var(9, 0) * var(10, 0) - var(11, 0) + }; + for (std::size_t i = 0; i < gate.constraints.size(); ++i) { + BOOST_CHECK_EQUAL(gate.constraints[i], expected_constraints[i]); + } + const std::vector expected_copy_constraints = { + {var(0, 0, false, var::column_type::public_input), var(0, 0, false, var::column_type::witness)}, + {var(1, 0, false, var::column_type::witness), var(0, 1, false, var::column_type::public_input)}, + {var(0, 2, false, var::column_type::public_input), var(3, 0, false, var::column_type::witness)}, + {var(4, 0, false, var::column_type::witness), var(0, 3, false, var::column_type::public_input)} + }; + BOOST_ASSERT(compare_copy_constraint_vectors(circuit.copy_constraints(), expected_copy_constraints)); + + // assignment.export_table(std::cout); + // circuit.export_circuit(std::cout); +} + +BOOST_AUTO_TEST_CASE(component_batch_continuation_test) { + using curve_type = nil::crypto3::algebra::curves::vesta; + using field_type = typename curve_type::scalar_field_type; + + using assignment_type = assignment>; + using circuit_type = circuit>; + using ArithmetizationType = nil::crypto3::zk::snark::plonk_constraint_system; + using var = crypto3::zk::snark::plonk_variable; + using constraint_type = crypto3::zk::snark::plonk_constraint; + using copy_constraint_type = crypto3::zk::snark::plonk_copy_constraint; + + assignment_type assignment(15, 1, 0, 2); + circuit_type circuit; + public_input_var_maker public_input_var_maker(assignment); + + using component_type = components::multiplication< + ArithmetizationType, field_type, nil::blueprint::basic_non_native_policy>; + component_batch component_batch(assignment); + auto first_result = assignment.add_input_to_batch({public_input_var_maker(), public_input_var_maker()}); + auto second_result = assignment.add_input_to_batch({public_input_var_maker(), public_input_var_maker()}); + assignment.add_input_to_batch({public_input_var_maker(), public_input_var_maker()}); + auto third_result = assignment.add_input_to_batch({public_input_var_maker(), public_input_var_maker()}); + auto fourth_result = assignment.add_input_to_batch({first_result.output, second_result.output}); + using addition_type = components::addition< + ArithmetizationType, field_type, nil::blueprint::basic_non_native_policy>; + std::size_t row = 0; + addition_type add_component({0, 1, 2}, {}, {}); + auto addition_result = generate_assignments(add_component, assignment, {third_result.output, fourth_result.output}, row); + generate_circuit(add_component, circuit, assignment, {third_result.output, fourth_result.output}, row++); + auto fifth_result = assignment.add_input_to_batch({addition_result.output, public_input_var_maker()}); + generate_assignments(add_component, assignment, {addition_result.output, fifth_result.output}, row); + generate_circuit(add_component, circuit, assignment, {addition_result.output, fifth_result.output}, row++); + row = assignment.finalize_component_batches(circuit, row); + BOOST_CHECK_EQUAL(row, 4); + BOOST_CHECK_EQUAL(circuit.gates().size(), 2); + const auto &gate = circuit.gates()[1]; + BOOST_CHECK_EQUAL(gate.constraints.size(), 5); + std::array expected_constraints = { + var(0, 0) * var(1, 0) - var(2, 0), + var(3, 0) * var(4, 0) - var(5, 0), + var(6, 0) * var(7, 0) - var(8, 0), + var(9, 0) * var(10, 0) - var(11, 0), + var(12, 0) * var(13, 0) - var(14, 0) + }; + for (std::size_t i = 0; i < gate.constraints.size(); ++i) { + BOOST_CHECK_EQUAL(gate.constraints[i], expected_constraints[i]); + } + const std::vector expected_copy_constraints = { + {var(11, 2, false, var::column_type::witness), var(0, 0, false, var::column_type::witness)}, + {var(1, 0, false, var::column_type::witness), var(2, 3, false, var::column_type::witness)}, + {var(2, 0, false, var::column_type::witness), var(0, 1, false, var::column_type::witness)}, + {var(1, 1, false, var::column_type::witness), var(14, 2, false, var::column_type::witness)}, + {var(0, 0, false, var::column_type::public_input), var(0, 2, false, var::column_type::witness)}, + {var(1, 2, false, var::column_type::witness), var(0, 1, false, var::column_type::public_input)}, + {var(0, 2, false, var::column_type::public_input), var(3, 2, false, var::column_type::witness)}, + {var(4, 2, false, var::column_type::witness), var(0, 3, false, var::column_type::public_input)}, + {var(0, 4, false, var::column_type::public_input), var(6, 2, false, var::column_type::witness)}, + {var(7, 2, false, var::column_type::witness), var(0, 5, false, var::column_type::public_input)}, + {var(0, 6, false, var::column_type::public_input), var(9, 2, false, var::column_type::witness)}, + {var(10, 2, false, var::column_type::witness), var(0, 7, false, var::column_type::public_input)}, + {var(2, 0, false, var::column_type::witness), var(12, 2, false, var::column_type::witness)}, + {var(13, 2, false, var::column_type::witness), var(0, 8, false, var::column_type::public_input)}, + {var(2, 2, false, var::column_type::witness), var(0, 3, false, var::column_type::witness)}, + {var(1, 3, false, var::column_type::witness), var(5, 2, false, var::column_type::witness)} + }; + + BOOST_ASSERT(compare_copy_constraint_vectors(circuit.copy_constraints(), expected_copy_constraints)); + + // assignment.export_table(std::cout); + // circuit.export_circuit(std::cout); +} + +BOOST_AUTO_TEST_CASE(component_batch_multibatch_test) { + using curve_type = nil::crypto3::algebra::curves::vesta; + using field_type = typename curve_type::scalar_field_type; + + using assignment_type = assignment>; + using circuit_type = circuit>; + using ArithmetizationType = nil::crypto3::zk::snark::plonk_constraint_system; + using var = crypto3::zk::snark::plonk_variable; + using constraint_type = crypto3::zk::snark::plonk_constraint; + using copy_constraint_type = crypto3::zk::snark::plonk_copy_constraint; + + assignment_type assignment(15, 1, 0, 3); + circuit_type circuit; + public_input_var_maker public_input_var_maker(assignment); + + using mul_component_type = components::multiplication< + ArithmetizationType, field_type, nil::blueprint::basic_non_native_policy>; + using add_component_type = components::addition< + ArithmetizationType, field_type, nil::blueprint::basic_non_native_policy>; + using div_or_zero_component_type = components::division_or_zero; + auto mul_result = assignment.add_input_to_batch( + {public_input_var_maker(), public_input_var_maker()}); + auto add_result = assignment.add_input_to_batch({mul_result.output, public_input_var_maker()}); + auto mul_result_2 = assignment.add_input_to_batch({add_result.output, mul_result.output}); + assignment.add_input_to_batch({public_input_var_maker(), public_input_var_maker()}); + div_or_zero_component_type div_or_zero_component({0, 1, 2, 3, 4}, {}, {}); + var div_or_zero_var = public_input_var_maker(); + auto div_or_zero_res = generate_assignments( + div_or_zero_component, assignment, {mul_result_2.output, div_or_zero_var}, 0); + generate_circuit(div_or_zero_component, circuit, assignment, {mul_result_2.output, div_or_zero_var}, 0); + assignment.add_input_to_batch({div_or_zero_res.output, public_input_var_maker()}); + assignment.add_input_to_batch({public_input_var_maker(), public_input_var_maker()}); + assignment.add_input_to_batch({add_result.output, mul_result.output}); + // duplicates, should not count! + for (std::size_t i = 0; i < 5; i++) { + assignment.add_input_to_batch({add_result.output, mul_result.output}); + } + // not duplicates, should count + for (std::size_t i = 0; i < 5; i++) { + assignment.add_input_to_batch({public_input_var_maker(), public_input_var_maker()}); + } + std::size_t row = assignment.finalize_component_batches(circuit, 1); + BOOST_CHECK_EQUAL(row, 4); + + BOOST_CHECK_EQUAL(circuit.gates().size(), 3); + const auto &gate_1 = circuit.gates()[1]; + BOOST_CHECK_EQUAL(gate_1.constraints.size(), 5); + const std::array expected_constraints_mul = { + var(0, 0) * var(1, 0) - var(2, 0), + var(3, 0) * var(4, 0) - var(5, 0), + var(6, 0) * var(7, 0) - var(8, 0), + var(9, 0) * var(10, 0) - var(11, 0), + var(12, 0) * var(13, 0) - var(14, 0) + }; + const std::array expected_constraints_add = { + var(0, 0) + var(1, 0) - var(2, 0), + var(3, 0) + var(4, 0) - var(5, 0), + var(6, 0) + var(7, 0) - var(8, 0), + var(9, 0) + var(10, 0) - var(11, 0), + var(12, 0) + var(13, 0) - var(14, 0) + }; + if (gate_1.constraints[0] == var(0, 0) * var(1, 0) - var(2, 0)) { + for (std::size_t i = 0; i < gate_1.constraints.size(); ++i) { + BOOST_CHECK_EQUAL(gate_1.constraints[i], expected_constraints_mul[i]); + } + } else { + for (std::size_t i = 0; i < gate_1.constraints.size(); ++i) { + BOOST_CHECK_EQUAL(gate_1.constraints[i], expected_constraints_add[i]); + } + } + const auto &gate_2 = circuit.gates()[2]; + BOOST_CHECK_EQUAL(gate_1.constraints.size(), 5); + if (gate_2.constraints[0] == var(0, 0) * var(1, 0) - var(2, 0)) { + for (std::size_t i = 0; i < gate_2.constraints.size(); ++i) { + BOOST_CHECK_EQUAL(gate_2.constraints[i], expected_constraints_mul[i]); + } + } else { + for (std::size_t i = 0; i < gate_2.constraints.size(); ++i) { + BOOST_CHECK_EQUAL(gate_2.constraints[i], expected_constraints_add[i]); + } + } + BOOST_ASSERT((gate_1.constraints[0] == expected_constraints_mul[0] && + gate_2.constraints[0] == expected_constraints_add[0]) || + (gate_1.constraints[0] == expected_constraints_add[0] && + gate_2.constraints[0] == expected_constraints_mul[0])); + + const std::vector expected_copy_constraints = { + {var(14, 2, false, var::column_type::witness), var(0, 0, false, var::column_type::witness)}, + {var(1, 0, false, var::column_type::witness), var(0, 5, false, var::column_type::public_input)}, + {var(0, 0, false, var::column_type::public_input), var(0, 1, false, var::column_type::witness)}, + {var(1, 1, false, var::column_type::witness), var(0, 1, false, var::column_type::public_input)}, + {var(0, 3, false, var::column_type::public_input), var(3, 1, false, var::column_type::witness)}, + {var(4, 1, false, var::column_type::witness), var(0, 4, false, var::column_type::public_input)}, + {var(0, 7, false, var::column_type::public_input), var(6, 1, false, var::column_type::witness)}, + {var(7, 1, false, var::column_type::witness), var(0, 8, false, var::column_type::public_input)}, + {var(0, 9, false, var::column_type::public_input), var(9, 1, false, var::column_type::witness)}, + {var(10, 1, false, var::column_type::witness), var(0, 10, false, var::column_type::public_input)}, + {var(0, 11, false, var::column_type::public_input), var(12, 1, false, var::column_type::witness)}, + {var(13, 1, false, var::column_type::witness), var(0, 12, false, var::column_type::public_input)}, + {var(0, 13, false, var::column_type::public_input), var(0, 2, false, var::column_type::witness)}, + {var(1, 2, false, var::column_type::witness), var(0, 14, false, var::column_type::public_input)}, + {var(0, 15, false, var::column_type::public_input), var(3, 2, false, var::column_type::witness)}, + {var(4, 2, false, var::column_type::witness), var(0, 16, false, var::column_type::public_input)}, + {var(0, 17, false, var::column_type::public_input), var(6, 2, false, var::column_type::witness)}, + {var(7, 2, false, var::column_type::witness), var(0, 18, false, var::column_type::public_input)}, + {var(2, 0, false, var::column_type::witness), var(9, 2, false, var::column_type::witness)}, + {var(10, 2, false, var::column_type::witness), var(0, 6, false, var::column_type::public_input)}, + {var(2, 3, false, var::column_type::witness), var(12, 2, false, var::column_type::witness)}, + {var(13, 2, false, var::column_type::witness), var(2, 1, false, var::column_type::witness)}, + {var(2, 1, false, var::column_type::witness), var(0, 3, false, var::column_type::witness)}, + {var(1, 3, false, var::column_type::witness), var(0, 2, false, var::column_type::public_input)}, + {var(2, 3, false, var::column_type::witness), var(3, 3, false, var::column_type::witness)}, + {var(4, 3, false, var::column_type::witness), var(2, 1, false, var::column_type::witness)} + }; + + BOOST_ASSERT(compare_copy_constraint_vectors(circuit.copy_constraints(), expected_copy_constraints)); + + // assignment.export_table(std::cout); + // circuit.export_circuit(std::cout); +} + +BOOST_AUTO_TEST_CASE(component_batch_const_batch_test) { + using curve_type = nil::crypto3::algebra::curves::vesta; + using field_type = typename curve_type::scalar_field_type; + + using assignment_type = assignment>; + using circuit_type = circuit>; + using ArithmetizationType = nil::crypto3::zk::snark::plonk_constraint_system; + using var = crypto3::zk::snark::plonk_variable; + using constraint_type = crypto3::zk::snark::plonk_constraint; + using lookup_constraint_type = crypto3::zk::snark::plonk_lookup_constraint; + using copy_constraint_type = crypto3::zk::snark::plonk_copy_constraint; + + assignment_type assignment(15, 1, 1, 3); + circuit_type circuit; + public_input_var_maker public_input_var_maker(assignment); + + using multiplication_type = components::multiplication< + ArithmetizationType, field_type, nil::blueprint::basic_non_native_policy>; + using mul_by_constant_type = components::mul_by_constant; + + mul_by_constant_type mul_by_constant_component({0, 1, 2}, {0}, {}, 1444); + std::size_t row = 0; + var mul_by_constant_input = public_input_var_maker(); + auto mul_by_const_result = generate_assignments( + mul_by_constant_component, assignment, {mul_by_constant_input}, row); + generate_circuit(mul_by_constant_component, circuit, assignment, {mul_by_constant_input}, row++); + lookup_constraint_type lookup_constraint; + lookup_constraint.table_id = 0; + lookup_constraint.lookup_input.push_back(constraint_type({var(0, 1, true, var::column_type::constant)})); + std::size_t lookup_selector = circuit.add_lookup_gate(lookup_constraint); + assignment.enable_selector(lookup_selector, row++); + // filling the constants is required to resize the column + assignment.constant(0, row) = 1445; + assignment.enable_selector(lookup_selector, row++); + assignment.constant(0, row) = 1446; + auto mul_result = assignment.add_input_to_batch( + {assignment.add_batch_constant_variable(1), assignment.add_batch_constant_variable(2)}); + // have to check lookup functionality manually + assignment.add_input_to_batch({public_input_var_maker(), mul_result.output}); + assignment.add_input_to_batch({mul_by_const_result.output, public_input_var_maker()}); + assignment.finalize_component_batches(circuit, row); + assignment.finalize_constant_batches(circuit, 0); + + // duplicates; should not count! + for (std::size_t i = 0; i < 10; i++) { + assignment.add_batch_constant_variable(2); + } + + BOOST_ASSERT(assignment.constant(0, 0) == 1444); + BOOST_ASSERT(assignment.constant(0, 1) == 1); + BOOST_ASSERT(assignment.constant(0, 2) == 1445); + BOOST_ASSERT(assignment.constant(0, 3) == 1446); + BOOST_ASSERT(assignment.constant(0, 4) == 2); + BOOST_ASSERT(assignment.rows_amount() == 5); + + const std::vector expected_copy_constraints = { + {var(0, 0, false, var::column_type::public_input), var(0, 0, false, var::column_type::witness)}, + {var(0, 1, false, var::column_type::public_input), var(0, 3, false, var::column_type::witness)}, + {var(1, 3, false, var::column_type::witness), var(8, 3, false, var::column_type::witness)}, + {var(1, 0, false, var::column_type::witness), var(3, 3, false, var::column_type::witness)}, + {var(4, 3, false, var::column_type::witness), var(0, 2, false, var::column_type::public_input)}, + {var(0, 1, false, var::column_type::constant), var(6, 3, false, var::column_type::witness)}, + {var(7, 3, false, var::column_type::witness), var(0, 4, false, var::column_type::constant)} + }; + + BOOST_ASSERT(compare_copy_constraint_vectors(circuit.copy_constraints(), expected_copy_constraints)); + + // assignment.export_table(std::cout); + // circuit.export_circuit(std::cout); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/test/test_plonk_component.hpp b/test/test_plonk_component.hpp index 3711108c8..246d42e7d 100644 --- a/test/test_plonk_component.hpp +++ b/test/test_plonk_component.hpp @@ -184,7 +184,6 @@ namespace nil { blueprint::connectedness_check_type connectedness_check, ComponentStaticInfoArgs... component_static_info_args) { using component_type = ComponentType; - blueprint::circuit> bp; blueprint::assignment> assignment(desc); @@ -197,7 +196,11 @@ namespace nil { static boost::random::mt19937 gen; static boost::random::uniform_int_distribution<> dist(0, 100); - std::size_t start_row = dist(gen); + std::size_t start_row = 0; // dist(gen); + // resize to ensure that if the component is empty by default (e.g. a component which only uses batching) + if (start_row != 0) { + assignment.witness(0, start_row - 1) = 0; + } if constexpr (PrivateInput) { for (std::size_t i = 0; i < public_input.size(); i++) { @@ -216,12 +219,45 @@ namespace nil { assigner(component_instance, assignment, instance_input, start_row)); result_check(assignment, component_result); + // Stretched components do not have a manifest, as they are dynamically generated. + if constexpr (!blueprint::components::is_component_stretcher< + BlueprintFieldType, ComponentType>::value) { + BOOST_ASSERT_MSG(bp.num_gates() + bp.num_lookup_gates() == + component_type::get_gate_manifest(component_instance.witness_amount(), 0, + component_static_info_args...).get_gates_amount(), + "Component total gates amount does not match actual gates amount."); + } + + if (start_row + component_instance.rows_amount >= public_input.size()) { + BOOST_ASSERT_MSG(assignment.rows_amount() - start_row == component_instance.rows_amount, + "Component rows amount does not match actual rows amount."); + // Stretched components do not have a manifest, as they are dynamically generated. + if constexpr (!blueprint::components::is_component_stretcher< + BlueprintFieldType, ComponentType>::value) { + BOOST_ASSERT_MSG(assignment.rows_amount() - start_row == + component_type::get_rows_amount(component_instance.witness_amount(), 0, + component_static_info_args...), + "Static component rows amount does not match actual rows amount."); + } + } + + const std::size_t rows_after_component_batching = + assignment.finalize_component_batches(bp, start_row + component_instance.rows_amount); + const std::size_t rows_after_const_batching = + assignment.finalize_constant_batches(bp, 0, std::max(start_row, 1)); + const std::size_t rows_after_batching = std::max(rows_after_component_batching, rows_after_const_batching); + for (auto variable : component_result.all_vars()) { + if (assignment.get_batch_variable_map().count(variable)) { + variable.get() = assignment.get_batch_variable_map().at(variable); + } + } + if constexpr (!PrivateInput) { bool is_connected = check_connectedness( assignment, bp, instance_input.all_vars(), - component_result.all_vars(), start_row, component_instance.rows_amount, + component_result.all_vars(), start_row, rows_after_batching - start_row, connectedness_check); if (connectedness_check.t == blueprint::connectedness_check_type::type::NONE) { std::cout << "WARNING: Connectedness check is disabled." << std::endl; @@ -232,9 +268,9 @@ namespace nil { // If the whole of public_input isn't shown, increase the end row // auto zones = blueprint::detail::generate_connectedness_zones( - // assignment, bp, instance_input.all_vars(), start_row, component_instance.rows_amount); + // assignment, bp, instance_input.all_vars(), start_row, rows_after_batching - start_row); // blueprint::detail::export_connectedness_zones( - // zones, assignment, instance_input.all_vars(), start_row, component_instance.rows_amount, std::cout); + // zones, assignment, instance_input.all_vars(), start_row, rows_after_batching - start_row, std::cout); // BOOST_ASSERT_MSG(is_connected, // "Component disconnected! See comment above this assert for a way to output a visual representation of the connectedness graph."); @@ -242,27 +278,6 @@ namespace nil { desc.usable_rows_amount = assignment.rows_amount(); - if (start_row + component_instance.rows_amount >= public_input.size()) { - BOOST_ASSERT_MSG(assignment.rows_amount() - start_row == component_instance.rows_amount, - "Component rows amount does not match actual rows amount."); - // Stretched components do not have a manifest, as they are dynamically generated. - if constexpr (!blueprint::components::is_component_stretcher< - BlueprintFieldType, ComponentType>::value) { - BOOST_ASSERT_MSG(assignment.rows_amount() - start_row == - component_type::get_rows_amount(component_instance.witness_amount(), 0, - component_static_info_args...), - "Static component rows amount does not match actual rows amount."); - } - } - // Stretched components do not have a manifest, as they are dynamically generated. - if constexpr (!blueprint::components::is_component_stretcher< - BlueprintFieldType, ComponentType>::value) { - BOOST_ASSERT_MSG(bp.num_gates() + bp.num_lookup_gates()== - component_type::get_gate_manifest(component_instance.witness_amount(), 0, - component_static_info_args...).get_gates_amount(), - "Component total gates amount does not match actual gates amount."); - } - if constexpr (nil::blueprint::use_lookups()) { // Components with lookups may use constant columns. // But now all constants are placed in the first column. @@ -270,7 +285,9 @@ namespace nil { // Rather universal for testing // We may start from zero if component doesn't use ordinary constants. std::vector lookup_columns_indices; - for( std::size_t i = 1; i < assignment.constants_amount(); i++ ) lookup_columns_indices.push_back(i); + for(std::size_t i = 1; i < assignment.constants_amount(); i++) { + lookup_columns_indices.push_back(i); + } std::size_t cur_selector_id = 0; for(const auto &gate: bp.gates()){ diff --git a/test/verifiers/placeholder/expression_evaluation_component.cpp b/test/verifiers/placeholder/expression_evaluation_component.cpp new file mode 100644 index 000000000..eeba45a9b --- /dev/null +++ b/test/verifiers/placeholder/expression_evaluation_component.cpp @@ -0,0 +1,156 @@ +//---------------------------------------------------------------------------// +// Copyright (c) 2024 Dmitrii Tabalin +// +// MIT License +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//---------------------------------------------------------------------------// + +#define BOOST_TEST_MODULE plonk_expression_evaluation_component_test + +#include + +#include + +#include +#include + +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "../../test_plonk_component.hpp" + +using namespace nil; + +template +void test(std::vector &public_input, + std::unordered_map, + crypto3::zk::snark::plonk_variable> var_map, + crypto3::zk::snark::plonk_constraint &constraint) { + + constexpr std::size_t WitnessColumns = WitnessAmount; + constexpr std::size_t PublicInputColumns = 1; + constexpr std::size_t ConstantColumns = 1; + constexpr std::size_t SelectorColumns = 3; + using hash_type = nil::crypto3::hashes::keccak_1600<256>; + constexpr std::size_t Lambda = 1; + + zk::snark::plonk_table_description desc( + WitnessColumns, PublicInputColumns, ConstantColumns, SelectorColumns); + using ArithmetizationType = crypto3::zk::snark::plonk_constraint_system; + using AssignmentType = blueprint::assignment; + using value_type = typename BlueprintFieldType::value_type; + using var = crypto3::zk::snark::plonk_variable; + + using component_type = blueprint::components::detail::expression_evaluation_component; + + std::array witnesses; + std::iota(witnesses.begin(), witnesses.end(), 0); + component_type component_instance(witnesses, std::array(), std::array(), + constraint); + + std::function get_var_value = [&var_map, &public_input](const var &v) { + BOOST_ASSERT(var_map.count(v) > 0); + const var input_var = var_map[v]; + BOOST_ASSERT(input_var.type == var::column_type::public_input); + BOOST_ASSERT(input_var.index == 0); + return public_input[input_var.rotation]; + }; + expression_evaluator evaluator(constraint, get_var_value); + value_type expected_res = evaluator.evaluate(); + + typename component_type::input_type instance_input = {var_map}; + + auto result_check = [&expected_res](AssignmentType &assignment, typename component_type::result_type &real_res) { + BOOST_ASSERT(var_value(assignment, real_res.output) == expected_res); + }; + + crypto3::test_component( + component_instance, desc, public_input, result_check, instance_input, + nil::blueprint::connectedness_check_type::type::STRONG, + constraint); +} + +BOOST_AUTO_TEST_SUITE(blueprint_plonk_test_suite) + +BOOST_AUTO_TEST_CASE(blueprint_plonk_expression_evaluation_component_basic_test) { + using field_type = typename crypto3::algebra::curves::pallas::base_field_type; + using value_type = field_type::value_type; + using var = crypto3::zk::snark::plonk_variable; + using constraint_type = crypto3::zk::snark::plonk_constraint; + + constraint_type example_constraint = var(0, 0) * var(1, 1) - var(2, 3); + std::unordered_map var_map = { + {var(0, 0), var(0, 0, false, var::column_type::public_input)}, + {var(1, 1), var(0, 1, false, var::column_type::public_input)}, + {var(2, 3), var(0, 2, false, var::column_type::public_input)} + }; + std::vector public_input = {value_type(2), value_type(3), value_type(4)}; + test(public_input, var_map, example_constraint); + constraint_type example_constraint_2 = var(0, 0).pow(4); + std::vector public_input_2 = {value_type(2)}; + std::unordered_map var_map_2 = { + {var(0, 0), var(0, 0, false, var::column_type::public_input)}, + }; + test(public_input_2, var_map_2, example_constraint_2); +} + +BOOST_AUTO_TEST_CASE(blueprint_plonk_expression_evaluation_component_random_tests) { + using field_type = typename crypto3::algebra::curves::pallas::base_field_type; + using value_type = field_type::value_type; + using var = crypto3::zk::snark::plonk_variable; + + boost::random::mt19937 gen(1444); + nil::crypto3::random::algebraic_engine random_engine(gen); + + constexpr std::size_t WitnessAmount = 15; + blueprint::assignment> tmp_assignment( + WitnessAmount, 0, 1, 0); + for (std::size_t i = 0; i < 10; i++) { + auto constraint = nil::blueprint::generate_random_constraint(tmp_assignment, 7, 3, gen); + std::set variable_set; + std::function variable_extractor = + [&variable_set](var variable) { variable_set.insert(variable); }; + nil::crypto3::math::expression_for_each_variable_visitor visitor(variable_extractor); + visitor.visit(constraint); + std::unordered_map var_map; + std::size_t rotation = 0; + for (auto &variable : variable_set) { + var_map[variable] = var(0, rotation++, false, var::column_type::public_input); + } + std::vector public_input; + public_input.reserve(variable_set.size()); + for (std::size_t i = 0; i < variable_set.size(); i++) { + public_input.push_back(random_engine()); + } + test(public_input, var_map, constraint); + } +} + +BOOST_AUTO_TEST_SUITE_END() From 0c92a01dad3158ff5d2353837a13820006bd6664 Mon Sep 17 00:00:00 2001 From: Iluvmagick Date: Thu, 14 Mar 2024 00:23:27 +0400 Subject: [PATCH 2/2] Final polynomial check. --- .github/workflows/run_tests.yml | 1 + .../plonk/verifier/final_polynomial_check.hpp | 282 ++++++++++++++++++ test/CMakeLists.txt | 1 + .../placeholder/final_polynomial_check.cpp | 159 ++++++++++ 4 files changed, 443 insertions(+) create mode 100644 include/nil/blueprint/components/systems/snark/plonk/verifier/final_polynomial_check.hpp create mode 100644 test/verifiers/placeholder/final_polynomial_check.cpp diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml index 9edf0e1bb..dcda5ea23 100644 --- a/.github/workflows/run_tests.yml +++ b/.github/workflows/run_tests.yml @@ -82,6 +82,7 @@ jobs: blueprint_mock_mocked_components_test blueprint_component_batch_test blueprint_verifiers_placeholder_expression_evaluation_component_test + blueprint_verifiers_placeholder_final_polynomial_check_test ] # Tests to execute include: # Abused to enable proof generation for some tests; add more as needed - target: blueprint_algebra_fields_plonk_non_native_logic_ops_test diff --git a/include/nil/blueprint/components/systems/snark/plonk/verifier/final_polynomial_check.hpp b/include/nil/blueprint/components/systems/snark/plonk/verifier/final_polynomial_check.hpp new file mode 100644 index 000000000..d34e33549 --- /dev/null +++ b/include/nil/blueprint/components/systems/snark/plonk/verifier/final_polynomial_check.hpp @@ -0,0 +1,282 @@ +//---------------------------------------------------------------------------// +// Copyright (c) 2024 Dmitrii Tabalin +// +// MIT License +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//---------------------------------------------------------------------------// + +#pragma once + +#include "nil/blueprint/components/algebra/fields/plonk/addition.hpp" +#include "nil/blueprint/components/algebra/fields/plonk/multiplication.hpp" +#include "nil/blueprint/components/systems/snark/plonk/placeholder/detail/expression_evaluation_component.hpp" +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace nil { + namespace blueprint { + namespace components { + + using detail::expression_evaluation_component; + + template + class final_polynomial_check; + + // checks that the polynomial defined by power + 1 coefficients has values equal to 2*lambda passed values + // at 2*lambda points of the form (s, -s) + // (where one of the points is passed, and the other one is inferred) + // coefficients passed highest to lowest power + template + class final_polynomial_check< + crypto3::zk::snark::plonk_constraint_system> + : public plonk_component { + public: + using component_type = plonk_component; + + using var = typename component_type::var; + using constraint_type = nil::crypto3::zk::snark::plonk_constraint; + using manifest_type = nil::blueprint::plonk_component_manifest; + using expression_evaluator_type = plonk_expression_evaluation_component; + + std::size_t power; + std::size_t lambda; + + static const std::size_t rows_amount = 0; + static const std::size_t gates_amount = 0; + + class gate_manifest_type : public component_gate_manifest { + public: + gate_manifest_type() {} + + std::uint32_t gates_amount() const override { + return final_polynomial_check::gates_amount; + } + }; + + static gate_manifest get_gate_manifest( + std::size_t witness_amount, + std::size_t lookup_column_amount, + std::size_t power, + std::size_t labmda) { + static gate_manifest manifest = gate_manifest_type(); + return manifest; + } + + static manifest_type get_manifest() { + static manifest_type manifest = + manifest_type(std::shared_ptr(new manifest_single_value_param(3)), true); + return manifest; + } + + constexpr static std::size_t get_rows_amount(std::size_t witness_amount, + std::size_t lookup_column_amount, + std::size_t power, + std::size_t labmda) { + return final_polynomial_check::rows_amount; + } + + struct input_type { + std::vector coefficients; + std::vector points; + std::vector values; + + std::vector> all_vars() { + std::vector> result; + for (auto &coefficient : coefficients) { + result.push_back(coefficient); + } + for (auto &point : points) { + result.push_back(point); + } + for (auto &value : values) { + result.push_back(value); + } + return result; + } + }; + + struct result_type { + // fail if the check is not satisfied + result_type(const final_polynomial_check &component, std::uint32_t start_row_index) {} + + std::vector> all_vars() { + return {}; + } + }; + + template + final_polynomial_check(ContainerType witness, std::size_t power_, std::size_t lambda_) : + component_type(witness, {}, {}, get_manifest()), + power(power_), lambda(lambda_) + {}; + + template + final_polynomial_check(WitnessContainerType witness, ConstantContainerType constant, + PublicInputContainerType public_input, + std::size_t power_, std::size_t lambda_) : + component_type(witness, constant, public_input, get_manifest()), + power(power_), lambda(lambda_) + {}; + + final_polynomial_check( + std::initializer_list + witnesses, + std::initializer_list + constants, + std::initializer_list + public_inputs, + std::size_t power_, std::size_t lambda_) : + component_type(witnesses, constants, public_inputs, get_manifest()), + power(power_), lambda(lambda_) + {}; + + inline std::tuple> build_mapping_and_constraints( + const input_type &instance_input) const { + + std::unordered_map coefficient_mapping; + // map coefficients to themselves; we can directly put them into an expression + for (auto coefficient : instance_input.coefficients) { + coefficient_mapping[coefficient] = coefficient; + } + // the only relative vars present, thus cannot possibly conflict with the mapping + var s_var = var(0, 0, true, var::column_type::witness), + y_var = var(0, 1, true, var::column_type::witness); + constraint_type constraint_s = instance_input.coefficients[0]; + for (std::size_t i = 1; i < instance_input.coefficients.size(); i++) { + constraint_s = instance_input.coefficients[i] + s_var * constraint_s; + } + constraint_s = constraint_s - y_var; + constraint_type constraint_m_s = instance_input.coefficients[0]; + for (std::size_t i = 1; i < instance_input.coefficients.size(); i++) { + constraint_m_s = instance_input.coefficients[i] - s_var * constraint_m_s; + } + constraint_m_s = constraint_m_s - y_var; + return std::make_tuple(constraint_s, constraint_m_s, coefficient_mapping); + } + }; + + template + using plonk_final_polynomial_check = final_polynomial_check< + crypto3::zk::snark::plonk_constraint_system>; + + template + typename plonk_final_polynomial_check::result_type generate_assignments( + const plonk_final_polynomial_check + &component, + assignment> + &assignment, + const typename plonk_final_polynomial_check::input_type + instance_input, + const std::size_t start_row_index) { + + using component_type = plonk_final_polynomial_check; + using expression_evaluator_type = typename component_type::expression_evaluator_type; + using expression_evaluator_input_type = typename expression_evaluator_type::input_type; + using var = typename component_type::var; + + BOOST_ASSERT(instance_input.coefficients.size() == component.power + 1); + BOOST_ASSERT(instance_input.points.size() == component.lambda); + BOOST_ASSERT(instance_input.values.size() == 2 * component.lambda); + + auto mapping_and_constraints = component.build_mapping_and_constraints(instance_input); + for (std::size_t i = 0; i < instance_input.points.size(); i++) { + var point = instance_input.points[i]; + var value = instance_input.values[2 * i], + value_m = instance_input.values[2 * i + 1]; + std::unordered_map mapping = std::get<2>(mapping_and_constraints); + mapping.insert({var(0, 0, true, var::column_type::witness), point}); + mapping.insert({var(0, 1, true, var::column_type::witness), value}); + expression_evaluator_type evaluator( + component._W, component._C, component._PI, std::get<0>(mapping_and_constraints)); + expression_evaluator_input_type input = {mapping}; + generate_assignments(evaluator, assignment, input, start_row_index); + expression_evaluator_type evaluator_m( + component._W, component._C, component._PI, std::get<1>(mapping_and_constraints)); + mapping.erase(var(0, 1, true, var::column_type::witness)); + mapping.insert({var(0, 1, true, var::column_type::witness), value_m}); + input = {mapping}; + generate_assignments(evaluator_m, assignment, input, start_row_index); + } + + return typename component_type::result_type(component, start_row_index); + } + + template + typename plonk_final_polynomial_check::result_type generate_circuit( + const plonk_final_polynomial_check + &component, + circuit> + &bp, + assignment> + &assignment, + const typename plonk_final_polynomial_check::input_type + instance_input, + const std::size_t start_row_index) { + + using component_type = plonk_final_polynomial_check; + using expression_evaluator_type = typename component_type::expression_evaluator_type; + using expression_evaluator_input_type = typename expression_evaluator_type::input_type; + using var = typename component_type::var; + + BOOST_ASSERT(instance_input.coefficients.size() == component.power + 1); + BOOST_ASSERT(instance_input.points.size() == component.lambda); + BOOST_ASSERT(instance_input.values.size() == 2 * component.lambda); + + var zero = assignment.add_batch_constant_variable(0); + + auto mapping_and_constraints = component.build_mapping_and_constraints(instance_input); + for (std::size_t i = 0; i < instance_input.points.size(); i++) { + var point = instance_input.points[i]; + var value = instance_input.values[2 * i], + value_m = instance_input.values[2 * i + 1]; + std::unordered_map mapping = std::get<2>(mapping_and_constraints); + mapping.insert({var(0, 0, true, var::column_type::witness), point}); + mapping.insert({var(0, 1, true, var::column_type::witness), value}); + expression_evaluator_type evaluator( + component._W, component._C, component._PI, std::get<0>(mapping_and_constraints)); + expression_evaluator_input_type input = {mapping}; + auto result = generate_circuit(evaluator, bp, assignment, input, start_row_index); + bp.add_copy_constraint({result.output, zero}); + expression_evaluator_type evaluator_m( + component._W, component._C, component._PI, std::get<1>(mapping_and_constraints)); + mapping.erase(var(0, 1, true, var::column_type::witness)); + mapping.insert({var(0, 1, true, var::column_type::witness), value_m}); + input = {mapping}; + auto result_m = generate_circuit(evaluator_m, bp, assignment, input, start_row_index); + bp.add_copy_constraint({result_m.output, zero}); + } + + return typename component_type::result_type(component, start_row_index); + } + } // namespace components + } // namespace blueprint +} // namespace nil diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 6157c1bca..25cdcead4 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -149,6 +149,7 @@ set(PLONK_TESTS_FILES "verifiers/placeholder/fri_lin_inter" "verifiers/placeholder/fri_array_swap" "verifiers/placeholder/expression_evaluation_component" + "verifiers/placeholder/final_polynomial_check" ) set(FIELDS_TESTS_FILES diff --git a/test/verifiers/placeholder/final_polynomial_check.cpp b/test/verifiers/placeholder/final_polynomial_check.cpp new file mode 100644 index 000000000..546177246 --- /dev/null +++ b/test/verifiers/placeholder/final_polynomial_check.cpp @@ -0,0 +1,159 @@ +//---------------------------------------------------------------------------// +// Copyright (c) 2024 Dmitrii Tabalin +// +// MIT License +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//---------------------------------------------------------------------------// + +#include "nil/blueprint/components/systems/snark/plonk/verifier/final_polynomial_check.hpp" +#include +#include +#define BOOST_TEST_MODULE plonk_final_polynomial_check_component_test + +#include + +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "../../test_plonk_component.hpp" + +using namespace nil; + +template +void test(std::vector &public_input, + bool expected_to_pass) { + + constexpr std::size_t WitnessColumns = WitnessAmount; + constexpr std::size_t PublicInputColumns = 1; + constexpr std::size_t ConstantColumns = 1; + constexpr std::size_t SelectorColumns = 3; + using hash_type = nil::crypto3::hashes::keccak_1600<256>; + constexpr std::size_t TestLambda = 1; + + zk::snark::plonk_table_description desc( + WitnessColumns, PublicInputColumns, ConstantColumns, SelectorColumns); + using ArithmetizationType = crypto3::zk::snark::plonk_constraint_system; + using AssignmentType = blueprint::assignment; + using value_type = typename BlueprintFieldType::value_type; + using var = crypto3::zk::snark::plonk_variable; + + using component_type = blueprint::components::final_polynomial_check; + + std::array witnesses; + std::iota(witnesses.begin(), witnesses.end(), 0); + component_type component_instance(witnesses, std::array(), std::array(), + Power, Lambda); + + typename component_type::input_type instance_input; + std::size_t rotation = 0; + for (std::size_t i = 0; i < Lambda; i++) { + instance_input.points.push_back(var(0, rotation++, false, var::column_type::public_input)); + } + for (std::size_t i = 0; i < 2 * Lambda; i++) { + instance_input.values.push_back(var(0, rotation++, false, var::column_type::public_input)); + } + for (std::size_t i = 0; i < Power + 1; i++) { + instance_input.coefficients.push_back(var(0, rotation++, false, var::column_type::public_input)); + } + + auto result_check = [](AssignmentType &assignment, typename component_type::result_type &real_res) {}; + + if (expected_to_pass) { + crypto3::test_component( + component_instance, desc, public_input, result_check, instance_input, + nil::blueprint::connectedness_check_type::type::STRONG, + Power, Lambda); + } else { + crypto3::test_component_to_fail( + component_instance, desc, public_input, result_check, instance_input, + nil::blueprint::connectedness_check_type::type::STRONG, + Power, Lambda); + } +} + +BOOST_AUTO_TEST_SUITE(blueprint_plonk_test_suite) + +template +void test_random_polynomials(boost::random::mt19937 &gen) { + using value_type = typename BlueprintFieldType::value_type; + nil::crypto3::random::algebraic_engine random_engine(gen); + boost::random::uniform_int_distribution<> dist(0, 2 * Lambda - 1); + // test case generation doesn't work otherwise + BOOST_ASSERT(2 * Lambda == Power + 1); + for (std::size_t i = 0; i < 15; i++) { + std::vector public_input; + std::vector points_with_m; + std::vector values; + for (std::size_t j = 0; j < Lambda; j++) { + value_type point = random_engine(); + public_input.push_back(point); + points_with_m.push_back(point); + points_with_m.emplace_back(-point); + } + for (std::size_t j = 0; j < 2 * Lambda; j++) { + value_type value = random_engine(); + public_input.push_back(value); + values.push_back(value); + } + std::vector> points_values; + for (std::size_t j = 0; j < 2 * Lambda; j += 2) { + points_values.emplace_back(std::make_pair(points_with_m[j], values[j])); + points_values.emplace_back(std::make_pair(points_with_m[j + 1], values[j + 1])); + } + // now we use lagrange interpolation to create a polynomial which would be y at all the (s; -s) + auto polynomial = nil::crypto3::math::lagrange_interpolation(points_values); + BOOST_ASSERT(polynomial.size() == 2 * Lambda); + std::vector coefficients; + for (auto val : polynomial) { + coefficients.push_back(val); + } + BOOST_ASSERT(coefficients.size() == Power + 1); + std::reverse(coefficients.begin(), coefficients.end()); + public_input.insert(public_input.end(), coefficients.begin(), coefficients.end()); + test(public_input, true); + // randomly try to break a constraint + std::size_t rand_index = dist(gen); + public_input[Lambda + rand_index] = random_engine(); + test(public_input, false); + } +} + +BOOST_AUTO_TEST_CASE(blueprint_plonk_final_polynomial_check_component_random_tests) { + using field_type = typename crypto3::algebra::curves::pallas::base_field_type; + + boost::random::mt19937 gen(1444); + test_random_polynomials(gen); +} + +BOOST_AUTO_TEST_SUITE_END()