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()